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
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.
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.
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 donePhase 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
markPlanCompleteto 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
createChronicleRecordandQuickVerifyStore.generateFromCompletionstill 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
updateTasksto 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@Publishedassignment - Re-file the three failed tasks from the previous plan as explicitly decided rather than silently open
Decisions
0/0 doneManual testing
2 tasks None of this was observed running — GUI verification is blocked from the terminal.- Run
/cr-completeon any plan and confirm the plan's status, tasks, and summary entry all land as beforeyou - Run
/cr-checkinand confirm task completion still flows through to the plan cardyou
Risks & edge cases
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.
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.
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.