CODERAVEN · PLAN · REVIEW COPY

Snapshot Write Collapse

Finishing the Phase 6 work the previous plan’s completion correctly failed — by measuring it first.

13Tasks done
9Phases
30Files touched
3Commits
The gist

The automatic completion of the silent-failure sweep failed three tasks the check-in had marked done, and it was right to. The checkbox named six stores to give batch-write APIs; one was built and the other five were argued away rather than measured.

This plan does the measurement, implements the one case it supports, and records the rest as decisions at the call sites instead of leaving them as silently-open checkboxes.

The problem

CompletionBridgeImporter assigns snapshots[projectId] three times per imported file — updateTasks, addSummaryEntry and markPlanComplete, all against the same snapshot. Each assignment publishes and fires the cache-invalidating didSet, so one completion costs three full render cascades. CheckinBridgeImporter has the same shape with two.

The other four stores turned out to write once per file, which cannot be batched into fewer than once. And the completion’s claim that SummaryBridgeImporter “still assigns per-record” was wrong — it writes once per file to three different stores.

Approach

Measure per file, not per record

That distinction is where the previous assessment went wrong in both directions. It is why PlanTaskStore genuinely needed batching (N writes per task) and why four other stores did not (one write per file).

Assemble locally, write once

updateTasks returns the mutated snapshot instead of persisting it, the summary entry is built as a value rather than round-tripped through the store, and markPlanComplete mutates a passed copy. One assignment at the end.

Record the declines where someone will hit them

A comment at each call site naming the measured write count, and one on beginBatchCapture saying why it was not extended. A decision that lives only in a plan file is a decision nobody will find.

Decisions made for you

Declined

beginBatchCapture was not extended

The task said to make it defer the @Published assignment. That is the wrong mechanism: addSummaryEntry reads snapshots[projectId] mid-flow and getSnapshot(id:) reads through a cached id index, so deferring the assignment means every reader inside the batch consults staged state or silently sees stale data — the exact failure class this codebase keeps producing. Restructuring two call sites gets the same publish count with a fraction of the blast radius.

Declined

Four stores get no batch API

ChronicleManager, ScratchpadManager, ToDoStore and QuickTaskManager each write once per imported file. One write cannot be batched into fewer than one; the API would add surface area and remove zero publishes.

Correction

The completion’s read of SummaryBridgeImporter was wrong

It said “still assigns per-record.” It writes once per file, to three different stores. Collapsing across stores needs global publish suspension — a different and riskier design. No change made.

The work

What the measurement actually says

0/0 done

Phase 1 — Collapse the completion importer's three snapshot writes to one

6/6 done The measured win — 3 snapshot writes per completion file collapse to 1.
  • Add a non-persisting variant of the summary-entry build so the entry can be appended to a local copy instead of round-tripping through the store
  • Change updateTasks(on:with:projectId:) to return the mutated snapshot without writing it
  • Change markPlanComplete to mutate a passed-in copy and return it, rather than reading and assigning
  • Write the fully-assembled snapshot exactly once at the end of processValidBridge
  • Confirm createChronicleRecord and QuickVerifyStore.generateFromCompletion still receive the fully-updated snapshot, since they read task state from it
  • Build and verify the artifact

Phase 2 — Collapse the check-in importer's two snapshot writes to one

4/4 done Same shape, 2 to 1. Write order matters: it must land before the Chronicle record re-fetches.
  • Change updateTasks to return the mutated snapshot without writing
  • Append the summary entry to the local copy rather than through the store
  • Write the snapshot once at the end
  • Build and verify the artifact

Phase 3 — Record the decisions, don't leave them implied

3/3 done The point of the plan — decisions recorded where someone will hit them.
  • Comment at each of the four not-warranted stores' importer call sites stating the measured write count and why no batch API was added
  • Comment on beginBatchCapture() recording why it was NOT extended to defer the @Published assignment
  • Re-file the three failed tasks from the previous plan as explicitly decided rather than silently open

Decisions

0/0 done

Manual testing

2 tasks None of this was observed running — GUI verification is blocked from the terminal.
  • Run /cr-complete on any plan and confirm the plan's status, tasks, and summary entry all land as beforeyou
  • Run /cr-checkin and confirm task completion still flows through to the plan cardyou

Risks & edge cases

Ordering

The single write must land before the Chronicle record

createChronicleRecord deliberately re-fetches the snapshot to read updated task counts. Moving the write later would hand it stale counts — silently. The order is commented at the call site.

Needs your eyes

Nothing observed at runtime

Compile-verified only. A /cr-complete and a /cr-checkin both need to land and be checked — specifically that plan status, task state and the summary entry all still appear.

Out of scope

Cross-store publish suspension

The only remaining way to reduce publishes further is a global mechanism that suspends every store’s objectWillChange for the duration of an import. That is a real design with real risk, and it should be its own decision rather than a side effect of finishing a checkbox.

Final review

What this does well. It replaces an argument with a measurement, and the measurement changed the answer in both directions — it found a real 3→1 win that the original checkbox never named, and it disproved a claim the completion agent made about SummaryBridgeImporter. The declines are now comments in the code rather than open checkboxes that will read as unfinished work forever.

Where it is weakest. The importers now depend on a write happening before a re-fetch, which is an ordering constraint held by a comment rather than by the type system. And this is a performance change with no runtime measurement behind it — the publish counts are read off the code, not observed.

88/100

Higher than the sweep it finishes, because the scope is small, the measurement is concrete, and the declines are argued from evidence rather than convenience. Held back from higher by the ordering constraint in both importers and by the absence of any runtime observation.