Skip to content

refactor(web): extract contentSaver + shared progress-merge from monoliths (TASK-2029)#885

Merged
xarmian merged 1 commit into
mainfrom
refactor/task-2029-extract-content-saver
Jul 9, 2026
Merged

refactor(web): extract contentSaver + shared progress-merge from monoliths (TASK-2029)#885
xarmian merged 1 commit into
mainfrom
refactor/task-2029-extract-content-saver

Conversation

@xarmian

@xarmian xarmian commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

TASK-2029 (PLAN-1984 Web-UX audit)

Behavior-preserving refactor. Pulls two tangled clusters out of the item/collection page monoliths into cohesive, unit-tested modules so future feature work can touch them safely.

A. contentSaver.svelte.ts — raw-markdown saver (item detail page)

New web/src/lib/items/contentSaver.svelte.tscreateContentSaver({ debounceMs, save }) returns a saver owning the 1.2s debounce, the pending dirty tracker (was rawPendingMarkdown), and flushNow({ keepalive }) / queue / cancel / clearPending / dirty. The actual PATCH + all reactive item/saveStatus/editorStore bookkeeping stays in the page as the injected save callback, so the module has no Svelte/API deps and is testable in plain node.

Rewired in [collection]/[slug]/+page.svelte:

  • handleRawContentUpdaterawContentSaver.queue(markdown)
  • beforeunload keepalive block (BUG-2024) → rawContentSaver.flushNow({ keepalive: true }) — keepalive-on-unload, debounce-cancel, dirty prompt, and clear-on-save all preserved; collab flush still fires first.
  • flushRawIfPending drain loop → uses saver.pending / saver.cancel() / saver.clearPending() (its own awaited PATCH loop unchanged).
  • item-swap reset + the legacy non-collab handleContentUpdate path cancel the saver too, preserving the shared-timer mode-toggle non-trample.

pending is a plain closure var (not $state) per CONVE-1688 — the reactive dirty flag stays in editorStore.

B. progressMerge.ts — deduped progress fetch/merge (collection page)

New web/src/lib/collections/progressMerge.ts — pure plansProgressToMap + mergeChildAndCheckboxProgress (BUG-1509 preference logic) plus fetchCollectionProgress (parallel fetch + merge, per-fetch .catch(() => [])). Both duplicated copies in [collection]/+page.svelte (refreshProgress and inline in loadCollection) now call the shared fn; each keeps its own seq-guard / label / error handling so behavior is identical.

Tests

  • contentSaver.test.ts (7, fake timers): debounce coalescing, dirty set/clear, flush-now cancels pending debounce, keepalive flag plumbed, no-save-when-clean, cancel leaves dirty, default debounce.
  • progressMerge.test.ts (8): plans map, all three merge branches, defensive orphan-checkbox, empties.
  • Full suite: 153 pass. npm run check: 0 errors. npm run build: success. Codex review: CLEAN.

Line deltas

  • [collection]/+page.svelte: 2957 → 2891 (-66)
  • [collection]/[slug]/+page.svelte: 4648 → ~4660 (raw-save mechanics moved to the tested module; the page-coupled PATCH body remains as the injected callback, so net LOC on this file is flat — the win is the extracted, tested primitive + removed unload duplication)

Behavior-preserving: no user-visible change to save timing, keepalive-on-unload (BUG-2024), dirty prompt, collab flush order, or progress badges.

@xarmian xarmian merged commit 7d76fc2 into main Jul 9, 2026
3 of 4 checks passed
@xarmian xarmian deleted the refactor/task-2029-extract-content-saver branch July 9, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant