fix(db): apply migrations by hash + recover workspace->task rename #500 #502
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
PR Title
fix(db): robust migrations (hash-based), recover workspace→task rename, and add DB setup telemetry
Why (Product/User focus)
Fixes app startup failures where users see repeated SQLITE_ERROR: no such table: tasks after updates.
Prevents “silent data loss” feelings in dev builds where the app accidentally uses a different DB location (e.g. Application Support/Electron) and appears empty.
Improves reliability of upgrades for long-lived installs (including .dmg users) by ensuring new migrations always apply, even if their DB has a “weird” history.
Adds a privacy-safe signal so we can detect DB setup failures in the wild and react faster.
What changed (Engineering focus)
Migrations now apply by missing hash (not timestamps)
Drizzle’s default sqlite-proxy migrator can skip migrations forever if a DB has newer created_at values than the new migration’s when.
We now read migrations via readMigrationFiles() and apply any migration whose hash is not in __drizzle_migrations.
Foreign key safety during schema transitions
Migrations run with PRAGMA foreign_keys=OFF and restore ON after, preventing failures on legacy/orphaned data during table rebuild migrations.
Recovery for partial workspace→task rename migration
Detects intermediate state (e.g. __new_conversations exists) and completes the migration so DB doesn’t get stuck.
Manual-fix safe
If the DB already reflects the workspaces → tasks rename but the migration wasn’t recorded, we mark it applied without re-running destructive SQL.
Dev DB path correctness + continuity
Ensure app.setName('Emdash') happens before any app.getPath('userData') calls (prevents defaulting to Application Support/Electron).
resolveDatabasePath() now attempts to migrate an existing emdash.db from sibling legacy dirs (e.g. Electron/) into the current app’s userData.
Telemetry
Added db_setup event (privacy-safe): outcome, applied_migrations(_bucket), recovered, and error_type on failure.
Files changed
src/main/services/DatabaseService.ts
src/main/db/path.ts
src/main/entry.ts
src/main/main.ts
src/main/telemetry.ts
How to test
Existing DB with workspaces: start app → migrations run → tasks exists, workspaces gone, no “no such table: tasks”.
DB with later migrations already applied (timestamp-skew): start app → rename migration still applies (hash-based).
Partial migration state (__new_conversations leftover): start app → recovery completes → no leftover _new*.
Dev path check: confirm DB lives under the app’s userData (not Electron/) and legacy DB is migrated if present.
Notes / Safety
No schema/migration SQL files were modified; this PR only changes how we apply migrations and how we recover safely.
Telemetry is best-effort and does not include paths, names, prompts, or other PII.
#500