PERF: one optimistic mutation helper with client IDs and durable Undo receipts (#663) #667

Open
opened 2026-10-02 05:20:41 +00:00 by kayg · 11 comments
Owner

Context: #663 instant-interaction audit. No product change is part of the audit job.

Own rule 4 and shared accepted/durable timing in DESIGN §58. This issue is the only owner of client-ID idempotency, mutation receipts and durable inverse Undo. Tab adoption issues only supply operations and adapters.

Evidence at c4a61e8cf0:

  • Calendar optimistic pending IDs and in-memory Undo exist (apps/web/src/lib/calendar/edits.ts:86, :305).
  • Files uses Undo closures after acknowledgement (apps/web/src/lib/files/transfer.ts:108, :138); FilesBrowser removes rows after awaited trash (FilesBrowser.svelte:714).
  • Mail read-state changes wait for POST (apps/web/src/lib/mail/MailView.svelte:238); Money waits for a write and then reloads (apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:133). These are not one durable receipt contract.

Expected:

  • One synchronous update changes the item, selection and next item in a single frame, across all retained views. A client-generated operation ID identifies one intent. The server stores a User-scoped durable receipt and inverse transactionally with the accepted mutation.
  • Repeated IDs return the same result; an ID with different input is rejected. A timeout/disconnect is unknown, not a rejection. Provide receipt lookup/reconciliation before replay or rollback. Keep conditional-write conflicts and authoritative server validation.
  • Undo uses the receipt inverse, survives reconnect/restart and checks current access and revision. Do not overwrite an intervening edit. Reuse #539/#616 Undo toasts, Calendar pending helpers and Files agent Undo journals.
  • Provider side effects use existing durable queues. Track accepted and durable times separately in content-free logs. The server remains the single writer through calternal-fs.
  • Credential issuance, sign-out, revocation and destructive Admin security operations keep their existing confirmation/step-up boundary. A safe optimistic pending state must never grant authority or claim irreversible work is complete.

Tests:

  • Same-frame item/selection publication; cross-view updates; definitive 4xx rollback; lost acknowledgement; duplicate/racing IDs; restart before/after receipt commit; receipt expiry; Undo after restart; intervening edit conflict; User/revocation isolation.
  • Existing status assertions and data-loss tests stay unchanged. Add one real-server adversarial regression round for new endpoints.
  • Production/HDD locked VM: ≥5 action samples, median/p95/max, accepted state ≤150 ms; report durable latency separately and burst CPU/RSS.

Reuse/dependencies: #555 user scope, shared cache/snapshot/change owners, Files Undo, Calendar edits, Mail queue. #640/#641/#642 remain owners of their active UI work.

Measurement evidence: see the production/HDD matrix posted on #663. Latencies of sampled endpoints do not prove the shared contract is complete.

Locked production measurement evidence:
The audit intentionally made no User mutation as an acceptance test: 0 action/Undo timing samples. Read latency cannot establish the ≤150 ms accepted-action target. The client awaited-write paths and in-memory inverse closures above are source evidence; this owner issue must supply the durable receipt and accepted/durable benchmark.
Runtime server source cc25c441b7, binary SHA-256 2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9, shared release binary with embedded production SPA. The source audit uses the newer c4a61e8 base; no code equivalence claim. Perf VM HDD emulator uses bench/hdd-emu.sh (direct-I/O/ext4, 8 ms read/write delay, 200 IOPS and 150 MiB/s caps) and flock -w 14400 /root/perf.lock for every measured phase. First valid qualification QD1 115.3 IOPS/8.028 ms median, QD16 200.7 IOPS/96.993 ms; load 0.10/0.18/0.08 and 0.20/0.20/0.09. Fixture: 366 Daily notes, 10,980 Logs, 100 Files/Photos, 20 Notes/Tasks, three Budgets and 100 transactions; Mail empty and Admin one User. No matching transport/HDD/fixture baseline exists, so no regression ratio is claimed. Full raw scopes, failure retention and endpoint baseline references are on #663.

Context: #663 instant-interaction audit. No product change is part of the audit job. Own rule 4 and shared accepted/durable timing in DESIGN §58. This issue is the only owner of client-ID idempotency, mutation receipts and durable inverse Undo. Tab adoption issues only supply operations and adapters. Evidence at c4a61e8cf090170f35b1bed3350d9de20c83ecd5: - Calendar optimistic pending IDs and in-memory Undo exist (`apps/web/src/lib/calendar/edits.ts:86`, `:305`). - Files uses Undo closures after acknowledgement (`apps/web/src/lib/files/transfer.ts:108`, `:138`); FilesBrowser removes rows after awaited trash (`FilesBrowser.svelte:714`). - Mail read-state changes wait for POST (`apps/web/src/lib/mail/MailView.svelte:238`); Money waits for a write and then reloads (`apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte:133`). These are not one durable receipt contract. Expected: - One synchronous update changes the item, selection and next item in a single frame, across all retained views. A client-generated operation ID identifies one intent. The server stores a User-scoped durable receipt and inverse transactionally with the accepted mutation. - Repeated IDs return the same result; an ID with different input is rejected. A timeout/disconnect is unknown, not a rejection. Provide receipt lookup/reconciliation before replay or rollback. Keep conditional-write conflicts and authoritative server validation. - Undo uses the receipt inverse, survives reconnect/restart and checks current access and revision. Do not overwrite an intervening edit. Reuse #539/#616 Undo toasts, Calendar pending helpers and Files agent Undo journals. - Provider side effects use existing durable queues. Track accepted and durable times separately in content-free logs. The server remains the single writer through calternal-fs. - Credential issuance, sign-out, revocation and destructive Admin security operations keep their existing confirmation/step-up boundary. A safe optimistic pending state must never grant authority or claim irreversible work is complete. Tests: - Same-frame item/selection publication; cross-view updates; definitive 4xx rollback; lost acknowledgement; duplicate/racing IDs; restart before/after receipt commit; receipt expiry; Undo after restart; intervening edit conflict; User/revocation isolation. - Existing status assertions and data-loss tests stay unchanged. Add one real-server adversarial regression round for new endpoints. - Production/HDD locked VM: ≥5 action samples, median/p95/max, accepted state ≤150 ms; report durable latency separately and burst CPU/RSS. Reuse/dependencies: #555 user scope, shared cache/snapshot/change owners, Files Undo, Calendar edits, Mail queue. #640/#641/#642 remain owners of their active UI work. Measurement evidence: see the production/HDD matrix posted on #663. Latencies of sampled endpoints do not prove the shared contract is complete. Locked production measurement evidence: The audit intentionally made no User mutation as an acceptance test: 0 action/Undo timing samples. Read latency cannot establish the ≤150 ms accepted-action target. The client awaited-write paths and in-memory inverse closures above are source evidence; this owner issue must supply the durable receipt and accepted/durable benchmark. Runtime server source cc25c441b7a974185622a1dee853cf38686d2b67, binary SHA-256 2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9, shared release binary with embedded production SPA. The source audit uses the newer c4a61e8 base; no code equivalence claim. Perf VM HDD emulator uses bench/hdd-emu.sh (direct-I/O/ext4, 8 ms read/write delay, 200 IOPS and 150 MiB/s caps) and flock -w 14400 /root/perf.lock for every measured phase. First valid qualification QD1 115.3 IOPS/8.028 ms median, QD16 200.7 IOPS/96.993 ms; load 0.10/0.18/0.08 and 0.20/0.20/0.09. Fixture: 366 Daily notes, 10,980 Logs, 100 Files/Photos, 20 Notes/Tasks, three Budgets and 100 transactions; Mail empty and Admin one User. No matching transport/HDD/fixture baseline exists, so no regression ratio is claimed. Full raw scopes, failure retention and endpoint baseline references are on #663.
Author
Owner

Started #667 on job/perf-mut-667, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Read #663 matrix and DESIGN §58; imported its two documentation commits. Scope: one shared transactional receipt/Undo contract and one client mutation helper, with at most one proving Tab adapter. No push or deploy. Other shared primitives stay with #665/#666/#668.

Started #667 on `job/perf-mut-667`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Read #663 matrix and DESIGN §58; imported its two documentation commits. Scope: one shared transactional receipt/Undo contract and one client mutation helper, with at most one proving Tab adapter. No push or deploy. Other shared primitives stay with #665/#666/#668.
Author
Owner

Core receipt slice committed as 245ca30b3. calternal-db clippy and tests pass. Tests show that racing duplicate IDs commit one domain write, dropped transactions leave no receipt, replay and inverse Undo survive an Index reopen, and different input / other User / expired ID cannot reuse the receipt.

Proving adapter choice: Mail's read-marking preference. This is an Index-only User preference with no provider effect; existing synchronous Mail Seen changes remain with the Mail adoption issue. Legacy preference PATCH keeps its 204 status and advances the revision so an Undo cannot erase an intervening legacy write. New forward, receipt lookup and Undo routes check the current data scope before accessing receipts. No other shared primitive is implemented.

Decisions: inverse retention is 24 hours, distinct from the existing eight-second Undo toast. IDs remain tombstones after expiry; 10,000 receipts per User bound this initial store. The existing Db WAL/NORMAL policy remains: committed receipts survive process restart, but this does not add a power-loss guarantee. File/provider adapters must use their recovery journals/durable queues; this job does not invent a second filesystem writer.

Core receipt slice committed as `245ca30b3`. `calternal-db` clippy and tests pass. Tests show that racing duplicate IDs commit one domain write, dropped transactions leave no receipt, replay and inverse Undo survive an Index reopen, and different input / other User / expired ID cannot reuse the receipt. Proving adapter choice: Mail's read-marking preference. This is an Index-only User preference with no provider effect; existing synchronous Mail Seen changes remain with the Mail adoption issue. Legacy preference PATCH keeps its 204 status and advances the revision so an Undo cannot erase an intervening legacy write. New forward, receipt lookup and Undo routes check the current data scope before accessing receipts. No other shared primitive is implemented. Decisions: inverse retention is 24 hours, distinct from the existing eight-second Undo toast. IDs remain tombstones after expiry; 10,000 receipts per User bound this initial store. The existing Db WAL/NORMAL policy remains: committed receipts survive process restart, but this does not add a power-loss guarantee. File/provider adapters must use their recovery journals/durable queues; this job does not invent a second filesystem writer.
Author
Owner

Client primitive committed as 29470a99e0d230f22311dea5e9e17a46a8eb8966: one synchronous adapter publication, bounded User-scoped pending intent storage, same-ID reconciliation before replay, no rollback for transport/5xx/expired lookup outcomes. Seven focused tests pass, including same-frame shared-view publication, restart after lost acknowledgement, User-switch fencing and coalesced reconciliation. The initial web check passes with zero errors/warnings. No dependencies added or versions changed.

Client primitive committed as `29470a99e0d230f22311dea5e9e17a46a8eb8966`: one synchronous adapter publication, bounded User-scoped pending intent storage, same-ID reconciliation before replay, no rollback for transport/5xx/expired lookup outcomes. Seven focused tests pass, including same-frame shared-view publication, restart after lost acknowledgement, User-switch fencing and coalesced reconciliation. The initial web check passes with zero errors/warnings. No dependencies added or versions changed.
Author
Owner

Mail proving adoption is committed in 19f4f4f02, with server adapter/timing commits 680aafdad and f25e89223. Seventeen focused web tests and the production SPA build pass. Settings and retained Mail readers now share the preference value, and body loading does not wait for preference reconciliation. The UI has Undo, reload on rejection/load error, and Check again for unknown acknowledgements. A saved Undo action is fenced at session end.

Expiry correction: inverse application expires after 24 hours; receipt lookup and same-ID replay continue to return the committed outcome. This prevents rollback or a second write when an acknowledgement was lost longer than the Undo window. Core regression commit: 5a3237ee2.

The required one-time fetch/merge of origin/dev returned Already up to date. Core migration 7 and Mail migration 10 remain free on that remote branch. Final per-crate gates are queued behind the current server build. The earlier server check had no production SPA directory; the prerequisite is now built. One prerequisite compilation was interrupted (exit 130); it is retained in the local log and resumed, not counted as a passed gate.

Interfaces for #669–#676/#701: Db::begin_mutation -> Replay/New transaction, MutationTransaction::connection/take_inverse/commit, Db::mutation_receipt, and mutationController with synchronous publish/confirm/reject plus send/lookup. The adoption guide is docs/mutations/receipts.md. #665/#666/#668 caches and change streams are not reimplemented. A generic SQLite receipt cannot atomically commit a file rename: Files/Calendar/Notes adapters must reuse their recovery journals and calternal-fs; provider adapters enqueue their durable jobs inside the same transaction.

Mail proving adoption is committed in `19f4f4f02`, with server adapter/timing commits `680aafdad` and `f25e89223`. Seventeen focused web tests and the production SPA build pass. Settings and retained Mail readers now share the preference value, and body loading does not wait for preference reconciliation. The UI has Undo, reload on rejection/load error, and Check again for unknown acknowledgements. A saved Undo action is fenced at session end. Expiry correction: inverse application expires after 24 hours; receipt lookup and same-ID replay continue to return the committed outcome. This prevents rollback or a second write when an acknowledgement was lost longer than the Undo window. Core regression commit: `5a3237ee2`. The required one-time fetch/merge of `origin/dev` returned `Already up to date.` Core migration 7 and Mail migration 10 remain free on that remote branch. Final per-crate gates are queued behind the current server build. The earlier server check had no production SPA directory; the prerequisite is now built. One prerequisite compilation was interrupted (exit 130); it is retained in the local log and resumed, not counted as a passed gate. Interfaces for #669–#676/#701: `Db::begin_mutation` -> Replay/New transaction, `MutationTransaction::connection/take_inverse/commit`, `Db::mutation_receipt`, and `mutationController` with synchronous publish/confirm/reject plus send/lookup. The adoption guide is `docs/mutations/receipts.md`. #665/#666/#668 caches and change streams are not reimplemented. A generic SQLite receipt cannot atomically commit a file rename: Files/Calendar/Notes adapters must reuse their recovery journals and calternal-fs; provider adapters enqueue their durable jobs inside the same transaction.
Author
Owner

Head 429bd6193, branch job/perf-mut-667.

Receipt history must preserve an acknowledged outcome even after the Undo window expires. Lookup and same-ID replay return that outcome; only inverse application returns 410 after 24 hours. Deleting an expired ID would permit duplicate work after a lost acknowledgement.

The initial 10,000-receipt cap is removed. It would block all later edits by the User and require a COUNT on each intent. The Index now grows with accepted intents. Input, result and inverse remain limited to 64 KiB each. An archival policy is a follow-up; it must keep replay protection. The new bench profile tests 100,000 retained receipt rows and a five-request duplicate-ID burst.

Database gates: 10 unit tests, 3 receipt tests, 16 queue tests pass (1 existing ignored). Mail gates: 47 tests pass (2 existing ignored). Clippy passes for both crates. The four-route real local User/scope matrix, session revocation, duplicate/reused IDs, Undo replay and intervening legacy edits pass. Production pointer/touch/keyboard walk, lost acknowledgement recovery, conflict Retry and Undo after reload pass. Twelve screenshots are attached: 390/820/1440 px, light/dark, Settings and Mail reader. Visual review remains with the orchestrator.

The final loading review found state and restored-Undo reads without a deadline. Both now use a 15-second deadline so a stalled request releases loading and permits Retry. A regression test covers timeout and retry. Web gates and final server tests are running; the production build will be measured under the perf VM lock after it completes.

Head 429bd6193, branch job/perf-mut-667. Receipt history must preserve an acknowledged outcome even after the Undo window expires. Lookup and same-ID replay return that outcome; only inverse application returns 410 after 24 hours. Deleting an expired ID would permit duplicate work after a lost acknowledgement. The initial 10,000-receipt cap is removed. It would block all later edits by the User and require a COUNT on each intent. The Index now grows with accepted intents. Input, result and inverse remain limited to 64 KiB each. An archival policy is a follow-up; it must keep replay protection. The new bench profile tests 100,000 retained receipt rows and a five-request duplicate-ID burst. Database gates: 10 unit tests, 3 receipt tests, 16 queue tests pass (1 existing ignored). Mail gates: 47 tests pass (2 existing ignored). Clippy passes for both crates. The four-route real local User/scope matrix, session revocation, duplicate/reused IDs, Undo replay and intervening legacy edits pass. Production pointer/touch/keyboard walk, lost acknowledgement recovery, conflict Retry and Undo after reload pass. Twelve screenshots are attached: 390/820/1440 px, light/dark, Settings and Mail reader. Visual review remains with the orchestrator. The final loading review found state and restored-Undo reads without a deadline. Both now use a 15-second deadline so a stalled request releases loading and permits Retry. A regression test covers timeout and retry. Web gates and final server tests are running; the production build will be measured under the perf VM lock after it completes.
Author
Owner

Final review findings, branch job/perf-mut-667, head 8582b76ad.

The first release binary was built while the 10,000-row quota was being removed. A local profile seeded 100,000 receipt rows and received 500 on the next action. That binary still contains SELECT count(*) FROM mutation_receipts WHERE owner_id=?. This obsolete-binary run is retained in bench-local-profile.json and is not a successful budget sample. The source quota removal is 429bd6193. Regression test 8582b76ad seeds 10,001 old receipts, accepts a fresh ID, and verifies its receipt. Database gates pass, including that fourth receipt test. The current release is being rebuilt before VM measurement.

The retained Mail reader also had an incomplete pending guard: a tentative preference could start an automatic provider read timer. The shared model now allows this timer only after confirmation. Pending or unknown state cancels the timer; confirmation reschedules it. The new test exercises initial loading, confirmed state, lost acknowledgement and receipt reconciliation. This does not delay bodies or adopt the provider Seen mutation; that adapter remains a follow-up in #672. Web gates are running for this final correction.

Final review findings, branch job/perf-mut-667, head 8582b76ad. The first release binary was built while the 10,000-row quota was being removed. A local profile seeded 100,000 receipt rows and received 500 on the next action. That binary still contains `SELECT count(*) FROM mutation_receipts WHERE owner_id=?`. This obsolete-binary run is retained in bench-local-profile.json and is not a successful budget sample. The source quota removal is 429bd6193. Regression test 8582b76ad seeds 10,001 old receipts, accepts a fresh ID, and verifies its receipt. Database gates pass, including that fourth receipt test. The current release is being rebuilt before VM measurement. The retained Mail reader also had an incomplete pending guard: a tentative preference could start an automatic provider read timer. The shared model now allows this timer only after confirmation. Pending or unknown state cancels the timer; confirmation reschedules it. The new test exercises initial loading, confirmed state, lost acknowledgement and receipt reconciliation. This does not delay bodies or adopt the provider Seen mutation; that adapter remains a follow-up in #672. Web gates are running for this final correction.
Author
Owner

Code and gates are complete. Current head: 2f2dee97e9. Last product change: 3c313d4ed7.

Final release records GIT_COMMIT=3c313d4ed705fe5c00b09e6508ef9d12f39220dd. Binary SHA-256: af05232a711fdce969b8f28b3d4985e716cdf3fb8a7b8ce02fe875d70e899d48. The release openapi subcommand rewrote the contract with no git diff. The SPA walk passed again and 12 current production SPA captures are attached.

The VM profile queued at 09:30 UTC behind flock -w 14400 /root/perf.lock. It has not acquired the lock or run any VM samples yet. No measurement is made outside the lock. Chromium and the server will both run on the VM; the HTTPS front is forwarded from the build host. The profile keeps five forward and five Undo samples each for warm state, a fresh server process and 100,000 retained receipts, then a five-request duplicate-ID burst. It checks /api/v1/system/info against the selected source and records CPU/RSS, both timing boundaries, load inside the lock, HDD qualification and all raw samples. There is no matching accepted/durable mutation baseline.

All current gates pass: Db 10 unit + 4 receipt + 16 queue tests (1 existing ignored); Mail 47 (2 existing ignored); server 107 (3 existing ignored); Clippy clean for all three crates; fmt clean; web check 0 errors/0 warnings, 155 files and 1,065 tests pass. The final report will quote the exact output. Measurement and cleanup remain pending.

Code and gates are complete. Current head: 2f2dee97e9c632890d3b43ffad745a790e28dde4. Last product change: 3c313d4ed705fe5c00b09e6508ef9d12f39220dd. Final release records GIT_COMMIT=3c313d4ed705fe5c00b09e6508ef9d12f39220dd. Binary SHA-256: af05232a711fdce969b8f28b3d4985e716cdf3fb8a7b8ce02fe875d70e899d48. The release `openapi` subcommand rewrote the contract with no git diff. The SPA walk passed again and 12 current production SPA captures are attached. The VM profile queued at 09:30 UTC behind `flock -w 14400 /root/perf.lock`. It has not acquired the lock or run any VM samples yet. No measurement is made outside the lock. Chromium and the server will both run on the VM; the HTTPS front is forwarded from the build host. The profile keeps five forward and five Undo samples each for warm state, a fresh server process and 100,000 retained receipts, then a five-request duplicate-ID burst. It checks `/api/v1/system/info` against the selected source and records CPU/RSS, both timing boundaries, load inside the lock, HDD qualification and all raw samples. There is no matching accepted/durable mutation baseline. All current gates pass: Db 10 unit + 4 receipt + 16 queue tests (1 existing ignored); Mail 47 (2 existing ignored); server 107 (3 existing ignored); Clippy clean for all three crates; fmt clean; web check 0 errors/0 warnings, 155 files and 1,065 tests pass. The final report will quote the exact output. Measurement and cleanup remain pending.
Author
Owner

#667 completion report

Branch: job/perf-mut-667. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Head: 52d2b17f80.

Built

One shared receipt transaction in calternal-db. It stores the domain write, inverse and committed result in one writer transaction. Operation IDs are scoped by User across operation kinds. Same-ID retries return the committed result; changed input conflicts. Undo uses a fresh ID and the server inverse. An intervening revision conflicts and rolls back the Undo mark. Restart, duplicate/racing IDs, expired inverse windows and dropped transactions have regression tests.

One shared optimistic client controller. One synchronous adapter callback publishes the item, selection and next item. It persists pending intent IDs through userStorage before network I/O. Lost acknowledgement remains unknown. Reconciliation looks up the receipt before a same-ID replay. Only a definite write rejection permits rollback. Session changes fence late responses.

The proving adapter is Mail read marking, shared by Settings and retained readers. Four routes add revision-bearing state, conditional mutation, receipt lookup and inverse Undo. Legacy GET/PATCH stay unchanged; legacy PATCH advances the revision. Pending preferences cannot start the automatic provider read timer. Bodies do not await preference reconciliation. Existing provider Seen writes remain in #672.

Generated the API client, OpenAPI contract, action registry and parity matrix. Added the real-server regression probe, cross-User route fixtures, production UI walk and bench/mutation-receipts.mjs. Imported the parent DESIGN §58 commits as requested. No other cache or stream primitive is implemented.

Files

  • CLAUDE.md
  • apps/web/e2e/mutation-receipts-667.mjs
  • apps/web/src/lib/mail/MailView.svelte
  • apps/web/src/lib/mail/preferences.svelte.test.ts
  • apps/web/src/lib/mail/preferences.svelte.ts
  • apps/web/src/lib/mutations.test.ts
  • apps/web/src/lib/mutations.ts
  • apps/web/src/routes/settings/mail/MailSection.svelte
  • bench/mutation-receipts.mjs
  • contracts/actions.json
  • contracts/openapi.json
  • crates/calternal-db/src/lib.rs
  • crates/calternal-db/src/migrations.rs
  • crates/calternal-db/src/migrations/0007_mutation_receipts.sql
  • crates/calternal-db/src/mutations.rs
  • crates/calternal-db/tests/mutations.rs
  • crates/plugins/mail/migrations/0010_preference_revision.sql
  • crates/plugins/mail/src/cache.rs
  • crates/plugins/mail/src/cache/store.rs
  • crates/plugins/mail/src/routes.rs
  • docs/DESIGN.md
  • docs/mutations/receipts.md
  • docs/parity-matrix.md
  • docs/perf/2026-10-02-mutations-667.md
  • docs/perf/runs/2026-10-02-mutations-667-bench-local-profile.json
  • docs/perf/runs/2026-10-02-mutations-667-bench-local-validation.json
  • docs/perf/runs/2026-10-02-mutations-667-bench-vm-browser-missing.json
  • docs/perf/runs/2026-10-02-mutations-667-bench-vm-diagnostic-failed.json
  • docs/perf/runs/2026-10-02-mutations-667-bench-vm-startup-failed.json
  • docs/perf/runs/2026-10-02-mutations-667.json
  • packages/api-client/src/generated.ts
  • tests/adversarial/mutation_receipts.mjs
  • tests/adversarial/xuser_matrix.py

UX gaps closed

  • Preference choices update in the input frame through one shared model.
  • Undo uses the server inverse and works after a document/server restart.
  • Lost acknowledgement keeps the choice and offers Check again, including reconnect recovery.
  • Definite conflicts show Reload and recover the authoritative choice.
  • State and restored-Undo reads have a 15-second deadline. Loading releases on timeout and Retry works.
  • Pending/unknown preferences cancel automatic provider read timers. Confirmation reschedules the timer.
  • Pointer, touch and keyboard use the existing shared controls and Undo toast. Settings retains its Copy link action. Real empty Mail remains a real API empty state.

UX gaps left / known gaps

Other Tabs adopt this primitive in #669–#676 and #701. Provider Seen receipts remain in #672. File adapters must use their existing recovery journals; a SQLite receipt alone cannot make a filesystem rename atomic with commit. Visual approval remains with the orchestrator; 12 current production SPA captures are attached below.

Receipt history grows with accepted intents. A later archive policy must preserve ID replay protection. Browser storage can be disabled; the server receipt remains, but a document restart cannot recover an intent reference that the browser did not retain. The existing SQLite WAL/NORMAL policy gives process-restart durability; this job does not add a power-loss guarantee.

Decisions

DESIGN §58 does not set an inverse window, byte limit or adapter transport shape. This job uses a 24-hour inverse window, 64 KiB each for input/result/inverse, and at most 100 pending client intents / 64 KiB client state. Lookup and replay remain available after the inverse window. There is no lifetime action quota. Mail accepts one pending preference at a time to avoid inverse reordering. New conditional routes preserve the existing GET/PATCH contract. The read-marking preference is the one proving adapter; provider and filesystem adoption remains with the follow-up owners.

Gates (verbatim output)

cargo fmt --check returned 0 with no output. All cargo commands used line-tables-only, incremental=0 and build jobs=4. Rust gates ran per crate. Web tests used two workers.

cargo clippy -p calternal-db --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.00s
cargo test -p calternal-db
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.32s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s
test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.82s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 56.20s
cargo test -p calternal-plugin-mail
test result: ok. 47 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.31s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
cargo clippy -p calternal-server --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 01s
cargo test -p calternal-server
test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 16.43s
bun run check
svelte-check found 0 errors and 0 warnings
bun run test --maxWorkers=2
 Test Files  155 passed (155)
      Tests  1065 passed (1065)
   Duration  146.50s (transform 26%, environment 26%, import 25%, tests 17%, setup 5%)

The one-time git fetch origin / git merge origin/dev returned:

Already up to date.

Core migration 7 and Mail migration 10 were free on fetched origin/dev before the final gates.

Real local server round:

PASS: legacy contract, duplicate/reused IDs, Undo replay, intervening edit, four-route User/scope matrix
PASS: session revocation blocks receipt lookup
PASS: pointer/touch/keyboard, lost acknowledgement, conflict Retry, Undo after reload; 12 width/theme captures

Offline cross-User classification: 339 API operations and 957 generated tools classified; 7 classification tests pass. Admin coverage: 39 reviewed operations agree with Rust guards. The new routes cover anonymous, another User, protocol-scoped, read-only API-scoped and revoked sessions. Existing test expectations are unchanged.

Performance

Locked production/HDD run: 30 valid samples, five forward and five Undo per phase. Both Chromium 153.0.8010.12 and the server ran on the VM; the HTTPS front used SSH forwarding. Production source 3c313d4ed705fe5c00b09e6508ef9d12f39220dd, binary SHA-256 af05232a711fdce969b8f28b3d4985e716cdf3fb8a7b8ce02fe875d70e899d48. Profile source 3b5dd40abf89d133b04fc78ebfbfd2b260c2c9f3, profile SHA-256 ff1cab97f2226792a8323f6ac77f057f1f5a91d31942db9efada095c9d65f735. The server source endpoint matched the selected revision.

Phase / action Accepted median / p95 / max, ms Durable median / p95 / max, ms
average / forward 3.6 / 120.6 / 120.6 83.8 / 126.8 / 126.8
average / undo 6.4 / 81.7 / 81.7 91.8 / 523.2 / 523.2
cold-process / forward 105.3 / 114.0 / 114.0 83.1 / 85.4 / 85.4
cold-process / undo 61.9 / 175.5 / 175.5 80.7 / 88.6 / 88.6
large-history / forward 3.0 / 3.6 / 3.6 79.7 / 329.9 / 329.9
large-history / undo 9.2 / 13.3 / 13.3 78.9 / 83.2 / 83.2

Accepted is click to the next frame with checked DOM state. Durable ends when the client reads the receipt. Cold means a fresh server process before each input; OS caches remain. Large history starts at 100,020 receipts, including 100,000 fixture rows. All slow samples remain. Warm and large-history accepted states meet 150 ms. One cold Undo sample is 175.5 ms, tracked in #713; it does not block the merge.

The full warm interval used 70 ms server CPU (0.30% of one CPU) and 11780 ms browser CPU over 23.36 seconds. Warm sampled peak server RSS is 191.8 MiB; browser process RSS sum is 2009.4 MiB. Sampling intervals include automation/SSH waits and browser work; these are not per-handler costs. RSS can double-count shared pages. CPU counters have 10 ms resolution.

The five-request duplicate-ID burst returned identical committed results: 1085.5 ms wall, 0 ms server CPU tick delta, 20 ms browser CPU, 138.9 MiB server RSS and 1939.5 MiB browser RSS sum. Zero tick delta does not mean zero work.

HDD qualification passed: QD1 124.9 IOPS / 8.028 ms median; QD16 200.9 IOPS / 100.139 ms median. Load inside the lock at cold phase: 2.13 1.95 1.83 1/444 64887. Full phase load and raw samples are committed in docs/perf/2026-10-02-mutations-667.md and docs/perf/runs/2026-10-02-mutations-667.json.

There is no matching accepted/durable mutation baseline. docs/perf/baseline.json source 369ab6a2f9fc673e3564b94857fbecfeb04df404 has Mail accounts read p50 1.3 ms / p95 3.8 ms; that is context only, not a comparable mutation baseline. No regression ratio is claimed.

Incomplete runs are committed beside the successful raw record and excluded from budgets: an earlier local harness check, an obsolete-binary local 500 at 100,000 receipts, two VM startup attempts before fixture-root creation, and one qualified HDD run that did not find the existing Chromium path. The lifetime quota is removed, the 10,001-row regression passes, and this run accepts intents at 100,020 rows. The profile now creates fixture roots, retains sanitized startup diagnostics and finds /opt/ms-playwright and headless-shell caches.

Review captures

Current production SPA 3c313d4ed705fe5c00b09e6508ef9d12f39220dd, real isolated API, phone 390 px, tablet 820 px and desktop 1440 px, light and dark. No screenshots or binary artifacts are committed.

Cleanup

Cargo clean completed (verbatim):

     Removed 24111 files, 11.0GiB total

Removed web build output, .svelte-kit, temporary test data, the media fixture and Python bytecode. Review screenshots remain in artifacts/. VM cleanup check: zero #667 fixture roots and zero #667 copied servers. No push or deploy. The only merge was the requested origin/dev update. The issue remains open.

# #667 completion report Branch: `job/perf-mut-667`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Head: 52d2b17f805072cd0304d7a05fe0534523cc7bc3. ## Built One shared receipt transaction in calternal-db. It stores the domain write, inverse and committed result in one writer transaction. Operation IDs are scoped by User across operation kinds. Same-ID retries return the committed result; changed input conflicts. Undo uses a fresh ID and the server inverse. An intervening revision conflicts and rolls back the Undo mark. Restart, duplicate/racing IDs, expired inverse windows and dropped transactions have regression tests. One shared optimistic client controller. One synchronous adapter callback publishes the item, selection and next item. It persists pending intent IDs through userStorage before network I/O. Lost acknowledgement remains unknown. Reconciliation looks up the receipt before a same-ID replay. Only a definite write rejection permits rollback. Session changes fence late responses. The proving adapter is Mail read marking, shared by Settings and retained readers. Four routes add revision-bearing state, conditional mutation, receipt lookup and inverse Undo. Legacy GET/PATCH stay unchanged; legacy PATCH advances the revision. Pending preferences cannot start the automatic provider read timer. Bodies do not await preference reconciliation. Existing provider Seen writes remain in #672. Generated the API client, OpenAPI contract, action registry and parity matrix. Added the real-server regression probe, cross-User route fixtures, production UI walk and bench/mutation-receipts.mjs. Imported the parent DESIGN §58 commits as requested. No other cache or stream primitive is implemented. ## Files - `CLAUDE.md` - `apps/web/e2e/mutation-receipts-667.mjs` - `apps/web/src/lib/mail/MailView.svelte` - `apps/web/src/lib/mail/preferences.svelte.test.ts` - `apps/web/src/lib/mail/preferences.svelte.ts` - `apps/web/src/lib/mutations.test.ts` - `apps/web/src/lib/mutations.ts` - `apps/web/src/routes/settings/mail/MailSection.svelte` - `bench/mutation-receipts.mjs` - `contracts/actions.json` - `contracts/openapi.json` - `crates/calternal-db/src/lib.rs` - `crates/calternal-db/src/migrations.rs` - `crates/calternal-db/src/migrations/0007_mutation_receipts.sql` - `crates/calternal-db/src/mutations.rs` - `crates/calternal-db/tests/mutations.rs` - `crates/plugins/mail/migrations/0010_preference_revision.sql` - `crates/plugins/mail/src/cache.rs` - `crates/plugins/mail/src/cache/store.rs` - `crates/plugins/mail/src/routes.rs` - `docs/DESIGN.md` - `docs/mutations/receipts.md` - `docs/parity-matrix.md` - `docs/perf/2026-10-02-mutations-667.md` - `docs/perf/runs/2026-10-02-mutations-667-bench-local-profile.json` - `docs/perf/runs/2026-10-02-mutations-667-bench-local-validation.json` - `docs/perf/runs/2026-10-02-mutations-667-bench-vm-browser-missing.json` - `docs/perf/runs/2026-10-02-mutations-667-bench-vm-diagnostic-failed.json` - `docs/perf/runs/2026-10-02-mutations-667-bench-vm-startup-failed.json` - `docs/perf/runs/2026-10-02-mutations-667.json` - `packages/api-client/src/generated.ts` - `tests/adversarial/mutation_receipts.mjs` - `tests/adversarial/xuser_matrix.py` ## UX gaps closed - Preference choices update in the input frame through one shared model. - Undo uses the server inverse and works after a document/server restart. - Lost acknowledgement keeps the choice and offers Check again, including reconnect recovery. - Definite conflicts show Reload and recover the authoritative choice. - State and restored-Undo reads have a 15-second deadline. Loading releases on timeout and Retry works. - Pending/unknown preferences cancel automatic provider read timers. Confirmation reschedules the timer. - Pointer, touch and keyboard use the existing shared controls and Undo toast. Settings retains its Copy link action. Real empty Mail remains a real API empty state. ## UX gaps left / known gaps Other Tabs adopt this primitive in #669–#676 and #701. Provider Seen receipts remain in #672. File adapters must use their existing recovery journals; a SQLite receipt alone cannot make a filesystem rename atomic with commit. Visual approval remains with the orchestrator; 12 current production SPA captures are attached below. Receipt history grows with accepted intents. A later archive policy must preserve ID replay protection. Browser storage can be disabled; the server receipt remains, but a document restart cannot recover an intent reference that the browser did not retain. The existing SQLite WAL/NORMAL policy gives process-restart durability; this job does not add a power-loss guarantee. ## Decisions DESIGN §58 does not set an inverse window, byte limit or adapter transport shape. This job uses a 24-hour inverse window, 64 KiB each for input/result/inverse, and at most 100 pending client intents / 64 KiB client state. Lookup and replay remain available after the inverse window. There is no lifetime action quota. Mail accepts one pending preference at a time to avoid inverse reordering. New conditional routes preserve the existing GET/PATCH contract. The read-marking preference is the one proving adapter; provider and filesystem adoption remains with the follow-up owners. ## Gates (verbatim output) `cargo fmt --check` returned 0 with no output. All cargo commands used line-tables-only, incremental=0 and build jobs=4. Rust gates ran per crate. Web tests used two workers. ```text cargo clippy -p calternal-db --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.00s cargo test -p calternal-db test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.32s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.82s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 56.20s cargo test -p calternal-plugin-mail test result: ok. 47 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.31s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 01s cargo test -p calternal-server test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 16.43s bun run check svelte-check found 0 errors and 0 warnings bun run test --maxWorkers=2 Test Files 155 passed (155) Tests 1065 passed (1065) Duration 146.50s (transform 26%, environment 26%, import 25%, tests 17%, setup 5%) ``` The one-time `git fetch origin` / `git merge origin/dev` returned: ```text Already up to date. ``` Core migration 7 and Mail migration 10 were free on fetched origin/dev before the final gates. Real local server round: ```text PASS: legacy contract, duplicate/reused IDs, Undo replay, intervening edit, four-route User/scope matrix PASS: session revocation blocks receipt lookup PASS: pointer/touch/keyboard, lost acknowledgement, conflict Retry, Undo after reload; 12 width/theme captures ``` Offline cross-User classification: 339 API operations and 957 generated tools classified; 7 classification tests pass. Admin coverage: 39 reviewed operations agree with Rust guards. The new routes cover anonymous, another User, protocol-scoped, read-only API-scoped and revoked sessions. Existing test expectations are unchanged. ## Performance Locked production/HDD run: 30 valid samples, five forward and five Undo per phase. Both Chromium 153.0.8010.12 and the server ran on the VM; the HTTPS front used SSH forwarding. Production source `3c313d4ed705fe5c00b09e6508ef9d12f39220dd`, binary SHA-256 `af05232a711fdce969b8f28b3d4985e716cdf3fb8a7b8ce02fe875d70e899d48`. Profile source `3b5dd40abf89d133b04fc78ebfbfd2b260c2c9f3`, profile SHA-256 `ff1cab97f2226792a8323f6ac77f057f1f5a91d31942db9efada095c9d65f735`. The server source endpoint matched the selected revision. | Phase / action | Accepted median / p95 / max, ms | Durable median / p95 / max, ms | | --- | --- | --- | | average / forward | 3.6 / 120.6 / 120.6 | 83.8 / 126.8 / 126.8 | | average / undo | 6.4 / 81.7 / 81.7 | 91.8 / 523.2 / 523.2 | | cold-process / forward | 105.3 / 114.0 / 114.0 | 83.1 / 85.4 / 85.4 | | cold-process / undo | 61.9 / 175.5 / 175.5 | 80.7 / 88.6 / 88.6 | | large-history / forward | 3.0 / 3.6 / 3.6 | 79.7 / 329.9 / 329.9 | | large-history / undo | 9.2 / 13.3 / 13.3 | 78.9 / 83.2 / 83.2 | Accepted is click to the next frame with checked DOM state. Durable ends when the client reads the receipt. Cold means a fresh server process before each input; OS caches remain. Large history starts at 100,020 receipts, including 100,000 fixture rows. All slow samples remain. Warm and large-history accepted states meet 150 ms. One cold Undo sample is 175.5 ms, tracked in [#713](https://git.kayg.org/kayg/calternal/issues/713); it does not block the merge. The full warm interval used 70 ms server CPU (0.30% of one CPU) and 11780 ms browser CPU over 23.36 seconds. Warm sampled peak server RSS is 191.8 MiB; browser process RSS sum is 2009.4 MiB. Sampling intervals include automation/SSH waits and browser work; these are not per-handler costs. RSS can double-count shared pages. CPU counters have 10 ms resolution. The five-request duplicate-ID burst returned identical committed results: 1085.5 ms wall, 0 ms server CPU tick delta, 20 ms browser CPU, 138.9 MiB server RSS and 1939.5 MiB browser RSS sum. Zero tick delta does not mean zero work. HDD qualification passed: QD1 124.9 IOPS / 8.028 ms median; QD16 200.9 IOPS / 100.139 ms median. Load inside the lock at cold phase: `2.13 1.95 1.83 1/444 64887`. Full phase load and raw samples are committed in `docs/perf/2026-10-02-mutations-667.md` and `docs/perf/runs/2026-10-02-mutations-667.json`. There is no matching accepted/durable mutation baseline. `docs/perf/baseline.json` source `369ab6a2f9fc673e3564b94857fbecfeb04df404` has Mail accounts read p50 1.3 ms / p95 3.8 ms; that is context only, not a comparable mutation baseline. No regression ratio is claimed. Incomplete runs are committed beside the successful raw record and excluded from budgets: an earlier local harness check, an obsolete-binary local 500 at 100,000 receipts, two VM startup attempts before fixture-root creation, and one qualified HDD run that did not find the existing Chromium path. The lifetime quota is removed, the 10,001-row regression passes, and this run accepts intents at 100,020 rows. The profile now creates fixture roots, retains sanitized startup diagnostics and finds `/opt/ms-playwright` and headless-shell caches. ## Review captures Current production SPA `3c313d4ed705fe5c00b09e6508ef9d12f39220dd`, real isolated API, phone 390 px, tablet 820 px and desktop 1440 px, light and dark. No screenshots or binary artifacts are committed. - [mail-preferences-1440-dark.png](https://git.kayg.org/attachments/20ed8dcc-9fc7-4423-8668-576cabf74449) - [mail-preferences-1440-light.png](https://git.kayg.org/attachments/a3acbb74-f12f-46ed-8a53-1e13b74b173d) - [mail-preferences-390-dark.png](https://git.kayg.org/attachments/d48793d6-c321-456d-ac0e-fc0a778aae1d) - [mail-preferences-390-light.png](https://git.kayg.org/attachments/7f6315ca-d889-4dbf-ae41-3f718b5b26e6) - [mail-preferences-820-dark.png](https://git.kayg.org/attachments/a604614c-189a-41d3-950b-cf20c1ad1f92) - [mail-preferences-820-light.png](https://git.kayg.org/attachments/4be06d6a-4164-4c6c-9d3f-e049896f22fc) - [mail-reader-1440-dark.png](https://git.kayg.org/attachments/4868a903-d693-4a3f-9fa1-01911e47ddc3) - [mail-reader-1440-light.png](https://git.kayg.org/attachments/e82acae7-762b-4142-ba0f-65183d7a0020) - [mail-reader-390-dark.png](https://git.kayg.org/attachments/e22ae77b-3931-4e67-ab11-4095e9935980) - [mail-reader-390-light.png](https://git.kayg.org/attachments/0bc5471b-0809-42e7-bf1e-0100d85f1511) - [mail-reader-820-dark.png](https://git.kayg.org/attachments/2ffd8b6f-fa4b-4452-9267-101f90102331) - [mail-reader-820-light.png](https://git.kayg.org/attachments/be6667e0-abf7-4c7e-9058-66d32708ca28) ## Cleanup Cargo clean completed (verbatim): ```text Removed 24111 files, 11.0GiB total ``` Removed web build output, .svelte-kit, temporary test data, the media fixture and Python bytecode. Review screenshots remain in artifacts/. VM cleanup check: zero #667 fixture roots and zero #667 copied servers. No push or deploy. The only merge was the requested origin/dev update. The issue remains open.
Author
Owner

#722 follow-up: I merged job/perf-mut-667 at 52d2b17f8 and added `optimisticUndo()` in apps/web/src/lib/mutations.ts. It publishes each cached inverse synchronously and reuses `isDefiniteRejection()` from #667 so definite 4xx rejections roll back while unknown outcomes stay optimistic. Photos, Files and Calendar do not expose the receipt/write/lookup endpoints needed to use `mutationController()` and durable receipts; these adapters still call their existing inverse APIs. I recorded that as a gap on #722. The same-frame and peer-tab E2E assertions are added but could not be run because the local server did not emit a setup token before the harness timeout.

#722 follow-up: I merged job/perf-mut-667 at 52d2b17f8 and added \`optimisticUndo()\` in apps/web/src/lib/mutations.ts. It publishes each cached inverse synchronously and reuses \`isDefiniteRejection()\` from #667 so definite 4xx rejections roll back while unknown outcomes stay optimistic. Photos, Files and Calendar do not expose the receipt/write/lookup endpoints needed to use \`mutationController()\` and durable receipts; these adapters still call their existing inverse APIs. I recorded that as a gap on #722. The same-frame and peer-tab E2E assertions are added but could not be run because the local server did not emit a setup token before the harness timeout.
Author
Owner

Conflict-audit evidence for the merge round:

  • origin/job/perf-mut-667 adds crates/calternal-db/src/migrations/0007_mutation_receipts.sql and registers Migration::new(7, ...) in crates/calternal-db/src/migrations.rs (built_in_migrations()).
  • origin/job/deployfix-732 adds core migrations 0007_integrations.sql through 0012_integration_endpoint_identity.sql and registers versions 7–12 in the same function.
  • git merge-tree --write-tree origin/job/perf-mut-667 origin/job/deployfix-732 reports a conflict in crates/calternal-db/src/migrations.rs.

The shared migration rule requires unique numbers per plugin. If integrations 0007–0012 land first, renumber the mutation-receipts migration and filename to 0013 before the merge, then verify fresh and upgraded Index migration tests.

Conflict-audit evidence for the merge round: - `origin/job/perf-mut-667` adds `crates/calternal-db/src/migrations/0007_mutation_receipts.sql` and registers `Migration::new(7, ...)` in `crates/calternal-db/src/migrations.rs` (`built_in_migrations()`). - `origin/job/deployfix-732` adds core migrations `0007_integrations.sql` through `0012_integration_endpoint_identity.sql` and registers versions 7–12 in the same function. - `git merge-tree --write-tree origin/job/perf-mut-667 origin/job/deployfix-732` reports a conflict in `crates/calternal-db/src/migrations.rs`. The shared migration rule requires unique numbers per plugin. If integrations 0007–0012 land first, renumber the mutation-receipts migration and filename to 0013 before the merge, then verify fresh and upgraded Index migration tests.
Author
Owner

P2 — Task actions wait for the server and use unguarded Undo closures

Evidence: apps/web/src/lib/notes/TaskHeader.svelte:69 waits for the tick
response before it updates currentTask at line 70. TaskSection also waits at
lines 315–316. Undo at TaskHeader line 90 sends the old status without a
revision or receipt. A later status edit from another Installation can be
overwritten by that Undo. The new helper in apps/web/src/lib/tasks/writes.ts
does not supply an operation ID or a receipt.

Fix: adopt the shared optimistic mutation and durable inverse contract owned
by #667. Publish accepted state in one frame. On Undo, check the current
revision and preserve intervening edits.

Test idea: hold the tick response and assert that the checkbox updates in the
same frame. Change the Task from another Installation before Undo, then assert
that Undo reports a conflict and preserves that change.

Rule: the interactive-path rules cited as DESIGN §58 in #667, rule 4 in #663.
Section 58 is absent from the reviewed and fetched DESIGN files. This review
uses the owner rules recorded on #663 and #667 and makes no latency claim.

## P2 — Task actions wait for the server and use unguarded Undo closures Evidence: `apps/web/src/lib/notes/TaskHeader.svelte:69` waits for the tick response before it updates `currentTask` at line 70. TaskSection also waits at lines 315–316. Undo at TaskHeader line 90 sends the old status without a revision or receipt. A later status edit from another Installation can be overwritten by that Undo. The new helper in `apps/web/src/lib/tasks/writes.ts` does not supply an operation ID or a receipt. Fix: adopt the shared optimistic mutation and durable inverse contract owned by #667. Publish accepted state in one frame. On Undo, check the current revision and preserve intervening edits. Test idea: hold the tick response and assert that the checkbox updates in the same frame. Change the Task from another Installation before Undo, then assert that Undo reports a conflict and preserves that change. Rule: the interactive-path rules cited as DESIGN §58 in #667, rule 4 in #663. Section 58 is absent from the reviewed and fetched DESIGN files. This review uses the owner rules recorded on #663 and #667 and makes no latency claim.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#667
No description provided.