- New SyncChange / FieldChange / SyncOp types replace `any[]` in
applyServerChanges. The wire format is now self-documenting and
TypeScript catches malformed callsites at compile time.
- isValidSyncChange() validates incoming server payloads at the boundary:
malformed entries are dropped with a single warn log, valid ones are
applied. A bad row from the server can no longer corrupt IndexedDB.
Hand-rolled type guards keep us free of a runtime-validation dep.
- applyServerChanges() and readFieldTimestamps() are now top-level
exports (extracted out of createUnifiedSync's closure) so they can be
imported directly by tests. Behaviour is unchanged — the closure
variant inside the sync manager just resolves the module-level
symbol now.
- New sync.test.ts covers:
* pure isValidSyncChange and readFieldTimestamps cases
* field-level LWW: server-newer wins, split outcome when local-newer
on one field and server-newer on another
* insert with __fieldTimestamps stamping
* soft-delete LWW guard
* malformed-entry drop with valid entries surviving
* sync-loop guard: server-applied writes don't generate _pendingChanges
- fake-indexeddb added as devDependency for the integration tests.
Note: the monorepo's vitest install is currently tangled across mixed
@vitest/* package versions in the lockfile, so `pnpm test` fails before
reaching this file. The tests are written to pass on any vitest 4.x once
that's untangled — needs its own dedicated cleanup pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- calendar/types: replace duplicate recurrenceRule with recurrenceDate on CalendarEvent; map it in timeBlockToCalendarEvent
- recurrence: drop stale Record casts now that LocalTimeBlock types isRecurrenceException and recurrenceDate
- todo: route recurrenceRule through TimeBlock in createTask/updateTask, load it from block in useTaskForm; accept labelIds via metadata; remove stale projectId casts
- calendar/events: include linkedBlockId/parentBlockId/recurrenceDate in createDraftEvent
- habits: drop unused db / LocalTimeBlock imports
- eslint-config: disable consistent-type-imports (parser conflict with .svelte.ts files)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Single source of truth for the active user via data/current-user.ts;
layout pushes authStore.user.id into it on every auth state change.
- Dexie creating-hook auto-stamps userId from getEffectiveUserId(); the
updating-hook strips userId from modifications so records are
effectively user-immutable after creation.
- BaseRecord gains an optional userId so module types inherit it without
per-module declarations. All hardcoded 'guest'/'local' fallbacks in
module type-converters and session timer stores are deleted; the dead
userId field is removed from the public view types where it was
unused (Task, Conversation, Template, Deck, Plant, Contact, etc.).
- New guest-migration.ts: on first authenticated session, walks every
sync-tracked table, deletes guest-owned records and re-adds them so
the creating-hook re-stamps with the real user id and produces fresh
insert pending-changes with the full payload. Stale guest pending-
changes are cleared up-front.
- Drive-by: root onMount now returns its cleanup synchronously; the
previous async form silently dropped the cleanup callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>