Deck/homework writes sync themselves when sync is on

Sync was push-on-demand only: a deck frozen on the laptop stayed in dirty
until someone opened Settings and pressed Sync now. The two stores whose
writes mean "the user just made something" now schedule one debounced
background syncNow("auto") through lib/sync-auto: debounced 2.5s so a burst
of writes is one push, one run at a time, silent on failure (the data stays
dirty and Settings still offers the manual sync), retried on the browser's
online event, and imported dynamically so the sync code stays out of the
first load with no import cycle.

markSyncDirty() now reports whether it actually marked, which keeps pull and
backup-import paths quiet while a real import pushes like any other local
change. Answers and chat keep the existing manual behaviour on purpose —
they change every few seconds while studying.

Tests: phase-1 (a deck write pushes itself, then Sync now is a no-op — 50
checks) and phase-3 (a check run pushes the new homework record — 30 checks).
README: decision 59. SW -> freezmo-v46.
This commit is contained in:
Martin Dvořák 2026-10-12 16:40:49 +00:00
commit abacfb6018
8 changed files with 173 additions and 11 deletions

View file

@ -1361,6 +1361,19 @@ Things I chose while building, with the reasoning:
phase-1 suite (43 checks, including the real route mapping against a stub Ollama returning a
monthly-limit body, a two-device sync round-trip, and the generation guards below).
59. **Adding a deck or a homework syncs itself.** Sync used to be push-on-demand only: a deck
frozen on the laptop sat in `dirty` until someone opened Settings and pressed Sync now. The two
stores whose writes mean "the user just made something" (`lib/storage`, `lib/homework`) now
schedule `syncNow("auto")` through `lib/sync-auto` — debounced 2.5 s so a burst of writes is one
push, one run at a time, silent on failure (the data stays dirty and Settings still offers the
manual sync), and retried on the browser's `online` event. Deliberately *not* wired to answers
or chat: those change every few seconds while studying. The module is imported dynamically from
the stores, so the sync code stays out of the first load and no import cycle forms.
`markSyncDirty()` now reports whether it actually marked, which also keeps pull/import paths
quiet while a backup import pushes like any other local change. Verified by the phase-1 suite
(a deck write pushes itself, then Sync now is a no-op) and the phase-3 suite (a check run pushes
the new homework record).
58. **The smart review follows the deck list.** Deleting a deck used to leave its due schedules
and mistakes in the library counts: `dueCount` counted raw SRS rows (no deck lookup) and both
counts were memoised on the learning store alone, so a deck change never recomputed them. The