···11+# syncSource Migration Test Plan
22+33+## Background
44+55+The `syncSource` column was removed from desktop and server schemas as part of the sync refactor. Device metadata now lives inside the item's `metadata` JSON field as `_sync: {createdBy, createdAt, modifiedBy, modifiedAt}` with raw UUIDs (no prefixes).
66+77+### Current State by Platform
88+99+| Platform | syncSource Status | Details |
1010+|----------|------------------|---------|
1111+| Desktop Electron (`backend/electron/`) | Removed from schema + code | No `syncSource` in generated schema or sync code |
1212+| Server (`backend/server/`) | Removed from schema + code | No `syncSource` in db.js or index.js |
1313+| Unified sync module (`sync/sync.js`) | **STILL USES IT** | Push filter, post-push update, pending count all reference `syncSource` |
1414+| iOS mobile (`backend/tauri-mobile/`) | Still has `sync_source` | Actively used for push filtering, merge, post-push |
1515+| Tauri Desktop (`backend/tauri/`) | Still has `sync_source` | Item struct, column access, sync operations |
1616+| Browser Extension (`backend/extension/`) | Clean | No references |
1717+1818+## Issues Found During Investigation
1919+2020+### Critical: `sync/sync.js` not refactored
2121+2222+The unified sync module still actively uses `syncSource`:
2323+- `sync.js:102,107` — push filter: `i.syncSource === ''`
2424+- `sync.js:165` — sets `syncSource: 'server'` after push
2525+- `sync.js:217` — pending count: `i.syncSource === ''`
2626+- `sync.js:279` — server change reset: `syncSource: ''`
2727+- `better-sqlite3.js:34` — adapter schema includes `syncSource TEXT DEFAULT ''`
2828+2929+If this module is used against a DB without `syncSource`, the `i.syncSource === ''` filter evaluates `undefined === ''` → `false`, causing all items to be excluded from push.
3030+3131+### Stale test assertion
3232+3333+`backend/server/test.js:620` asserts `syncSource` column exists:
3434+```js
3535+assert.ok(columnNames.includes("syncSource"))
3636+```
3737+This will fail with the current schema.
3838+3939+### Behavioral change: extension-origin items
4040+4141+Items with `syncSource = 'history'|'tab'|'bookmark'` from the browser extension previously had `syncedAt = 0` and were excluded from push by the old `syncSource !== ''` guard. After migration, `syncedAt = 0` means "never synced, should push" under the new algorithm → these items will be pushed on first sync after upgrade. This is intentional per the refactor design.
4242+4343+## Migration Behavior
4444+4545+### Desktop (Electron)
4646+4747+- **Schema creation**: `CREATE TABLE items` does NOT include `syncSource`
4848+- **Migration**: `migrateSyncColumns` only adds `syncId` — does NOT add or remove `syncSource`
4949+- **Table rebuild** (`migrateItemTypes`): If CHECK constraint update triggers, table is rebuilt WITHOUT `syncSource` → column silently dropped
5050+- **If no rebuild triggers**: `syncSource` persists as orphaned column
5151+- **SELECT * queries**: Return `syncSource` if it exists, but TypeScript `Item` interface ignores it
5252+5353+### Server
5454+5555+- **Schema creation**: No `syncSource` in CREATE TABLE
5656+- **Column renames**: Maps `sync_id`→`syncId`, `synced_at`→`syncedAt`, etc. — NO mapping for `sync_source`/`syncSource`
5757+- **Table rebuild** (`rebuildTableIfNeeded`): If triggered by pending renames, target schema excludes `syncSource` → dropped
5858+- **If no rebuild triggers**: `syncSource` persists as orphaned column
5959+- **Queries**: Use explicit column lists (not SELECT *) → orphaned column is harmless
6060+- **Validation**: `validateSchema()` checks `REQUIRED_SYNC_COLUMNS` from `schema/v1.json` — extra columns ignored
6161+6262+### iOS Mobile
6363+6464+No changes. Still uses `sync_source` for push filtering, merge operations, and post-push updates. iOS has its own local DB schema — not affected by desktop/server refactor.
6565+6666+## Test Scenarios
6767+6868+### Scenario 1: Server DB with snake_case schema (full migration path)
6969+7070+**Setup:** Create DB with old snake_case schema including `sync_source`:
7171+```sql
7272+CREATE TABLE items (
7373+ id TEXT PRIMARY KEY, type TEXT NOT NULL, content TEXT, metadata TEXT,
7474+ sync_id TEXT DEFAULT '', sync_source TEXT DEFAULT '',
7575+ synced_at INTEGER DEFAULT 0, created_at INTEGER NOT NULL,
7676+ updated_at INTEGER NOT NULL, deleted_at INTEGER DEFAULT 0
7777+);
7878+```
7979+8080+Seed items:
8181+- Item A: `sync_source = ''`, `synced_at = 0` (local, never synced)
8282+- Item B: `sync_source = 'server'`, `synced_at = 1000`, `sync_id = 'remote-1'` (pulled from server)
8383+- Item C: `sync_source = 'server'`, `synced_at = 1000`, `sync_id = 'remote-2'`, `deleted_at = 5000` (soft-deleted synced item)
8484+8585+**Expected:**
8686+- `rebuildTableIfNeeded` triggers (snake→camelCase renames pending)
8787+- All snake_case columns renamed; `sync_source` dropped (not in target schema)
8888+- `PRAGMA table_info(items)` does NOT contain `syncSource` or `sync_source`
8989+- All data preserved with correct values
9090+- `validateSchema()` passes
9191+9292+### Scenario 2: Server DB with camelCase schema + orphaned `syncSource` (no rebuild)
9393+9494+**Setup:** DB already has camelCase columns but also has `syncSource`:
9595+```sql
9696+CREATE TABLE items (
9797+ id TEXT PRIMARY KEY, type TEXT NOT NULL CHECK(type IN ('url','text','tagset','image')),
9898+ content TEXT, metadata TEXT, syncId TEXT DEFAULT '', syncSource TEXT DEFAULT '',
9999+ syncedAt INTEGER DEFAULT 0, createdAt INTEGER NOT NULL,
100100+ updatedAt INTEGER NOT NULL, deletedAt INTEGER DEFAULT 0
101101+);
102102+```
103103+104104+**Expected:**
105105+- `rebuildTableIfNeeded` does NOT trigger (no pending renames)
106106+- `syncSource` column persists (orphaned but harmless)
107107+- `validateSchema()` passes (extra columns ignored)
108108+- All operations (saveItem, getItems, getItemsSince) work correctly
109109+110110+### Scenario 3: Items with various `syncSource` values survive migration
111111+112112+**Setup:** Scenario 1 DB with additional items:
113113+- Item D: `sync_source = 'history'`, `synced_at = 0` (extension import)
114114+- Item E: `sync_source = 'bookmark'`, `synced_at = 0` (extension import)
115115+- Item F: `sync_source = 'tab'`, `synced_at = 0` (extension import)
116116+117117+**Expected:**
118118+- All items survive migration with correct data
119119+- Under new push algorithm (`syncedAt = 0` → push), items D/E/F become eligible for push
120120+- `getItems` returns all non-deleted items
121121+122122+### Scenario 4: Server receives items WITHOUT `syncSource` from refactored desktop
123123+124124+**Setup:** Fresh server DB (no `syncSource`). Simulate desktop push: `POST /items` with `{ type, content, tags, sync_id, metadata }` (no `syncSource`).
125125+126126+**Expected:**
127127+- `saveItem` succeeds without errors
128128+- Item stored with all fields
129129+- `getItems` returns item correctly
130130+131131+### Scenario 5: `_sync` metadata preserved through migration
132132+133133+**Setup:** Items where `metadata` JSON contains:
134134+```json
135135+{"_sync": {"createdBy": "abc-uuid", "createdAt": 1000, "modifiedBy": "abc-uuid", "modifiedAt": 2000}, "title": "Example"}
136136+```
137137+138138+**Expected:**
139139+- `metadata` column value preserved as-is through table rebuild
140140+- `JSON.parse(item.metadata)._sync.createdBy === 'abc-uuid'`
141141+142142+### Scenario 6: Desktop table rebuild drops `syncSource`
143143+144144+**Note:** Requires Electron environment. Manual test or future unit test.
145145+146146+**Setup:** Desktop DB with old CHECK constraint (`type IN ('note','text','tagset')`) and `syncSource` column, with a 'note' type item.
147147+148148+**Expected:**
149149+- `migrateItemTypes` triggers table rebuild
150150+- New table does NOT include `syncSource` → column dropped
151151+- Data copied correctly
152152+153153+### Scenario 7: Fix stale server test assertion
154154+155155+`backend/server/test.js:620` asserts `syncSource` exists. Update to assert it does NOT exist, or remove the assertion.
156156+157157+## Implementation
158158+159159+### Primary: `backend/server/test-migration.js`
160160+161161+Node.js test file using `node:test` (matches existing `backend/server/test.js` pattern):
162162+- Uses `better-sqlite3` directly to create legacy DBs in temp directories
163163+- Calls `db.js` `initializeSchema` to trigger migrations
164164+- Verifies schema (`PRAGMA table_info`) and data post-migration
165165+- Self-cleaning via `fs.rmSync`
166166+167167+Run: `node --test backend/server/test-migration.js`
168168+169169+### Secondary: Fix `backend/server/test.js`
170170+171171+Update or remove the stale `syncSource` assertion at line 620.
172172+173173+### Future: Refactor `sync/sync.js`
174174+175175+The unified sync module needs the same refactor as desktop/server — replace `syncSource` filtering with timestamp-based filtering. This is a separate task.
176176+177177+## Files to Modify
178178+179179+| File | Change |
180180+|------|--------|
181181+| `backend/server/test-migration.js` | New file — migration simulation tests (Scenarios 1-5) |
182182+| `backend/server/test.js` | Fix stale `syncSource` assertion (Scenario 7) |
183183+| `sync/sync.js` | Future — refactor to remove `syncSource` references |
184184+185185+---