Log attachments: deleted file still shown, attachments not live, batch send not live, Log order, raw camera names #427

Open
opened 2026-09-29 11:06:03 +00:00 by kayg · 238 comments
Owner

Owner report (2026-09-29, calternal.cloud, with a screenshot of the Calendar preview card for "Gym")

  1. "Even though I deleted this file, it still exists?" The card shows a photo attachment 7F56BB27-910A-4F79-B160-17CCBEED73D1_1_102_o.jpeg. Server evidence: the daily note Notes/20260929-dailynote.md still has the child line - [7F56BB27-…_1_102_o.jpeg](<Photos/2026/2026-09-29/2026-09-29 163314-3e69 7F56BB27-…_1_102_o.jpeg>) under the Gym entry, but the file no longer exists anywhere in the Home (it was deleted by the owner).
  2. "The attachments did not appear until I refreshed." After sending from the composer, the Log entry appeared live, but its attachments only appeared after a reload.
  3. "The two other events 'Auto to the gym' and 'Auto home' were lost, even though I clicked 'Send all 3' in the composer." Server evidence: they were NOT lost. All three lines are in the daily note (saved 11:03:24–26Z, four versions). The UI did not show two of the three until a refresh. The file also stores them out of time order: 15:15 Gym, then 16:00 Auto home, then 15:05 Auto to the gym.
  4. The attachment chip shows the raw camera file name ("7F56BB27-910A-4F79-B1…"), and its hover tooltip overlaps the next chip ("Gym" note link) so both are unreadable.

Expected

  • Batch send ("Send all N") shows every created entry live at once: in the Calendar, the day's Log and any other open view, through the same change feed as single sends. Write an e2e: send 3 entries in one batch, and assert that all 3 render without reload, in order.
  • The Log section stays in chronological order. A new line is inserted at its time position, not appended. Keep outside edits untouched: never reorder lines the User wrote out of order by hand. Only insert new lines in order. Document the rule in DESIGN (Log format) and CONTEXT if needed.
  • Attachments appear live with their entry (the same change event, or a follow-up event the card listens to).
  • Deleting an attached file:
    • When the User deletes it through calternal (Files, Photos, or the card), the attachment line is removed from the Log entry in the same operation, with Undo. Restore from Trash re-adds it: remember the link in the Trash metadata.
    • When it is deleted outside calternal (WebDAV, Finder, CLI), the card shows the attachment as Missing (dimmed, with a "Remove link" action) and never as a live file.
    • Moves and renames keep working through the stable calternal-id (DESIGN §33); a rename never breaks the link.
  • The attachment chip shows a thumbnail for images and a friendly label: the photo's taken time or place ("Photo · 16:33"), and for other files the file name without the camera UUID noise when a better title exists. The full name goes in the tooltip. Tooltips never overlap neighbouring chips: use the shared warm tooltip, positioned with collision handling.

Proof

Production-build e2e for each point (batch send live, ordering, live attachments, delete with undo and restore, outside delete shows Missing), plus screenshots of the card with a photo, a document and a Missing attachment at 390 and 1440 px, light and dark. The adversarial round: delete and restore races while the entry is being edited, and a thousand attachments on one entry. Gates per crate as in the preamble.

## Owner report (2026-09-29, calternal.cloud, with a screenshot of the Calendar preview card for "Gym") 1. "Even though I deleted this file, it still exists?" The card shows a photo attachment `7F56BB27-910A-4F79-B160-17CCBEED73D1_1_102_o.jpeg`. **Server evidence:** the daily note `Notes/20260929-dailynote.md` still has the child line `- [7F56BB27-…_1_102_o.jpeg](<Photos/2026/2026-09-29/2026-09-29 163314-3e69 7F56BB27-…_1_102_o.jpeg>)` under the Gym entry, but the file no longer exists anywhere in the Home (it was deleted by the owner). 2. "The attachments did not appear until I refreshed." After sending from the composer, the Log entry appeared live, but its attachments only appeared after a reload. 3. "The two other events 'Auto to the gym' and 'Auto home' were lost, even though I clicked 'Send all 3' in the composer." **Server evidence: they were NOT lost.** All three lines are in the daily note (saved 11:03:24–26Z, four versions). The UI did not show two of the three until a refresh. The file also stores them **out of time order**: 15:15 Gym, then 16:00 Auto home, then 15:05 Auto to the gym. 4. The attachment chip shows the raw camera file name ("7F56BB27-910A-4F79-B1…"), and its hover tooltip overlaps the next chip ("Gym" note link) so both are unreadable. ## Expected - **Batch send ("Send all N")** shows every created entry live at once: in the Calendar, the day's Log and any other open view, through the same change feed as single sends. Write an e2e: send 3 entries in one batch, and assert that all 3 render without reload, in order. - **The Log section stays in chronological order.** A new line is inserted at its time position, not appended. Keep outside edits untouched: never reorder lines the User wrote out of order by hand. Only insert new lines in order. Document the rule in DESIGN (Log format) and CONTEXT if needed. - **Attachments appear live** with their entry (the same change event, or a follow-up event the card listens to). - **Deleting an attached file:** - When the User deletes it through calternal (Files, Photos, or the card), the attachment line is removed from the Log entry in the same operation, with Undo. Restore from Trash re-adds it: remember the link in the Trash metadata. - When it is deleted outside calternal (WebDAV, Finder, CLI), the card shows the attachment as **Missing** (dimmed, with a "Remove link" action) and never as a live file. - Moves and renames keep working through the stable `calternal-id` (DESIGN §33); a rename never breaks the link. - **The attachment chip** shows a thumbnail for images and a friendly label: the photo's taken time or place ("Photo · 16:33"), and for other files the file name without the camera UUID noise when a better title exists. The full name goes in the tooltip. Tooltips never overlap neighbouring chips: use the shared warm tooltip, positioned with collision handling. ## Proof Production-build e2e for each point (batch send live, ordering, live attachments, delete with undo and restore, outside delete shows Missing), plus screenshots of the card with a photo, a document and a Missing attachment at 390 and 1440 px, light and dark. The adversarial round: delete and restore races while the entry is being edited, and a thousand attachments on one entry. Gates per crate as in the preamble.
Author
Owner

Starting attach-427 on branch job/attach-427, based at 191b179baa (dev). I have read CLAUDE.md, docs/DESIGN.md, and CONTEXT.md. I am tracing the log, attachment, delete/restore, and live-update paths before making atomic changes.

Starting attach-427 on branch job/attach-427, based at 191b179baac3ef4f5bebfe07ce91c4b7a887ace2 (dev). I have read CLAUDE.md, docs/DESIGN.md, and CONTEXT.md. I am tracing the log, attachment, delete/restore, and live-update paths before making atomic changes.
Author
Owner

Finding: the API create path calls append_log_entry for every composer and CalDAV create, and that helper always inserts after the last parsed line. The Calendar composerSaved handler also constructs each optimistic CalendarLog with attachments: [], so a newly sent entry is first rendered without its attachment data. I am fixing chronological insertion in calternal-notes-core while keeping existing line order unchanged, then I will address the live attachment projection.

Finding: the API create path calls append_log_entry for every composer and CalDAV create, and that helper always inserts after the last parsed line. The Calendar composerSaved handler also constructs each optimistic CalendarLog with attachments: [], so a newly sent entry is first rendered without its attachment data. I am fixing chronological insertion in calternal-notes-core while keeping existing line order unchanged, then I will address the live attachment projection.
Author
Owner

Finding: append_log_entry used to append every new Log row after the last row, even when the User had existing rows out of time order. It now inserts each new row before the first later start time and leaves existing rows in their original order. Regression cases cover a hand-reordered day, equal-time rows, CRLF, and the property writer's byte preservation. The Notes core suite passes after updating the old append-position assertions to identify the created row by block ID, which is the new insertion contract.

Finding: `append_log_entry` used to append every new Log row after the last row, even when the User had existing rows out of time order. It now inserts each new row before the first later start time and leaves existing rows in their original order. Regression cases cover a hand-reordered day, equal-time rows, CRLF, and the property writer's byte preservation. The Notes core suite passes after updating the old append-position assertions to identify the created row by block ID, which is the new insertion contract.
Author
Owner

The Log insertion rule and regression coverage are committed as 2b0d9f61 (Insert new Log rows at their time position). cargo fmt --check, cargo test -p calternal-notes-core (501 unit tests plus integration suites), and cargo clippy -p calternal-notes-core --all-targets -- -D warnings pass. The rule puts a new equal-time row after the last existing row with that time, and leaves every older row in place.

The Log insertion rule and regression coverage are committed as `2b0d9f61` (`Insert new Log rows at their time position`). `cargo fmt --check`, `cargo test -p calternal-notes-core` (501 unit tests plus integration suites), and `cargo clippy -p calternal-notes-core --all-targets -- -D warnings` pass. The rule puts a new equal-time row after the last existing row with that time, and leaves every older row in place.
Author
Owner

#427 finding: Files move/rename changed the Index path, but Log children still pointed at the old Markdown target; Calendar resolves attachment identity by that path, so a rename could appear as Missing. I added a Notes rewrite under the shared writer lock, with Files move recovery replaying the idempotent old-to-new update. A Files route regression now checks the updated child target and preserved item ID. Rust gates are in progress; I will report their output with the final head SHA.

#427 finding: Files move/rename changed the Index path, but Log children still pointed at the old Markdown target; Calendar resolves attachment identity by that path, so a rename could appear as Missing. I added a Notes rewrite under the shared writer lock, with Files move recovery replaying the idempotent old-to-new update. A Files route regression now checks the updated child target and preserved item ID. Rust gates are in progress; I will report their output with the final head SHA.
Author
Owner

Finding: the Home watcher calls Files adoption without an old fingerprint. That path rebuilt the Index row on an existing file and could replace its stable item ID after an outside change. I changed the no-fingerprint path to preserve an existing ID while still adopting new paths and removing deleted paths. The Files regression test now replaces the file, adopts it without a fingerprint, and asserts that its item ID stays the same. The Root change bus still uses its prior fingerprint to distinguish server writes.

Finding: the Home watcher calls Files adoption without an old fingerprint. That path rebuilt the Index row on an existing file and could replace its stable item ID after an outside change. I changed the no-fingerprint path to preserve an existing ID while still adopting new paths and removing deleted paths. The Files regression test now replaces the file, adopts it without a fingerprint, and asserts that its item ID stays the same. The Root change bus still uses its prior fingerprint to distinguish server writes.
Author
Owner

Related 5xx found while seeding fixtures: an attachment upload returned 500 after creating the files (new issue filed). attach-427 owns attachment behaviour, so fix it in this job if the root cause is in the attach path.

Related 5xx found while seeding fixtures: an attachment upload returned 500 after creating the files (new issue filed). attach-427 owns attachment behaviour, so fix it in this job if the root cause is in the attach path.
Author
Owner

Implemented and committed the #427 attachment lifecycle and live Log work. HEAD: 09c05b884d. Merged dev once before final checks; resolved the Calendar e2e overlap by retaining both suites' coverage.

Built:

  • Insert newly created Log rows chronologically while preserving the handwritten order of existing rows.
  • Publish attachment changes live; render friendly photo/file labels, full-name tooltips, Missing state and Remove link; bound the attachment preview to pages of 32.
  • Preserve file item identity through watcher adoption, remove child links on calternal Trash, and restore them by stable block ID on Undo/recovery. Rename/move updates linked Daily Note targets.
  • Add e2e coverage for three-item batch sends, multi-view live updates, attachment lifecycle, restore/edit race, outside delete, 1000 attachments, and responsive light/dark screenshot capture.

Files: apps/web/e2e/calendar.mjs, apps/web/e2e/composer.mjs, apps/web/src/lib/calendar/{attachments.test.ts,data.ts,journal.ts}, apps/web/src/lib/composer/{commit.test.ts,commit.ts}, apps/web/src/lib/tooltip/tooltip.test.ts, apps/web/src/routes/calendar/[view]/[date]/+page.svelte; crates/calternal-server/src/wire.rs, crates/plugins/calendar/src/view.rs, crates/plugins/files/src/{agent_undo.rs,index.rs,lib.rs}, crates/plugins/files/migrations/0016_log_attachment_trash.sql; packages/ui/src/components/calendar/{AttachmentDeck.svelte,ItemPreview.svelte,attachments.ts,model.ts}, packages/ui/src/components/tooltip/{TooltipLayer.svelte,place.ts}; docs/DESIGN.md; tests/adversarial/attack2.py.

Gate output:

cargo fmt --check
(no output; exit 0)
cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
Finished dev profile [unoptimized + debuginfo] target(s) in 51m 01s
cargo test -p calternal-plugin-notes
test result: ok. 112 passed; 0 failed
cargo clippy -p calternal-notes-core --all-targets -- -D warnings
Finished dev profile [unoptimized + debuginfo] target(s) in 10.10s
cargo test -p calternal-notes-core
504 lib tests, 13 log_rewrite tests, 5 parser_properties tests, 7 unicode_titles tests, 11 vector_parity tests: all passed; 0 failed
cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
Finished dev profile [unoptimized + debuginfo] target(s) in 45m 40s
bun run test
Test Files 125 passed (125)
Tests 801 passed (801)
node --check apps/web/e2e/calendar.mjs
(no output; exit 0)
git diff --check
(no output; exit 0)

Known gaps: cargo test -p calternal-plugin-files was stopped with exit 130 during dependency compilation, before tests ran. Calendar/server crate gates and workspace gates were not run. bun run check identified stale generated OpenAPI/API-client output for attachment fields and the Remove link route; code generation and a passing rerun remain. Production-build e2e, adversarial probes, and screenshots were not run, so no visual evidence is attached. No claim is made that those acceptance checks passed.

Decisions where DESIGN was silent: order only newly created entries by time and keep existing handwritten row order; use 32 attachments per preview page; restore Trash links by stable block ID while preserving edits; cap batched attachment metadata queries at 500 IDs. These choices are documented in code/DESIGN where applicable.

Implemented and committed the #427 attachment lifecycle and live Log work. HEAD: 09c05b884ddb5520ec9ccbd07420b2b628553214. Merged dev once before final checks; resolved the Calendar e2e overlap by retaining both suites' coverage. Built: - Insert newly created Log rows chronologically while preserving the handwritten order of existing rows. - Publish attachment changes live; render friendly photo/file labels, full-name tooltips, Missing state and Remove link; bound the attachment preview to pages of 32. - Preserve file item identity through watcher adoption, remove child links on calternal Trash, and restore them by stable block ID on Undo/recovery. Rename/move updates linked Daily Note targets. - Add e2e coverage for three-item batch sends, multi-view live updates, attachment lifecycle, restore/edit race, outside delete, 1000 attachments, and responsive light/dark screenshot capture. Files: apps/web/e2e/calendar.mjs, apps/web/e2e/composer.mjs, apps/web/src/lib/calendar/{attachments.test.ts,data.ts,journal.ts}, apps/web/src/lib/composer/{commit.test.ts,commit.ts}, apps/web/src/lib/tooltip/tooltip.test.ts, apps/web/src/routes/calendar/[view]/[date]/+page.svelte; crates/calternal-server/src/wire.rs, crates/plugins/calendar/src/view.rs, crates/plugins/files/src/{agent_undo.rs,index.rs,lib.rs}, crates/plugins/files/migrations/0016_log_attachment_trash.sql; packages/ui/src/components/calendar/{AttachmentDeck.svelte,ItemPreview.svelte,attachments.ts,model.ts}, packages/ui/src/components/tooltip/{TooltipLayer.svelte,place.ts}; docs/DESIGN.md; tests/adversarial/attack2.py. Gate output: ``` cargo fmt --check (no output; exit 0) cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished dev profile [unoptimized + debuginfo] target(s) in 51m 01s cargo test -p calternal-plugin-notes test result: ok. 112 passed; 0 failed cargo clippy -p calternal-notes-core --all-targets -- -D warnings Finished dev profile [unoptimized + debuginfo] target(s) in 10.10s cargo test -p calternal-notes-core 504 lib tests, 13 log_rewrite tests, 5 parser_properties tests, 7 unicode_titles tests, 11 vector_parity tests: all passed; 0 failed cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Finished dev profile [unoptimized + debuginfo] target(s) in 45m 40s bun run test Test Files 125 passed (125) Tests 801 passed (801) node --check apps/web/e2e/calendar.mjs (no output; exit 0) git diff --check (no output; exit 0) ``` Known gaps: `cargo test -p calternal-plugin-files` was stopped with exit 130 during dependency compilation, before tests ran. Calendar/server crate gates and workspace gates were not run. `bun run check` identified stale generated OpenAPI/API-client output for attachment fields and the Remove link route; code generation and a passing rerun remain. Production-build e2e, adversarial probes, and screenshots were not run, so no visual evidence is attached. No claim is made that those acceptance checks passed. Decisions where DESIGN was silent: order only newly created entries by time and keep existing handwritten row order; use 32 attachments per preview page; restore Trash links by stable block ID while preserving edits; cap batched attachment metadata queries at 500 IDs. These choices are documented in code/DESIGN where applicable.
Author
Owner

Resuming round 2 on branch job/attach-427, based on 43c1377c8f. I am tracing the Log attachment upload and liveness paths, then will run the requested production-build proof and gates.

Resuming round 2 on branch job/attach-427, based on 43c1377c8fd3b1b495841446989a64156f8ae1e4. I am tracing the Log attachment upload and liveness paths, then will run the requested production-build proof and gates.
Author
Owner

Finding during #427 verification: the Trash restore flow re-adds the child link, but the serialized target is [attached](<Attachments/attached.txt>). calternal-notes-core uses and tests this canonical angle-bracket form. The existing Files integration assertion expects [attached](Attachments/attached.txt) and fails once the stack overflow is avoided. I left that assertion unchanged under the owner rule; the Files suite therefore has a known failure pending orchestrator review of the stale expectation.

Finding during #427 verification: the Trash restore flow re-adds the child link, but the serialized target is `[attached](<Attachments/attached.txt>)`. `calternal-notes-core` uses and tests this canonical angle-bracket form. The existing Files integration assertion expects `[attached](Attachments/attached.txt)` and fails once the stack overflow is avoided. I left that assertion unchanged under the owner rule; the Files suite therefore has a known failure pending orchestrator review of the stale expectation.
Author
Owner

The first OpenAPI regeneration exposed a Calendar compile error in the attachment path: the call supplied the view timezone to load_log_details, but the function signature omitted it while photo labels used it. I added the missing parameter and documented the visibility/timezone invariant. I’m running the Calendar crate gate now.

The first OpenAPI regeneration exposed a Calendar compile error in the attachment path: the call supplied the view timezone to load_log_details, but the function signature omitted it while photo labels used it. I added the missing parameter and documented the visibility/timezone invariant. I’m running the Calendar crate gate now.
Author
Owner

Resuming round 2 on job/attach-427 at 7e8e9af43. The prior integration commit is 336f5e306; I will fetch and integrate current origin/dev once before final gates. I am continuing the interrupted Calendar timezone projection change, then finishing #460 atomic attach behavior, the e2e proof and required gates.

Resuming round 2 on `job/attach-427` at `7e8e9af43`. The prior integration commit is `336f5e306`; I will fetch and integrate current `origin/dev` once before final gates. I am continuing the interrupted Calendar timezone projection change, then finishing #460 atomic attach behavior, the e2e proof and required gates.
Author
Owner

Resuming round 2 on job/attach-427 at 7e8e9af43. The branch includes the origin/dev integration at 336f5e306; the latest committed change adds a concurrent attachment probe. I am finishing the timezone-aware attachment labels, then handling #460 retry atomicity/concurrency, generated API client checks, production e2e evidence, and the requested gates.

Resuming round 2 on `job/attach-427` at `7e8e9af43`. The branch includes the `origin/dev` integration at `336f5e306`; the latest committed change adds a concurrent attachment probe. I am finishing the timezone-aware attachment labels, then handling #460 retry atomicity/concurrency, generated API client checks, production e2e evidence, and the requested gates.
Author
Owner

Focused evidence while resuming #427: cargo test -p calternal-plugin-calendar compiled and ran 50 tests; 48 passed and 2 failed. Both failures are Calendar range requests that now resolve Log attachments. SQLite returned no such table: photos_media, and the route returned HTTP 500. Calendar must keep rendering when the optional Photos plugin has not created its Index table. I am checking plugin migration startup and will add a regression for the absent table.

Focused evidence while resuming #427: `cargo test -p calternal-plugin-calendar` compiled and ran 50 tests; 48 passed and 2 failed. Both failures are Calendar range requests that now resolve Log attachments. SQLite returned `no such table: photos_media`, and the route returned HTTP 500. Calendar must keep rendering when the optional Photos plugin has not created its Index table. I am checking plugin migration startup and will add a regression for the absent table.
Author
Owner

Calendar attachment lookup now passes against a test Index with the Photos migration installed, matching server startup (which applies the core plugin migration sets). cargo fmt --check passed, and cargo test -p calternal-plugin-calendar passed: 50 unit tests, 1 cache integration test, 3 protocol tests, 0 doc-test failures.

Calendar attachment lookup now passes against a test Index with the Photos migration installed, matching server startup (which applies the core plugin migration sets). `cargo fmt --check` passed, and `cargo test -p calternal-plugin-calendar` passed: 50 unit tests, 1 cache integration test, 3 protocol tests, 0 doc-test failures.
Author
Owner

Files gate evidence: cargo test -p calternal-plugin-files ran 133 tests; 129 passed, 3 failed, 1 was ignored. Two restore tests failed their existing exact link checks. restore_event_attachment emitted <...> destination wrappers for every link, including simple paths whose stored form was unwrapped ([attached](Attachments/attached.txt)). Trash metadata did not retain that Markdown style. I am preserving the wrapper bit in the saved link and restoring it; unsafe bare destinations still need wrappers. The third failure was the five-minute atomic-write/reconcile stress test timing out on this shared host; I classify it as SLOW-only.

Files gate evidence: `cargo test -p calternal-plugin-files` ran 133 tests; 129 passed, 3 failed, 1 was ignored. Two restore tests failed their existing exact link checks. `restore_event_attachment` emitted `<...>` destination wrappers for every link, including simple paths whose stored form was unwrapped (`[attached](Attachments/attached.txt)`). Trash metadata did not retain that Markdown style. I am preserving the wrapper bit in the saved link and restoring it; unsafe bare destinations still need wrappers. The third failure was the five-minute atomic-write/reconcile stress test timing out on this shared host; I classify it as SLOW-only.
Author
Owner

Starting resumed work on #427 and #460.

Branch: job/attach-427
Current HEAD: 7d856a5561
Branch base at start of this round: 43c1377c8f

I am reviewing the existing uncommitted Trash-link Markdown-style preservation change, then will finish atomic/idempotent attachment handling, generated API artifacts, adversarial coverage, and the requested production e2e evidence. I will merge origin/dev once before final gates as directed.

Starting resumed work on #427 and #460. Branch: job/attach-427 Current HEAD: 7d856a5561673df82c8db9d83576503ba79d072f Branch base at start of this round: 43c1377c8fd3b1b495841446989a64156f8ae1e4 I am reviewing the existing uncommitted Trash-link Markdown-style preservation change, then will finish atomic/idempotent attachment handling, generated API artifacts, adversarial coverage, and the requested production e2e evidence. I will merge origin/dev once before final gates as directed.
Author
Owner

During conflict resolution with origin/dev, the first cargo test -p calternal-notes-core run found that the merged batch splice omitted the Daily note prefix before the Log section. Five tests failed with the output missing ## Log or the day heading. The splice now copies the exact source prefix first; the full core crate passes 511 unit tests and all 37 integration tests.

During conflict resolution with origin/dev, the first `cargo test -p calternal-notes-core` run found that the merged batch splice omitted the Daily note prefix before the Log section. Five tests failed with the output missing `## Log` or the day heading. The splice now copies the exact source prefix first; the full core crate passes 511 unit tests and all 37 integration tests.
Author
Owner

After regenerating the contract, bun run check reported that the existing removeLogAttachment path was not an ApiPath. The Notes handler was registered and had a utoipa::path annotation, but remove_journal_attachment was missing from the Notes OpenApi path list. I added the handler to that list; contract regeneration and the clean web check are pending.

After regenerating the contract, `bun run check` reported that the existing `removeLogAttachment` path was not an `ApiPath`. The Notes handler was registered and had a `utoipa::path` annotation, but `remove_journal_attachment` was missing from the Notes `OpenApi` path list. I added the handler to that list; contract regeneration and the clean web check are pending.
Author
Owner

The first production Composer run passed the time-correction flow, then failed at the manual-order proof with ReferenceError: shiftDate is not defined in apps/web/e2e/composer.mjs. I replaced the undefined helper call with date arithmetic from the script's existing today value. The browser flow is being rerun.

The first production Composer run passed the time-correction flow, then failed at the manual-order proof with `ReferenceError: shiftDate is not defined` in `apps/web/e2e/composer.mjs`. I replaced the undefined helper call with date arithmetic from the script's existing `today` value. The browser flow is being rerun.
Author
Owner

The second production Composer run passed time correction and the previously failing date calculation, then stopped at the manual-order fixture with ReferenceError: findFile is not defined. findFile already exists in the shared e2e harness, so I imported it and removed the unused path variable. The Composer flow is being rerun.

The second production Composer run passed time correction and the previously failing date calculation, then stopped at the manual-order fixture with `ReferenceError: findFile is not defined`. `findFile` already exists in the shared e2e harness, so I imported it and removed the unused path variable. The Composer flow is being rerun.
Author
Owner

The third production Composer run passed the time-correction and manual-date steps, then the fixture failed with EACCES when the test process tried to overwrite a server-owned Daily note under userData. I changed the fixture to upload the complete hand-ordered Markdown through the real tus Files endpoint, then read it for the ordering assertion. This keeps the server as the only Home writer.

The third production Composer run passed the time-correction and manual-date steps, then the fixture failed with `EACCES` when the test process tried to overwrite a server-owned Daily note under `userData`. I changed the fixture to upload the complete hand-ordered Markdown through the real tus Files endpoint, then read it for the ordering assertion. This keeps the server as the only Home writer.
Author
Owner

The fourth Composer run passed the manual-order fixture, then failed in the held-send check because the journal response had no entries array. I updated the existing wait to include the response status and error body while keeping the same success condition. The next run will show whether the held send or journal read is failing.

The fourth Composer run passed the manual-order fixture, then failed in the held-send check because the journal response had no `entries` array. I updated the existing wait to include the response status and error body while keeping the same success condition. The next run will show whether the held send or journal read is failing.
Author
Owner

The fifth Composer run passed the attachment preview and manual-order fixture, then the held-send probe found GET /api/v1/notes/journal/<today> returning 404 Note not found after the batch was released. I added a response capture for that held batch so the next run asserts its status and returned date before polling the journal; this will distinguish a rejected write from a date mismatch.

The fifth Composer run passed the attachment preview and manual-order fixture, then the held-send probe found `GET /api/v1/notes/journal/<today>` returning 404 `Note not found` after the batch was released. I added a response capture for that held batch so the next run asserts its status and returned date before polling the journal; this will distinguish a rejected write from a date mismatch.
Author
Owner

The latest Composer run stopped at the existing chip geometry check: Composer chips use the shared 28–32 px visual size and readable text role. Earlier runs passed it, so I kept the size threshold unchanged and added measured height/font size to the failure output for the next production run.

The latest Composer run stopped at the existing chip geometry check: `Composer chips use the shared 28–32 px visual size and readable text role`. Earlier runs passed it, so I kept the size threshold unchanged and added measured height/font size to the failure output for the next production run.
Author
Owner

The first production Calendar run stopped at the pre-existing commit-freeze probe: assert.ok(sentBody, 'the send started') remained false after its 40 × 50 ms wait. No attachment flow ran. This is a 2-second UI/network timeout under the current shared-host load; I left the assertion unchanged and will retry the production run once.

The first production Calendar run stopped at the pre-existing commit-freeze probe: `assert.ok(sentBody, 'the send started')` remained false after its 40 × 50 ms wait. No attachment flow ran. This is a 2-second UI/network timeout under the current shared-host load; I left the assertion unchanged and will retry the production run once.
Author
Owner

Follow-up to the Calendar run note: I traced the repeated 2-second timeout to the test code, not host load. The test closed its blank Composer and typed Frozen snapshot without reopening it, so the intercepted send could not start. I added the missing await openComposer() before typing; the send assertion remains unchanged.

Follow-up to the Calendar run note: I traced the repeated 2-second timeout to the test code, not host load. The test closed its blank Composer and typed `Frozen snapshot` without reopening it, so the intercepted send could not start. I added the missing `await openComposer()` before typing; the send assertion remains unchanged.
Author
Owner

The Calendar commit-freeze test also intercepted the old /api/v1/notes/journal/log endpoint and read a top-level text field. Since #468, single sends use /api/v1/notes/journal/log/batch with entries[0].text, so the probe could never observe the request. I updated the interceptor, response capture, unroute and snapshot assertion to the one-entry batch contract; the existing success assertions remain.

The Calendar commit-freeze test also intercepted the old `/api/v1/notes/journal/log` endpoint and read a top-level `text` field. Since #468, single sends use `/api/v1/notes/journal/log/batch` with `entries[0].text`, so the probe could never observe the request. I updated the interceptor, response capture, unroute and snapshot assertion to the one-entry batch contract; the existing success assertions remain.
Author
Owner

Production e2e retry: CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/attach-427/debug/calternal-server CALENDAR_E2E_SHOTS=... bun e2e/calendar.mjs stopped in the existing media sandbox preflight before starting the server. The harness reported too many local threads for the bounded media sandbox test: 3629 (limit 3584). I did not bypass the guard. Attachment-card screenshots and the production e2e proof remain outstanding due to this host-level limit.

Production e2e retry: `CALTERNAL_SERVER_BIN=/mnt/hdd/targets/jobs/attach-427/debug/calternal-server CALENDAR_E2E_SHOTS=... bun e2e/calendar.mjs` stopped in the existing media sandbox preflight before starting the server. The harness reported `too many local threads for the bounded media sandbox test: 3629` (limit 3584). I did not bypass the guard. Attachment-card screenshots and the production e2e proof remain outstanding due to this host-level limit.
Author
Owner

Focused real-server adversarial result: the attachment checks passed (8/8 concurrent tus uploads returned 201; the Log attach returned 201; a concurrent Daily note edit returned 200; the final read retained all eight links and the edit). Requests were slow on the shared build host (upload times 24.9–48.2 s), and I recorded the separate Log/Files scan-storm 503s and timeouts in #493 for controlled-load follow-up.

Production calendar e2e remains blocked before server startup by the media-sandbox thread guard (too many local threads for the bounded media sandbox test: 3629; limit 3584). I did not bypass the guard, so requested attachment-card screenshots and visual proof remain outstanding.

Focused real-server adversarial result: the attachment checks passed (8/8 concurrent tus uploads returned 201; the Log attach returned 201; a concurrent Daily note edit returned 200; the final read retained all eight links and the edit). Requests were slow on the shared build host (upload times 24.9–48.2 s), and I recorded the separate Log/Files scan-storm 503s and timeouts in #493 for controlled-load follow-up. Production calendar e2e remains blocked before server startup by the media-sandbox thread guard (`too many local threads for the bounded media sandbox test: 3629`; limit 3584). I did not bypass the guard, so requested attachment-card screenshots and visual proof remain outstanding.
Author
Owner

Production E2E evidence: the API-backed Daily note fixture returned 404 because it used Notes/Journal/<date>-dailynote.md. calternal-notes-core::daily_note_path uses the flat Notes/<date>-dailynote.md path. I corrected the Files API read fixture and am rerunning the production Calendar flow.

Production E2E evidence: the API-backed Daily note fixture returned 404 because it used `Notes/Journal/<date>-dailynote.md`. `calternal-notes-core::daily_note_path` uses the flat `Notes/<date>-dailynote.md` path. I corrected the Files API read fixture and am rerunning the production Calendar flow.
Author
Owner

Focused production Calendar proof created the real attachments and reached the Trash/restore race, then failed because trashAndRestore did not await page.evaluate(). The assertion therefore read undefined instead of HTTP statuses. I added the missing await without changing expected statuses and am rerunning the proof.

Focused production Calendar proof created the real attachments and reached the Trash/restore race, then failed because `trashAndRestore` did not await `page.evaluate()`. The assertion therefore read `undefined` instead of HTTP statuses. I added the missing await without changing expected statuses and am rerunning the proof.
Author
Owner

After the preview-refresh fix, the focused proof reached the Missing state successfully. It then failed its light-theme check because the E2E called setTheme without reloading the route, leaving the hydrated app in its prior system palette. I changed the fixture to navigate back to the stable Log deep link after setting each theme; theme expectations are unchanged.

After the preview-refresh fix, the focused proof reached the Missing state successfully. It then failed its light-theme check because the E2E called `setTheme` without reloading the route, leaving the hydrated app in its prior system palette. I changed the fixture to navigate back to the stable Log deep link after setting each theme; theme expectations are unchanged.
Author
Owner

The focused proof captured the 390px light card correctly. Resizing the still-open selected preview to 820px left its previous anchor outside the viewport, so the chip tooltip hover could not run. The screenshot loop now reopens the preview by its stable Log block link after each viewport change; all responsive assertions remain enabled.

The focused proof captured the 390px light card correctly. Resizing the still-open selected preview to 820px left its previous anchor outside the viewport, so the chip tooltip hover could not run. The screenshot loop now reopens the preview by its stable Log block link after each viewport change; all responsive assertions remain enabled.
Author
Owner

Finished: #427 and #460

Head: 0182ec91f7f81ab2cfc63a3eeb69d38fb035e0ad

Delivered

  • Log batch sends now render all pending entries and reconcile them with durable server IDs. New lines enter by time; existing hand-ordered lines keep their positions.
  • Calendar attachment cards show image thumbnails and friendly labels such as Photo · 16:33, keep full names in collision-aware tooltips, and show outside-deleted files as Missing with Remove link.
  • Files Trash, Undo, Trash restore, rename and move preserve attachment links by stable identity. An open Log preview follows refreshed Files state by block ID.
  • Tus finalization is retry-safe through the stable upload ID. Repeated finalization reuses the installed file instead of creating a duplicate.
  • Regenerated contracts/openapi.json and packages/api-client/src/generated.ts; bash packages/api-client/check-generated.sh completed successfully.

Production proof

  • The batch-send production flow showed all three entries live and in chronological order.
  • Focused attachment proof passed: calendar attachment e2e: lifecycle and responsive proof passed. It covers live attachments, Trash Undo, restore from Trash, edit/restore race, outside deletion to Missing, Remove link, photo/document labels and tooltip collision checks.
  • The card was captured at 390, 820 and 1440 px in Paper and Tokyo Night. The six images are attached here:

Gates

Captured output:

cargo fmt --check
(no output; exit 0)

bun run check
svelte-check found 0 errors and 0 warnings

bun run test
Test Files  137 passed (137)
      Tests  889 passed (889)

cargo test -p calternal-plugin-files
 test result: FAILED. 131 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 331.72s

cargo test -p calternal-plugin-files --lib tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm -- --exact
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 132 filtered out; finished in 197.07s

cargo test -p calternal-notes-core
test result: FAILED. 510 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.03s

cargo test -p calternal-notes-core parse_and_classify_are_fast -- --exact
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 510 filtered out; finished in 0.72s

cargo test -p calternal-plugin-notes
test result: ok. 126 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 302.84s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s

cargo test -p calternal-plugin-calendar
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.17s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s

cargo test -p calternal-server
test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 57.97s

CALENDAR_E2E_ATTACHMENT_PROOF_ONLY=1 ... bun e2e/calendar.mjs
calendar attachment e2e: lifecycle and responsive proof passed

Clippy passed for calternal-plugin-files, calternal-plugin-calendar, calternal-server, calternal-plugin-notes and calternal-notes-core with -D warnings. The generated API check passed. The production build completed; it printed existing vendor use client directive warnings.

Known gaps and findings

  • The Files full suite had one timeout/stress failure (entry not found); the same test passed alone. Notes-core had one performance-threshold failure at 8.252 ms against an 8 ms limit; it passed alone. Both occurred under shared-host load.
  • The broader Calendar/Composer production scripts still hit unrelated existing assertions (composer draft freeze, the #53 repair fixture, and a held-send test that assumes today while the route is on yesterday). The focused attachment proof and the requested three-entry live-order flow passed. Existing expectations were left unchanged.
  • One concurrent server worker storm returned 503s/timeouts while the server stayed alive. Evidence is filed as #493; the attachment-specific concurrent probe passed.

Decisions

  • #460 uses the existing Tus upload ID as the retry identity and serializes finalization for that ID. This avoids adding another client idempotency token and makes a retry reuse the committed file.
  • A selected preview follows fresh range data by stable Log block ID, so Files feed changes update an open card without changing its selection.

Files

CONTEXT.md; docs/DESIGN.md; contracts/openapi.json; packages/api-client/src/generated.ts; apps/web/e2e/{calendar.mjs,composer.mjs,harness.mjs}; apps/web/src/lib/calendar/{attachments.test.ts,data.ts,journal.ts}; apps/web/src/lib/composer/{commit.test.ts,commit.ts}; apps/web/src/lib/files/{uploads.svelte.test.ts,uploads.svelte.ts}; apps/web/src/lib/tooltip/tooltip.test.ts; apps/web/src/routes/calendar/[view]/[date]/+page.svelte; crates/calternal-notes-core/src/{dayfile.rs,lib.rs,links.rs}; crates/calternal-notes-core/tests/log_rewrite.rs; crates/calternal-server/src/wire.rs; crates/plugins/calendar/src/view.rs; crates/plugins/files/migrations/0017_log_attachment_trash.sql; crates/plugins/files/src/{agent_undo.rs,index.rs,lib.rs,uploads.rs}; crates/plugins/notes/src/{calendar_links.rs,lib.rs}; packages/ui/src/components/calendar/{AttachmentDeck.svelte,ItemPreview.svelte,attachments.ts,model.ts}; packages/ui/src/components/tooltip/{TooltipLayer.svelte,place.ts}; tests/adversarial/attack2.py.

## Finished: #427 and #460 **Head:** `0182ec91f7f81ab2cfc63a3eeb69d38fb035e0ad` ### Delivered - Log batch sends now render all pending entries and reconcile them with durable server IDs. New lines enter by time; existing hand-ordered lines keep their positions. - Calendar attachment cards show image thumbnails and friendly labels such as `Photo · 16:33`, keep full names in collision-aware tooltips, and show outside-deleted files as **Missing** with **Remove link**. - Files Trash, Undo, Trash restore, rename and move preserve attachment links by stable identity. An open Log preview follows refreshed Files state by block ID. - Tus finalization is retry-safe through the stable upload ID. Repeated finalization reuses the installed file instead of creating a duplicate. - Regenerated `contracts/openapi.json` and `packages/api-client/src/generated.ts`; `bash packages/api-client/check-generated.sh` completed successfully. ### Production proof - The batch-send production flow showed all three entries live and in chronological order. - Focused attachment proof passed: `calendar attachment e2e: lifecycle and responsive proof passed`. It covers live attachments, Trash Undo, restore from Trash, edit/restore race, outside deletion to Missing, Remove link, photo/document labels and tooltip collision checks. - The card was captured at 390, 820 and 1440 px in Paper and Tokyo Night. The six images are attached here: - [390 px, light](https://git.kayg.org/attachments/2421f394-8ab1-4d07-b07a-f43f8fbe19d9) · [390 px, dark](https://git.kayg.org/attachments/16b221a9-4bab-4894-b989-ce545957b8fe) - [820 px, light](https://git.kayg.org/attachments/75ab29c0-8bf8-4e3e-825b-480b98d3296d) · [820 px, dark](https://git.kayg.org/attachments/2ecb928d-979a-4526-8878-84d50ce337b6) - [1440 px, light](https://git.kayg.org/attachments/9397b862-3276-415c-865f-d43ae02db99c) · [1440 px, dark](https://git.kayg.org/attachments/63da4854-cee5-465c-857e-8e4b6c438322) ### Gates Captured output: ```text cargo fmt --check (no output; exit 0) bun run check svelte-check found 0 errors and 0 warnings bun run test Test Files 137 passed (137) Tests 889 passed (889) cargo test -p calternal-plugin-files test result: FAILED. 131 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 331.72s cargo test -p calternal-plugin-files --lib tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm -- --exact test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 132 filtered out; finished in 197.07s cargo test -p calternal-notes-core test result: FAILED. 510 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.03s cargo test -p calternal-notes-core parse_and_classify_are_fast -- --exact test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 510 filtered out; finished in 0.72s cargo test -p calternal-plugin-notes test result: ok. 126 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 302.84s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.67s cargo test -p calternal-plugin-calendar test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.17s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s cargo test -p calternal-server test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 57.97s CALENDAR_E2E_ATTACHMENT_PROOF_ONLY=1 ... bun e2e/calendar.mjs calendar attachment e2e: lifecycle and responsive proof passed ``` Clippy passed for `calternal-plugin-files`, `calternal-plugin-calendar`, `calternal-server`, `calternal-plugin-notes` and `calternal-notes-core` with `-D warnings`. The generated API check passed. The production build completed; it printed existing vendor `use client` directive warnings. ### Known gaps and findings - The Files full suite had one timeout/stress failure (`entry not found`); the same test passed alone. Notes-core had one performance-threshold failure at 8.252 ms against an 8 ms limit; it passed alone. Both occurred under shared-host load. - The broader Calendar/Composer production scripts still hit unrelated existing assertions (composer draft freeze, the #53 repair fixture, and a held-send test that assumes today while the route is on yesterday). The focused attachment proof and the requested three-entry live-order flow passed. Existing expectations were left unchanged. - One concurrent server worker storm returned 503s/timeouts while the server stayed alive. Evidence is filed as #493; the attachment-specific concurrent probe passed. ### Decisions - #460 uses the existing Tus upload ID as the retry identity and serializes finalization for that ID. This avoids adding another client idempotency token and makes a retry reuse the committed file. - A selected preview follows fresh range data by stable Log block ID, so Files feed changes update an open card without changing its selection. ### Files `CONTEXT.md`; `docs/DESIGN.md`; `contracts/openapi.json`; `packages/api-client/src/generated.ts`; `apps/web/e2e/{calendar.mjs,composer.mjs,harness.mjs}`; `apps/web/src/lib/calendar/{attachments.test.ts,data.ts,journal.ts}`; `apps/web/src/lib/composer/{commit.test.ts,commit.ts}`; `apps/web/src/lib/files/{uploads.svelte.test.ts,uploads.svelte.ts}`; `apps/web/src/lib/tooltip/tooltip.test.ts`; `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`; `crates/calternal-notes-core/src/{dayfile.rs,lib.rs,links.rs}`; `crates/calternal-notes-core/tests/log_rewrite.rs`; `crates/calternal-server/src/wire.rs`; `crates/plugins/calendar/src/view.rs`; `crates/plugins/files/migrations/0017_log_attachment_trash.sql`; `crates/plugins/files/src/{agent_undo.rs,index.rs,lib.rs,uploads.rs}`; `crates/plugins/notes/src/{calendar_links.rs,lib.rs}`; `packages/ui/src/components/calendar/{AttachmentDeck.svelte,ItemPreview.svelte,attachments.ts,model.ts}`; `packages/ui/src/components/tooltip/{TooltipLayer.svelte,place.ts}`; `tests/adversarial/attack2.py`.
Author
Owner

Started merge-round-3 on branch job/merge-round-3, from fetched origin/dev at 558457cf32e1d429da3834a05ff2720d11284302. The prior clean integration branch is preserved. I will merge the nine requested branches in order, resolve Files migration numbers, regenerate the contract and client, and run the requested per-crate and web gates. No push or deployment.

Started merge-round-3 on branch `job/merge-round-3`, from fetched `origin/dev` at `558457cf32e1d429da3834a05ff2720d11284302`. The prior clean integration branch is preserved. I will merge the nine requested branches in order, resolve Files migration numbers, regenerate the contract and client, and run the requested per-crate and web gates. No push or deployment.
Author
Owner

All nine requested branches are merged in order on job/merge-round-3. Files uses 0016 Share Search invalidations, 0017 Log attachment Trash, and 0018 Sidecar pairs. Conflicts retain DELETE child re-parenting, chronological insertion, Notes locking for Files mutations, attachment metadata in Trash, photo edit file movement and restore, and the existing Maintenance redirects. Settings now says “Photo edit files (.xmp, .aae)” and starts with what Files shows.

The final git fetch origin / git merge origin/dev brought document thumbnail and attachment request-struct changes. The resolution keeps those together with #427's friendly capture labels, Missing state and bounded deck pagination. Gate logs are in artifacts/merge-round-3/.

A merge interaction removed Log metadata from the Index before the Markdown restore completed. I removed that early deletion; the existing explicit cleanup remains after successful restore. A new migration test upgrades a version-15 Index with a retained Share through 16–18, then applies the migrations again to prove repeat startup succeeds.

All nine requested branches are merged in order on `job/merge-round-3`. Files uses 0016 Share Search invalidations, 0017 Log attachment Trash, and 0018 Sidecar pairs. Conflicts retain DELETE child re-parenting, chronological insertion, Notes locking for Files mutations, attachment metadata in Trash, photo edit file movement and restore, and the existing Maintenance redirects. Settings now says “Photo edit files (.xmp, .aae)” and starts with what Files shows. The final `git fetch origin` / `git merge origin/dev` brought document thumbnail and attachment request-struct changes. The resolution keeps those together with #427's friendly capture labels, Missing state and bounded deck pagination. Gate logs are in `artifacts/merge-round-3/`. A merge interaction removed Log metadata from the Index before the Markdown restore completed. I removed that early deletion; the existing explicit cleanup remains after successful restore. A new migration test upgrades a version-15 Index with a retained Share through 16–18, then applies the migrations again to prove repeat startup succeeds.
Author
Owner

Web type check passed: svelte-check found 0 errors and 0 warnings.

The web suite reported one timeout with unchanged expectations:
Test Files 1 failed | 136 passed (137)
Tests 1 failed | 907 passed (908)
StatRow summary cards > toggles all four cards together and remembers the mode.

The requested isolated rerun (bun run test src/lib/components/analytics/widgets/StatRow.svelte.test.ts) passed:
Test Files 1 passed (1)
Tests 13 passed (13).

I will retain and quote both complete gate logs in the final report. No assertion or timeout was changed.

Web type check passed: `svelte-check found 0 errors and 0 warnings`. The web suite reported one timeout with unchanged expectations: `Test Files 1 failed | 136 passed (137)` `Tests 1 failed | 907 passed (908)` `StatRow summary cards > toggles all four cards together and remembers the mode`. The requested isolated rerun (`bun run test src/lib/components/analytics/widgets/StatRow.svelte.test.ts`) passed: `Test Files 1 passed (1)` `Tests 13 passed (13)`. I will retain and quote both complete gate logs in the final report. No assertion or timeout was changed.
Author
Owner

The read-only source review found a concrete inherited paired-file recovery gap from job/hidden-420: a crash after the photo edit file enters Trash and before its photo moves leaves the edit file in Trash while recovery returns the photo's Log links. A crash between the parent and edit-file restore moves has the symmetric split.

The integration fix uses the existing stable parent ID and existing restore intents. Recovery returns already-trashed edit files before restoring aggregate Log links when the photo move did not commit. Recovery also finishes pending restore file moves before restoring Log links. Fingerprints must match before a returned edit file receives its indexed identity. A regression test replays both filesystem/Index boundaries. Rust validation is pending; no passing claim yet.

The read-only source review found a concrete inherited paired-file recovery gap from `job/hidden-420`: a crash after the photo edit file enters Trash and before its photo moves leaves the edit file in Trash while recovery returns the photo's Log links. A crash between the parent and edit-file restore moves has the symmetric split. The integration fix uses the existing stable parent ID and existing restore intents. Recovery returns already-trashed edit files before restoring aggregate Log links when the photo move did not commit. Recovery also finishes pending restore file moves before restoring Log links. Fingerprints must match before a returned edit file receives its indexed identity. A regression test replays both filesystem/Index boundaries. Rust validation is pending; no passing claim yet.
Author
Owner

The live normal-pair lifecycle returned 500 during Trash. The Files crate regression run reproduced it:

---- tests::paired_files_keep_log_links_through_rename_trash_and_restore stdout ----
assertion `left == right` failed
  left: 500
 right: 200

The fixture used invalid XMP bytes. Tags correctly rejects this source as a conflict, but Files mapped the Tags error to a generic 500. Files now keeps a conflict response and cancels the untouched Trash reservation for invalid metadata. A new regression checks the conflict and absence of a pending intent. The normal paired-file test now uses valid XMP. No existing test expectation was changed.

A second source review identified two gaps in the recovery follow-up. Pair restore intents are now written in one transaction. Recovery checks destination occupancy for the full pair before moving any member, so an uncommitted refused restore can cancel its intents without poisoning startup. These follow-ups await the new Files gates.

The live normal-pair lifecycle returned 500 during Trash. The Files crate regression run reproduced it: ``` ---- tests::paired_files_keep_log_links_through_rename_trash_and_restore stdout ---- assertion `left == right` failed left: 500 right: 200 ``` The fixture used invalid XMP bytes. Tags correctly rejects this source as a conflict, but Files mapped the Tags error to a generic 500. Files now keeps a conflict response and cancels the untouched Trash reservation for invalid metadata. A new regression checks the conflict and absence of a pending intent. The normal paired-file test now uses valid XMP. No existing test expectation was changed. A second source review identified two gaps in the recovery follow-up. Pair restore intents are now written in one transaction. Recovery checks destination occupancy for the full pair before moving any member, so an uncommitted refused restore can cancel its intents without poisoning startup. These follow-ups await the new Files gates.
Author
Owner

The local DAV replay passed:

PASS live DAV: 100-href multiget (11945 bytes), all 100 calendar-data responses; DELETE retained and re-parented the attachment child.

The attachment e2e first timed out waiting for the filename tooltip (TimeoutError: textContent: Timeout 30000ms exceeded.). Its isolated rerun passed without changing the test:

calendar attachment e2e: lifecycle and responsive proof passed

The production screenshots cover 390, 820 and 1440 px in Paper and Tokyo Night and are attached to this issue. They are for the orchestrator's visual review.

Composer's first run stopped before server startup because the shared-host media setup counted 4370 local threads, above its 4090-thread threshold. This is host-load evidence, not an application failure. A later bounded retry will be recorded with the first run.

The externally refreshed origin/dev ref now points beyond the final fetched/merged cutoff 8ac92b4008e489f22710878c69f481aabeabcb0a. All nine requested branch tips and that cutoff are ancestors of the integration branch. Per the one-fetch/merge rule, I am not chasing subsequent remote advances.

The local DAV replay passed: ``` PASS live DAV: 100-href multiget (11945 bytes), all 100 calendar-data responses; DELETE retained and re-parented the attachment child. ``` The attachment e2e first timed out waiting for the filename tooltip (`TimeoutError: textContent: Timeout 30000ms exceeded.`). Its isolated rerun passed without changing the test: ``` calendar attachment e2e: lifecycle and responsive proof passed ``` The production screenshots cover 390, 820 and 1440 px in Paper and Tokyo Night and are attached to this issue. They are for the orchestrator's visual review. Composer's first run stopped before server startup because the shared-host media setup counted 4370 local threads, above its 4090-thread threshold. This is host-load evidence, not an application failure. A later bounded retry will be recorded with the first run. The externally refreshed `origin/dev` ref now points beyond the final fetched/merged cutoff `8ac92b4008e489f22710878c69f481aabeabcb0a`. All nine requested branch tips and that cutoff are ancestors of the integration branch. Per the one-fetch/merge rule, I am not chasing subsequent remote advances.
Author
Owner

Local performance run: bun apps/web/e2e/send-fast-perf.mjs, 3 runs per batch size, debug server, production web build. The profile records one batch write per send plus visible/acknowledged p50/p95 and server mean/peak CPU and RSS. Revision: 6a025baad12aafaec426cc9b13f7893c3ca3cbf0 (before the pending Files recovery commit; the measured Composer hot path is unchanged by that commit).

Batch entries Visible p50 / p95 (ms) Acknowledged p50 / p95 (ms) Mean / peak CPU (%) Mean / peak RSS (MiB)
1 371.918 / 4147.804 579.327 / 3342.813 11.98 / 66.58 217.081 / 226.875
5 261.119 / 360.147 410.769 / 433.546 25.19 / 158.79 247.889 / 264.250
20 365.138 / 573.030 454.888 / 712.602 15.82 / 168.01 280.801 / 323.355

Load averages before: [21.8, 23.81, 23.37]; after: [18.98, 22.64, 23]. CPU may exceed 100% because the server uses several cores. The first one-entry sample includes cold work.

docs/perf/baseline.json records opening Composer (visible_ms p50 548 / p95 1322 ms), with CPU throttle 4 and different viewports. It has no matching batch-send acknowledgement/resource baseline. These values are adjacent context, not a regression comparison. No threshold issue is warranted from unmatched debug/local metrics. The raw JSON is attached to #427. This was a local run; no perf VM measurement or lock was used.

Local performance run: `bun apps/web/e2e/send-fast-perf.mjs`, 3 runs per batch size, debug server, production web build. The profile records one batch write per send plus visible/acknowledged p50/p95 and server mean/peak CPU and RSS. Revision: `6a025baad12aafaec426cc9b13f7893c3ca3cbf0` (before the pending Files recovery commit; the measured Composer hot path is unchanged by that commit). | Batch entries | Visible p50 / p95 (ms) | Acknowledged p50 / p95 (ms) | Mean / peak CPU (%) | Mean / peak RSS (MiB) | | --- | --- | --- | --- | --- | | 1 | 371.918 / 4147.804 | 579.327 / 3342.813 | 11.98 / 66.58 | 217.081 / 226.875 | | 5 | 261.119 / 360.147 | 410.769 / 433.546 | 25.19 / 158.79 | 247.889 / 264.250 | | 20 | 365.138 / 573.030 | 454.888 / 712.602 | 15.82 / 168.01 | 280.801 / 323.355 | Load averages before: `[21.8, 23.81, 23.37]`; after: `[18.98, 22.64, 23]`. CPU may exceed 100% because the server uses several cores. The first one-entry sample includes cold work. `docs/perf/baseline.json` records opening Composer (`visible_ms` p50 548 / p95 1322 ms), with CPU throttle 4 and different viewports. It has no matching batch-send acknowledgement/resource baseline. These values are adjacent context, not a regression comparison. No threshold issue is warranted from unmatched debug/local metrics. The raw JSON is attached to #427. This was a local run; no perf VM measurement or lock was used.
Author
Owner

The full Composer e2e retry progressed through the mounted Calendar batch and second-browser checks, then failed an existing assertion:

AssertionError [ERR_ASSERTION]: the held Log stays on today
+ actual - expected
+ '2026-09-29'
- '2026-09-30'

The test had previously opened /calendar/day/${yesterday}. ComposerController.show() uses options.date ?? this.contextDate?.(), and OpenComposerOptions.date documents “default: the context day, else today.” The Calendar installs that context day. The response therefore matches the documented controller behavior. I kept the existing expectation unchanged, as required. This mismatch needs the orchestrator's decision; I did not change the feature's date behavior or weaken the test. The separate required attachment lifecycle e2e passed on its isolated rerun.

The first Composer attempt stopped before startup due to the shared-host media thread threshold; the retry's assertion failure is a different result and is retained separately.

The full Composer e2e retry progressed through the mounted Calendar batch and second-browser checks, then failed an existing assertion: ``` AssertionError [ERR_ASSERTION]: the held Log stays on today + actual - expected + '2026-09-29' - '2026-09-30' ``` The test had previously opened `/calendar/day/${yesterday}`. `ComposerController.show()` uses `options.date ?? this.contextDate?.()`, and `OpenComposerOptions.date` documents “default: the context day, else today.” The Calendar installs that context day. The response therefore matches the documented controller behavior. I kept the existing expectation unchanged, as required. This mismatch needs the orchestrator's decision; I did not change the feature's date behavior or weaken the test. The separate required attachment lifecycle e2e passed on its isolated rerun. The first Composer attempt stopped before startup due to the shared-host media thread threshold; the retry's assertion failure is a different result and is retained separately.
Author
Owner

The production browser denial pass failed an existing pending-Role title assertion:

AssertionError: pending Role hides the admin page title
+ actual - expected
+ 'Calendar · calternal'
- 'Settings · calternal'

The preceding assertions passed: no admin header link and no “Copy link to Users” action while Role was pending. The title did not expose an admin section. Both job/admin-deny-483's merged tree and the fetched dev cutoff use displayedHeader.title; the shared overlay header retains the background Calendar title for a cold Settings link. This is a cosmetic expectation mismatch, not an authorization failure. I kept the expectation unchanged. The pending-title behavior needs the orchestrator's decision. The offline admin coverage guard passed with 39 reviewed operations, and the separate valid-body live authorization matrix is running.

The production browser denial pass failed an existing pending-Role title assertion: ``` AssertionError: pending Role hides the admin page title + actual - expected + 'Calendar · calternal' - 'Settings · calternal' ``` The preceding assertions passed: no admin header link and no “Copy link to Users” action while Role was pending. The title did not expose an admin section. Both `job/admin-deny-483`'s merged tree and the fetched dev cutoff use `displayedHeader.title`; the shared overlay header retains the background Calendar title for a cold Settings link. This is a cosmetic expectation mismatch, not an authorization failure. I kept the expectation unchanged. The pending-title behavior needs the orchestrator's decision. The offline admin coverage guard passed with 39 reviewed operations, and the separate valid-body live authorization matrix is running.
Author
Owner

Search clippy passed. The full Search unit run hit the existing five-second publication wait deadline:

---- indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories stdout ----
staged publication did not wait for the active Search reader

test result: FAILED. 35 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 34.47s

The required isolated rerun passed without changing the test or its deadline:

running 1 test
test indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 36 filtered out; finished in 3.50s

The test waits for a staged rebuild to reach the write lock while a reader holds the old generation. It did not reach that point within five seconds in the combined run on this shared host. Both logs are retained in the merge report. No expectation was changed.

Search clippy passed. The full Search unit run hit the existing five-second publication wait deadline: ``` ---- indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories stdout ---- staged publication did not wait for the active Search reader test result: FAILED. 35 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 34.47s ``` The required isolated rerun passed without changing the test or its deadline: ``` running 1 test test indexer::tests::staged_publication_waits_for_search_readers_before_swapping_directories ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 36 filtered out; finished in 3.50s ``` The test waits for a staged rebuild to reach the write lock while a reader holds the old generation. It did not reach that point within five seconds in the combined run on this shared host. Both logs are retained in the merge report. No expectation was changed.
Author
Owner

The bounded valid-body authorization matrix completed all 1891 requests, with 59 failures. No unauthorized success or 5xx was listed. Most failures were a 403 body/code mismatch:

!! GET /api/v1/admin/config as standard/valid: expected 403 (forbidden), received 403 (None)
!! PUT /api/v1/admin/config as stale_admin/valid: expected step-up 403 (forbidden), received 403 (None)
!! POST /api/v1/auth/sessions/revoke-all as standard/valid: authorized session revocation returned 403
!! POST /api/v1/auth/sessions/revoke-all as admin/valid: authorized session revocation returned 403
authorization matrix found 59 failures

The existing sample() chooses False for booleans. Therefore the schema-valid app_surfaces_put and admin_app_surfaces_put fixtures disable a surface. The authorized writes can switch off API access before later admin-denial cases. session_context then correctly returns the disabled-surface 403 text before the admin route guard, rather than the guard's JSON forbidden code. The long run also leaves the Web sessions used at the final fresh-assertion revocation checks older than at fixture setup; this is a likely contributor to those two 403s and is not yet independently verified.

I kept the existing fixtures and expectations unchanged under the owner rule. The existing focused ADMIN_DENIAL_ONLY mode is being checked with fresh fixtures and valid bodies; it sends no authorized admin configuration writes. This keeps the whole-matrix failure distinct from a focused proof of fail-closed admin guards. The matrix's final text still says “valid/malformed” because that label is hard-coded, but this bounded run used valid bodies only. Malformed and exploit/DoS phases were not run.

Decision needed from the orchestrator: repair the matrix fixture order/bodies and freshness setup before treating the full matrix as green. The integration branch cannot be reported as all gates green while this result remains.

The bounded valid-body authorization matrix completed all 1891 requests, with 59 failures. No unauthorized success or 5xx was listed. Most failures were a 403 body/code mismatch: ``` !! GET /api/v1/admin/config as standard/valid: expected 403 (forbidden), received 403 (None) !! PUT /api/v1/admin/config as stale_admin/valid: expected step-up 403 (forbidden), received 403 (None) !! POST /api/v1/auth/sessions/revoke-all as standard/valid: authorized session revocation returned 403 !! POST /api/v1/auth/sessions/revoke-all as admin/valid: authorized session revocation returned 403 authorization matrix found 59 failures ``` The existing `sample()` chooses `False` for booleans. Therefore the schema-valid `app_surfaces_put` and `admin_app_surfaces_put` fixtures disable a surface. The authorized writes can switch off API access before later admin-denial cases. `session_context` then correctly returns the disabled-surface 403 text before the admin route guard, rather than the guard's JSON `forbidden` code. The long run also leaves the Web sessions used at the final fresh-assertion revocation checks older than at fixture setup; this is a likely contributor to those two 403s and is not yet independently verified. I kept the existing fixtures and expectations unchanged under the owner rule. The existing focused `ADMIN_DENIAL_ONLY` mode is being checked with fresh fixtures and valid bodies; it sends no authorized admin configuration writes. This keeps the whole-matrix failure distinct from a focused proof of fail-closed admin guards. The matrix's final text still says “valid/malformed” because that label is hard-coded, but this bounded run used valid bodies only. Malformed and exploit/DoS phases were not run. Decision needed from the orchestrator: repair the matrix fixture order/bodies and freshness setup before treating the full matrix as green. The integration branch cannot be reported as all gates green while this result remains.
Author
Owner

Starting merge-round-3 in job/merge-round-3, based on origin/dev at cd3cea757508d9f581d00fb7d5d7973fbc0a75fd. I preserved the prior local job/merge-round-3 ref as job/merge-round-3-pre-rebuild and am assembling the requested branches in order. No push, deploy, or merge to dev is planned.

Starting merge-round-3 in `job/merge-round-3`, based on `origin/dev` at `cd3cea757508d9f581d00fb7d5d7973fbc0a75fd`. I preserved the prior local `job/merge-round-3` ref as `job/merge-round-3-pre-rebuild` and am assembling the requested branches in order. No push, deploy, or merge to dev is planned.
Author
Owner

Merge round 3 integration report

State: incomplete; do not fast-forward this snapshot to dev yet. The four-hour job limit ended while CLI Clippy was compiling. No completed final-tree gate failed. No source branch was dropped because the completed gates showed no failing branch.

  • Branch: job/merge-round-3
  • Head: 3e5056d485e9021d2d1f708613e783b0b901b447
  • Included in order: job/multiget-500, job/dav-delete-471, job/iso-435, job/admin-deny-483, job/attach-427, job/hidden-420, job/files-sel-keys, job/small-bugs-3, job/sweep-478.
  • Additional commits: 92f5803f3, 3ea69e317, 26b986bc4, 0948cffa1, 99c088193, df6b07a5a, 3e5056d48.

Completed gate output (verbatim excerpts)

cargo fmt --check exited 0 with no output.

  • DAV clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 56.92s`
  • DAV tests:
    test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s
    test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.31s
  • Notes Core clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 14.97s`
  • Notes Core tests:
    test result: ok. 512 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s
    test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.21s
    test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
    test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.35s
    test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  • Notes plugin clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 2m 55s`
  • Notes plugin tests: test result: ok. 127 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 151.26s
  • Files clippy (after comment fix): Finished \dev` profile [unoptimized + debuginfo] target(s) in 48.77s`
  • Files tests: test result: ok. 144 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 178.62s
    The dev-version migration test passed: test tests::dev_files_schema_upgrades_through_share_log_and_sidecar_migrations ... ok
  • Calendar clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 53s`
  • Calendar tests:
    test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.01s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
    test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s
  • Photos clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 13s`
  • Photos tests: test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.10s
  • Search clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 55.79s`
  • Search tests:
    test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 46.71s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.38s
    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
    test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.44s
    test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
    test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
    test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.66s
    test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
  • Embed clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 27.32s`
  • Embed tests: test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s
  • Filesystem clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 10.78s`
  • Filesystem tests:
    test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.04s
    test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.56s
  • Server clippy: Finished \dev` profile [unoptimized + debuginfo] target(s) in 1m 48s`
  • Server tests: test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.67s

Other completed checks:

  • Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps
  • Cross-User classification gate: 311 operations classified; its test suite printed Ran 5 tests in 0.246s and OK.
  • Admin coverage: 39 reviewed operations; contract and Rust guards agree; its test suite printed Ran 14 tests in 2.404s and OK.
  • Migration audit: ai: 4 migrations, no duplicate numbers; analytics: 2 migrations, no duplicate numbers; calendar: 3 migrations, no duplicate numbers; files: 18 migrations, no duplicate numbers; mail: 8 migrations, no duplicate numbers; notes: 19 migrations, no duplicate numbers; notifications: 4 migrations, no duplicate numbers; photos: 6 migrations, no duplicate numbers; video: 1 migrations, no duplicate numbers.

Remaining work

  • CLI Clippy was interrupted at the four-hour limit while checking dependencies; CLI tests and both Auth gates did not run.
  • The generated contract check, web bun run check, bun run test, and bun run build are pending.
  • The live two-User matrix, authz matrix, DAV round (including Apple’s 100-href and DELETE re-parent replays), sidecar probe, and attachment e2e are pending.
  • Production-browser screenshots for each affected screen at 390/820/1440 px in light/dark mode are pending. No visual review artifacts were produced.
  • The new benchmark profile was added, but its local run and comparison with docs/perf/baseline.json are pending.
  • cargo clean is running but has not returned yet; apps/web/build was removed.

Decisions

  • Files migration IDs follow merge order after dev’s 0015: 0016 share_search_invalidations, 0017 log_attachment_trash, 0018 sidecar_pairs. The populated dev-schema upgrade test passed.
  • Hidden-file Settings copy leads with the User-visible result and uses “Photo edit files (.xmp, .aae)”.
  • The parity exception snapshot was regenerated and reviewed for the newly merged API and Files UI actions.

The branch contains the merged code and commits, but the listed pending gates mean this is not a green merge candidate yet.

## Merge round 3 integration report **State: incomplete; do not fast-forward this snapshot to `dev` yet.** The four-hour job limit ended while CLI Clippy was compiling. No completed final-tree gate failed. No source branch was dropped because the completed gates showed no failing branch. - Branch: `job/merge-round-3` - Head: `3e5056d485e9021d2d1f708613e783b0b901b447` - Included in order: `job/multiget-500`, `job/dav-delete-471`, `job/iso-435`, `job/admin-deny-483`, `job/attach-427`, `job/hidden-420`, `job/files-sel-keys`, `job/small-bugs-3`, `job/sweep-478`. - Additional commits: `92f5803f3`, `3ea69e317`, `26b986bc4`, `0948cffa1`, `99c088193`, `df6b07a5a`, `3e5056d48`. ### Completed gate output (verbatim excerpts) `cargo fmt --check` exited 0 with no output. - DAV clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 56.92s` - DAV tests: `test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s` `test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s` `test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.31s` - Notes Core clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 14.97s` - Notes Core tests: `test result: ok. 512 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.21s` `test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.21s` `test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s` `test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.35s` `test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s` - Notes plugin clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 2m 55s` - Notes plugin tests: `test result: ok. 127 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 151.26s` - Files clippy (after comment fix): `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 48.77s` - Files tests: `test result: ok. 144 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 178.62s` The dev-version migration test passed: `test tests::dev_files_schema_upgrades_through_share_log_and_sidecar_migrations ... ok` - Calendar clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 53s` - Calendar tests: `test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.01s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s` `test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s` - Photos clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 13s` - Photos tests: `test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.10s` - Search clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 55.79s` - Search tests: `test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 46.71s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.38s` `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s` `test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.44s` `test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s` `test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s` `test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.66s` `test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s` - Embed clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 27.32s` - Embed tests: `test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s` - Filesystem clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 10.78s` - Filesystem tests: `test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.04s` `test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.56s` - Server clippy: `Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 1m 48s` - Server tests: `test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 19.67s` Other completed checks: - `Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps` - `Cross-User classification gate: 311 operations classified`; its test suite printed `Ran 5 tests in 0.246s` and `OK`. - `Admin coverage: 39 reviewed operations; contract and Rust guards agree`; its test suite printed `Ran 14 tests in 2.404s` and `OK`. - Migration audit: `ai: 4 migrations, no duplicate numbers`; `analytics: 2 migrations, no duplicate numbers`; `calendar: 3 migrations, no duplicate numbers`; `files: 18 migrations, no duplicate numbers`; `mail: 8 migrations, no duplicate numbers`; `notes: 19 migrations, no duplicate numbers`; `notifications: 4 migrations, no duplicate numbers`; `photos: 6 migrations, no duplicate numbers`; `video: 1 migrations, no duplicate numbers`. ### Remaining work - CLI Clippy was interrupted at the four-hour limit while checking dependencies; CLI tests and both Auth gates did not run. - The generated contract check, web `bun run check`, `bun run test`, and `bun run build` are pending. - The live two-User matrix, authz matrix, DAV round (including Apple’s 100-href and DELETE re-parent replays), sidecar probe, and attachment e2e are pending. - Production-browser screenshots for each affected screen at 390/820/1440 px in light/dark mode are pending. No visual review artifacts were produced. - The new benchmark profile was added, but its local run and comparison with `docs/perf/baseline.json` are pending. - `cargo clean` is running but has not returned yet; `apps/web/build` was removed. ### Decisions - Files migration IDs follow merge order after dev’s 0015: 0016 `share_search_invalidations`, 0017 `log_attachment_trash`, 0018 `sidecar_pairs`. The populated dev-schema upgrade test passed. - Hidden-file Settings copy leads with the User-visible result and uses “Photo edit files (.xmp, .aae)”. - The parity exception snapshot was regenerated and reviewed for the newly merged API and Files UI actions. The branch contains the merged code and commits, but the listed pending gates mean this is not a green merge candidate yet.
Author
Owner

Merge round 3 report addendum

Branch job/merge-round-3, HEAD 3e5056d485e9021d2d1f708613e783b0b901b447.

  • Cleanup completed after the main report: Removed 25367 files, 17.5GiB total.
  • apps/web/build removal check: web build output removed: True.
  • git diff --check exited 0 with no output; the worktree has no modified or untracked files.
  • The 4-hour cutoff also prevented a full re-read of documentation comments across all 234 changed files. This remains an audit gap alongside the pending gates listed in the main report.

The branch is still not a green merge candidate.

## Merge round 3 report addendum Branch `job/merge-round-3`, HEAD `3e5056d485e9021d2d1f708613e783b0b901b447`. - Cleanup completed after the main report: `Removed 25367 files, 17.5GiB total`. - `apps/web/build` removal check: `web build output removed: True`. - `git diff --check` exited 0 with no output; the worktree has no modified or untracked files. - The 4-hour cutoff also prevented a full re-read of documentation comments across all 234 changed files. This remains an audit gap alongside the pending gates listed in the main report. The branch is still not a green merge candidate.
Author
Owner

Resuming merge round 3 on job/merge-round-3, HEAD/base 3e5056d485e9021d2d1f708613e783b0b901b447. The fetched origin/dev is f2d03f37af584f4259c0fc9119fd524b91332a50. git merge-tree --write-tree HEAD origin/dev exited 0 with no conflicts. Per the resume instruction, no merge is required and gates stay on the exact requested tree. Per-crate gates use CARGO_BUILD_JOBS=6. Build/web/contract/classification gates and normal local functional lifecycle checks will run. Exploit, hostile-input and cross-User intrusion probes will remain incomplete. No push or deploy.

Resuming merge round 3 on `job/merge-round-3`, HEAD/base `3e5056d485e9021d2d1f708613e783b0b901b447`. The fetched `origin/dev` is `f2d03f37af584f4259c0fc9119fd524b91332a50`. `git merge-tree --write-tree HEAD origin/dev` exited 0 with no conflicts. Per the resume instruction, no merge is required and gates stay on the exact requested tree. Per-crate gates use CARGO_BUILD_JOBS=6. Build/web/contract/classification gates and normal local functional lifecycle checks will run. Exploit, hostile-input and cross-User intrusion probes will remain incomplete. No push or deploy.
Author
Owner

Final-tree web check failure at 3e5056d485e9021d2d1f708613e783b0b901b447:

$ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes and UI shape values use shared role tokens.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web
Getting Svelte diagnostics...

/home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:497:26
Error: Cannot find name 'failed'. Did you mean 'File'? (ts)
  function thumbFailed(key: string): void {
    const next = new Set(failed);
    next.add(key);

/home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:499:5
Error: Cannot find name 'failed'. Did you mean 'File'? (ts)
    next.add(key);
    failed = next;
  }

====================================
svelte-check found 2 errors and 0 warnings in 1 file
error: script "check" exited with code 1

AttachmentDeck.svelte retains an unused thumbFailed() function that refers to the removed failed state (lines 497 and 499). Search finds no call to this function. Thumbnail rendering now uses FileThumb, which owns its fallback state. The obsolete helper is present in the requested HEAD. No expectation or source was changed: the resume prompt requires gate-only work on this exact tree. This is a compile/type-check failure, not a SLOW result. The merge candidate cannot be reported green.

Final-tree web check failure at `3e5056d485e9021d2d1f708613e783b0b901b447`: ``` $ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes and UI shape values use shared role tokens. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web Getting Svelte diagnostics... /home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:497:26 Error: Cannot find name 'failed'. Did you mean 'File'? (ts) function thumbFailed(key: string): void { const next = new Set(failed); next.add(key); /home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:499:5 Error: Cannot find name 'failed'. Did you mean 'File'? (ts) next.add(key); failed = next; } ==================================== svelte-check found 2 errors and 0 warnings in 1 file error: script "check" exited with code 1 ``` `AttachmentDeck.svelte` retains an unused `thumbFailed()` function that refers to the removed `failed` state (lines 497 and 499). Search finds no call to this function. Thumbnail rendering now uses `FileThumb`, which owns its fallback state. The obsolete helper is present in the requested HEAD. No expectation or source was changed: the resume prompt requires gate-only work on this exact tree. This is a compile/type-check failure, not a SLOW result. The merge candidate cannot be reported green.
Author
Owner

Web test run at the unchanged requested HEAD finished with a timeout:

 FAIL  |component| src/lib/components/ThemePicker.svelte.test.ts > ThemePicker variants > offers the saved dark variant in Auto while the active scheme is light
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
 ❯ src/lib/components/ThemePicker.svelte.test.ts:31:2
     29|
     30| describe('ThemePicker variants', () => {
     31|  it('offers the saved dark variant in Auto while the active scheme is …
       |  ^
     32|   const onselectvariant = vi.fn();
     33|   const { getByRole, findByRole } = render(ThemePicker, {

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯


 Test Files  1 failed | 136 passed (137)
      Tests  1 failed | 914 passed (915)
   Start at  18:21:50
   Duration  332.86s (transform 55%, environment 19%, import 13%, tests 9%, setup 4%)

Environment  |component| jsdom was created 43 times · 338.89s total, 28% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

error: script "test" exited with code 1

One isolated rerun passed without changing source, fixtures, expectations or the 5000ms timeout:

$ vitest run src/lib/components/ThemePicker.svelte.test.ts -t "offers the saved dark variant in Auto while the active scheme is light"

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/merge-check/apps/web


 Test Files  1 passed (1)
      Tests  1 passed | 4 skipped (5)
   Start at  18:29:11
   Duration  12.81s (transform 62%, environment 19%, tests 8%, setup 8%, import 3%)

  Transform  |component| transforming modules took 7.75s · 62% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

The full run remains recorded as exit 1. The separate web check failure from the obsolete AttachmentDeck helper is a confirmed type error. The production build exited 0.

Web test run at the unchanged requested HEAD finished with a timeout: ``` FAIL |component| src/lib/components/ThemePicker.svelte.test.ts > ThemePicker variants > offers the saved dark variant in Auto while the active scheme is light Error: Test timed out in 5000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ src/lib/components/ThemePicker.svelte.test.ts:31:2 29| 30| describe('ThemePicker variants', () => { 31| it('offers the saved dark variant in Auto while the active scheme is … | ^ 32| const onselectvariant = vi.fn(); 33| const { getByRole, findByRole } = render(ThemePicker, { ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ Test Files 1 failed | 136 passed (137) Tests 1 failed | 914 passed (915) Start at 18:21:50 Duration 332.86s (transform 55%, environment 19%, import 13%, tests 9%, setup 4%) Environment |component| jsdom was created 43 times · 338.89s total, 28% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments error: script "test" exited with code 1 ``` One isolated rerun passed without changing source, fixtures, expectations or the 5000ms timeout: ``` $ vitest run src/lib/components/ThemePicker.svelte.test.ts -t "offers the saved dark variant in Auto while the active scheme is light" RUN v5.0.1 /home/kayg/Developer/calternal-wt/merge-check/apps/web Test Files 1 passed (1) Tests 1 passed | 4 skipped (5) Start at 18:29:11 Duration 12.81s (transform 62%, environment 19%, tests 8%, setup 8%, import 3%) Transform |component| transforming modules took 7.75s · 62% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` The full run remains recorded as exit 1. The separate web check failure from the obsolete AttachmentDeck helper is a confirmed type error. The production build exited 0.
Author
Owner

Resume checkpoint on unchanged HEAD 3e5056d485e9021d2d1f708613e783b0b901b447. CLI and Auth gates passed. Verbatim result lines:

calternal-cli clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 20s

calternal-cli test:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 40s
test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.21s

calternal-auth clippy:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 52s

calternal-auth test:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 49s
test result: ok. 63 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 88.25s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Search onwards continues with CARGO_BUILD_JOBS=6. Web results were posted separately.

Resume checkpoint on unchanged HEAD `3e5056d485e9021d2d1f708613e783b0b901b447`. CLI and Auth gates passed. Verbatim result lines: `calternal-cli` clippy: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 20s ``` `calternal-cli` test: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 40s test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.21s ``` `calternal-auth` clippy: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 52s ``` `calternal-auth` test: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 49s test result: ok. 63 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 88.25s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Search onwards continues with CARGO_BUILD_JOBS=6. Web results were posted separately.
Author
Owner

Search Clippy passed on the unchanged requested HEAD. Search unit tests passed (36 passed; 0 failed; 1 ignored), as did ask evaluation and citation integration tests. The indexer integration binary failed while waiting for a newly indexed fixture:

---- rebuild_matches_incremental_results stdout ----

thread 'rebuild_matches_incremental_results' (1778951) panicked at crates/calternal-search/tests/indexer.rs:102:5:
search did not find users/alice/Files/first.txt for "phrase"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    rebuild_matches_incremental_results

test result: FAILED. 20 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.45s

error: test failed, to rerun pass `-p calternal-search --test indexer`

The helper polls 250 times with 20 ms sleeps. No source, fixtures or expectations were changed. The runner continues through the remaining crates. A single isolated rerun and the integration binaries skipped by Cargo will run after this sequence; the original full-gate exit 101 will remain in the report.

Search Clippy passed on the unchanged requested HEAD. Search unit tests passed (`36 passed; 0 failed; 1 ignored`), as did ask evaluation and citation integration tests. The indexer integration binary failed while waiting for a newly indexed fixture: ``` ---- rebuild_matches_incremental_results stdout ---- thread 'rebuild_matches_incremental_results' (1778951) panicked at crates/calternal-search/tests/indexer.rs:102:5: search did not find users/alice/Files/first.txt for "phrase" note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: rebuild_matches_incremental_results test result: FAILED. 20 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.45s error: test failed, to rerun pass `-p calternal-search --test indexer` ``` The helper polls 250 times with 20 ms sleeps. No source, fixtures or expectations were changed. The runner continues through the remaining crates. A single isolated rerun and the integration binaries skipped by Cargo will run after this sequence; the original full-gate exit 101 will remain in the report.
Author
Owner

Search timeout follow-up on unchanged HEAD 3e5056d485e9021d2d1f708613e783b0b901b447. The single isolated failed case, the integration binaries skipped after Cargo stopped, and doc tests passed. No expectations or source changed. The original full cargo test -p calternal-search exit 101 remains recorded. Verbatim output excerpts:

resume-search-isolated.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 3.94s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 3.23s

resume-search-remaining.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.10s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 10.58s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

resume-search-doc.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.77s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Search timeout follow-up on unchanged HEAD `3e5056d485e9021d2d1f708613e783b0b901b447`. The single isolated failed case, the integration binaries skipped after Cargo stopped, and doc tests passed. No expectations or source changed. The original full `cargo test -p calternal-search` exit 101 remains recorded. Verbatim output excerpts: `resume-search-isolated.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 3.94s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 3.23s ``` `resume-search-remaining.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 1.10s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 10.58s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` `resume-search-doc.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 0.77s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ```
Author
Owner

Generated contract gate failed at unchanged requested HEAD 3e5056d485e9021d2d1f708613e783b0b901b447. bash packages/api-client/check-generated.sh built a fresh server and generated the contract/client, then git diff --exit-code returned 1. Verbatim diff:

diff --git a/contracts/openapi.json b/contracts/openapi.json
index 47602df53..7d8d32e50 100644
--- a/contracts/openapi.json
+++ b/contracts/openapi.json
@@ -5368,7 +5368,7 @@
     "/api/v1/files/restore": {
       "post": {
         "tags": [],
-        "summary": "Restore a Files item and then its Log children by stable block ID.",
+        "summary": "Restore a Files item, its paired Sidecars and its Log links by stable block ID\n(#427, #420; DESIGN §§4 and 33).",
         "operationId": "restore",
         "requestBody": {
           "content": {
@@ -5590,7 +5590,7 @@
     "/api/v1/files/trash": {
       "post": {
         "tags": [],
-        "summary": "Move Files items into Trash with their Log attachment links saved for Undo.",
+        "summary": "Move Files items and paired Sidecars into Trash while saving Log links for Undo\n(#427, #420; DESIGN §§4 and 33).",
         "operationId": "trash",
         "requestBody": {
           "content": {
diff --git a/packages/api-client/src/generated.ts b/packages/api-client/src/generated.ts
index d697d9ecd..7e49876f8 100644
--- a/packages/api-client/src/generated.ts
+++ b/packages/api-client/src/generated.ts
@@ -1766,6 +1766,10 @@ export interface paths {
         };
         get?: never;
         put?: never;
+        /**
+         * Copy source Items after live read-grant and destination write checks.
+         *     An unshared source matches a missing Item response (#435, DESIGN §26).
+         */
         post: operations["copy_paths"];
         delete?: never;
         options?: never;
@@ -1780,6 +1784,10 @@ export interface paths {
             path?: never;
             cookie?: never;
         };
+        /**
+         * Download a Files Item through the viewer's live Share grants. An unshared
+         *     or revoked ordinary path returns the same 404 as a missing Item (#435, DESIGN §26).
+         */
         get: operations["download"];
         put?: never;
         post?: never;
@@ -1798,6 +1806,10 @@ export interface paths {
         };
         get?: never;
         put?: never;
+        /**
+         * Stream files into one archive after checking each source against live Share
+         *     grants. An unshared source matches a missing Item response (#435, DESIGN §26).
+         */
         post: operations["download_zip"];
         delete?: never;
         options?: never;
@@ -2023,7 +2035,10 @@ export interface paths {
         };
         get?: never;
         put?: never;
-        /** Restore a Files item and then its Log children by stable block ID. */
+        /**
+         * Restore a Files item, its paired Sidecars and its Log links by stable block ID
+         *     (#427, #420; DESIGN §§4 and 33).
+         */
         post: operations["restore"];
         delete?: never;
         options?: never;
@@ -2104,7 +2119,10 @@ export interface paths {
         };
         get?: never;
         put?: never;
-        /** Move Files items into Trash with their Log attachment links saved for Undo. */
+        /**
+         * Move Files items and paired Sidecars into Trash while saving Log links for Undo
+         *     (#427, #420; DESIGN §§4 and 33).
+         */
         post: operations["trash"];
         delete?: never;
         options?: never;
@@ -3607,6 +3625,10 @@ export interface paths {
             path?: never;
             cookie?: never;
         };
+        /**
+         * Search Photos through the server-injected User context. Vector authority
+         *     cannot be supplied by a request owner ID (#458).
+         */
         get: operations["photos_search"];
         put?: never;
         post?: never;
@@ -11826,6 +11848,22 @@ export interface operations {
                     "application/json": components["schemas"]["ErrorEnvelope"];
                 };
             };
+            403: {
+                headers: {
+                    [name: string]: unknown;
+                };
+                content: {
+                    "application/json": components["schemas"]["ErrorEnvelope"];
+                };
+            };
+            404: {
+                headers: {
+                    [name: string]: unknown;
+                };
+                content: {
+                    "application/json": components["schemas"]["ErrorEnvelope"];
+                };
+            };
             507: {
                 headers: {
                     [name: string]: unknown;
@@ -11870,6 +11908,14 @@ export interface operations {
                 };
                 content?: never;
             };
+            404: {
+                headers: {
+                    [name: string]: unknown;
+                };
+                content: {
+                    "application/json": components["schemas"]["ErrorEnvelope"];
+                };
+            };
             /** @description The file changed during the read; try again */
             409: {
                 headers: {
@@ -11908,6 +11954,14 @@ export interface operations {
                     "application/json": components["schemas"]["ErrorEnvelope"];
                 };
             };
+            404: {
+                headers: {
+                    [name: string]: unknown;
+                };
+                content: {
+                    "application/json": components["schemas"]["ErrorEnvelope"];
+                };
+            };
         };
     };
     list: {

The generated files were restored to HEAD after saving this evidence, as the resume prompt requires only gates on the exact tree. No source or expected output was changed. The worktree is clean. This is stale generated output, not a SLOW finding.

Generated contract gate failed at unchanged requested HEAD `3e5056d485e9021d2d1f708613e783b0b901b447`. `bash packages/api-client/check-generated.sh` built a fresh server and generated the contract/client, then `git diff --exit-code` returned 1. Verbatim diff: ```diff diff --git a/contracts/openapi.json b/contracts/openapi.json index 47602df53..7d8d32e50 100644 --- a/contracts/openapi.json +++ b/contracts/openapi.json @@ -5368,7 +5368,7 @@ "/api/v1/files/restore": { "post": { "tags": [], - "summary": "Restore a Files item and then its Log children by stable block ID.", + "summary": "Restore a Files item, its paired Sidecars and its Log links by stable block ID\n(#427, #420; DESIGN §§4 and 33).", "operationId": "restore", "requestBody": { "content": { @@ -5590,7 +5590,7 @@ "/api/v1/files/trash": { "post": { "tags": [], - "summary": "Move Files items into Trash with their Log attachment links saved for Undo.", + "summary": "Move Files items and paired Sidecars into Trash while saving Log links for Undo\n(#427, #420; DESIGN §§4 and 33).", "operationId": "trash", "requestBody": { "content": { diff --git a/packages/api-client/src/generated.ts b/packages/api-client/src/generated.ts index d697d9ecd..7e49876f8 100644 --- a/packages/api-client/src/generated.ts +++ b/packages/api-client/src/generated.ts @@ -1766,6 +1766,10 @@ export interface paths { }; get?: never; put?: never; + /** + * Copy source Items after live read-grant and destination write checks. + * An unshared source matches a missing Item response (#435, DESIGN §26). + */ post: operations["copy_paths"]; delete?: never; options?: never; @@ -1780,6 +1784,10 @@ export interface paths { path?: never; cookie?: never; }; + /** + * Download a Files Item through the viewer's live Share grants. An unshared + * or revoked ordinary path returns the same 404 as a missing Item (#435, DESIGN §26). + */ get: operations["download"]; put?: never; post?: never; @@ -1798,6 +1806,10 @@ export interface paths { }; get?: never; put?: never; + /** + * Stream files into one archive after checking each source against live Share + * grants. An unshared source matches a missing Item response (#435, DESIGN §26). + */ post: operations["download_zip"]; delete?: never; options?: never; @@ -2023,7 +2035,10 @@ export interface paths { }; get?: never; put?: never; - /** Restore a Files item and then its Log children by stable block ID. */ + /** + * Restore a Files item, its paired Sidecars and its Log links by stable block ID + * (#427, #420; DESIGN §§4 and 33). + */ post: operations["restore"]; delete?: never; options?: never; @@ -2104,7 +2119,10 @@ export interface paths { }; get?: never; put?: never; - /** Move Files items into Trash with their Log attachment links saved for Undo. */ + /** + * Move Files items and paired Sidecars into Trash while saving Log links for Undo + * (#427, #420; DESIGN §§4 and 33). + */ post: operations["trash"]; delete?: never; options?: never; @@ -3607,6 +3625,10 @@ export interface paths { path?: never; cookie?: never; }; + /** + * Search Photos through the server-injected User context. Vector authority + * cannot be supplied by a request owner ID (#458). + */ get: operations["photos_search"]; put?: never; post?: never; @@ -11826,6 +11848,22 @@ export interface operations { "application/json": components["schemas"]["ErrorEnvelope"]; }; }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; 507: { headers: { [name: string]: unknown; @@ -11870,6 +11908,14 @@ export interface operations { }; content?: never; }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; /** @description The file changed during the read; try again */ 409: { headers: { @@ -11908,6 +11954,14 @@ export interface operations { "application/json": components["schemas"]["ErrorEnvelope"]; }; }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorEnvelope"]; + }; + }; }; }; list: { ``` The generated files were restored to HEAD after saving this evidence, as the resume prompt requires only gates on the exact tree. No source or expected output was changed. The worktree is clean. This is stale generated output, not a SLOW finding.
Author
Owner

The focused production attachment e2e failed at the final desktop/dark tooltip capture. Verbatim error:

403 |         await preview.waitFor();
404 |           assert.ok(await photoChip.isVisible(), `${width}px: the EXIF label remains visible`);
405 |           assert.ok(await preview.getByRole('button', { name: 'Open Boarding pass' }).isVisible());
406 |           assert.ok(await preview.getByRole('button', { name: 'Missing attachment Old gym scan. Remove link' }).isVisible());
407 |           await boardingPassChip.hover();
408 |           await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === 'boarding-pass.pdf', `${width}px: document tooltip did not update`);
                                                                                                   ^
TimeoutError: textContent: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')

  log: [ "  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ],

      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:94
      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19
      at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:17)
      at processTicksAndRejections (native:7:39)

The preceding normal lifecycle assertions completed; five responsive screenshots were captured. The 1000-attachment pagination assertions were not reached. A single unchanged focused rerun is in progress; both results will be retained. No source, expectations, fixtures or timeout settings changed.

The focused production attachment e2e failed at the final desktop/dark tooltip capture. Verbatim error: ``` 403 | await preview.waitFor(); 404 | assert.ok(await photoChip.isVisible(), `${width}px: the EXIF label remains visible`); 405 | assert.ok(await preview.getByRole('button', { name: 'Open Boarding pass' }).isVisible()); 406 | assert.ok(await preview.getByRole('button', { name: 'Missing attachment Old gym scan. Remove link' }).isVisible()); 407 | await boardingPassChip.hover(); 408 | await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === 'boarding-pass.pdf', `${width}px: document tooltip did not update`); ^ TimeoutError: textContent: Timeout 30000ms exceeded. Call log:  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label') log: [ " - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ], at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:94 at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19 at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:17) at processTicksAndRejections (native:7:39) ``` The preceding normal lifecycle assertions completed; five responsive screenshots were captured. The 1000-attachment pagination assertions were not reached. A single unchanged focused rerun is in progress; both results will be retained. No source, expectations, fixtures or timeout settings changed.
Author
Owner

The one unchanged attachment rerun also failed on tooltip visibility, this time at the initial photo tooltip:

361 |     await preview.waitFor();
362 |     const photoChip = preview.getByRole('button', { name: 'Open Photo · 16:33' });
363 |     await photoChip.waitFor();
364 |     await photoChip.hover();
365 |     const attachmentTooltip = page.locator('.warm-tooltip.visible');
366 |     await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === photoName, 'photo tooltip did not show the full camera file name');
                                                                                             ^
TimeoutError: textContent: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')

  log: [ "  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ],

      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:88
      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19
      at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:11)
      at processTicksAndRejections (native:7:39)

No more browser retries will run. Both focused e2e runs failed and are reported as failures, not assumed SLOW-only results. Five production screenshots from the first run exist (390/820/1440 Paper, 390/820 Tokyo Night); 1440 Tokyo Night and the final imported-list pagination assertions remain incomplete. The normal local DAV and Sidecar lifecycle checks both passed.

The one unchanged attachment rerun also failed on tooltip visibility, this time at the initial photo tooltip: ``` 361 | await preview.waitFor(); 362 | const photoChip = preview.getByRole('button', { name: 'Open Photo · 16:33' }); 363 | await photoChip.waitFor(); 364 | await photoChip.hover(); 365 | const attachmentTooltip = page.locator('.warm-tooltip.visible'); 366 | await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === photoName, 'photo tooltip did not show the full camera file name'); ^ TimeoutError: textContent: Timeout 30000ms exceeded. Call log:  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label') log: [ " - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ], at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:88 at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19 at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:11) at processTicksAndRejections (native:7:39) ``` No more browser retries will run. Both focused e2e runs failed and are reported as failures, not assumed SLOW-only results. Five production screenshots from the first run exist (390/820/1440 Paper, 390/820 Tokyo Night); 1440 Tokyo Night and the final imported-list pagination assertions remain incomplete. The normal local DAV and Sidecar lifecycle checks both passed.
Author
Owner

Merge round 3 resume report

State: not a green merge candidate. The requested gate runs are finished. Confirmed blockers remain in the unchanged snapshot.

  • Branch: job/merge-round-3
  • HEAD: 3e5056d485e9021d2d1f708613e783b0b901b447
  • Built: fresh debug server and production web app for gate and normal local lifecycle checks. No source changes or new commits.
  • Files: evidence is under artifacts/merge-round-3/resume-* (ignored). Source files and checked-in generated files are unchanged.
  • No push, deploy or merge to dev.
  • Cargo: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=6, TMPDIR=<worktree>/target/tmp. The preset CARGO_TARGET_DIR was used. Each crate ran separately.

Findings and known gaps

  1. bun run check failed with two undeclared failed references in packages/ui/src/components/calendar/AttachmentDeck.svelte:497 and :499. The unused thumbFailed() helper remains after thumbnail failure handling moved to FileThumb. This is a confirmed type error. Evidence was posted on #427.
  2. Generated contract/client check failed. Fresh generation changes the Files Trash/restore summaries and adds missing client documentation and response declarations. The complete diff was posted on #427 and saved as resume-generated.diff. Generated files were restored to HEAD to preserve the exact requested tree.
  3. Full Search tests failed in rebuild_matches_incremental_results while waiting for the new fixture to appear. One unchanged isolated rerun passed. The integration binaries skipped after Cargo stopped and the doc tests passed. The original gate remains exit 101.
  4. Full web tests had one 5000ms ThemePicker timeout (914 passed). One unchanged isolated rerun passed. The original full gate remains exit 1.
  5. Focused production attachment e2e failed twice on tooltip visibility: first at the final desktop/dark capture, then at the initial photo tooltip. These failures are not assumed SLOW-only. No assertions, fixtures or timeouts were changed. The final imported-list pagination assertions were not reached. The 1440px dark screenshot remains missing.
  6. The full live two-User and authz matrices and hostile-input/exploit probe phases were not run. Safemode limits this run to the offline classification suites and normal local functional checks. Classification is not live isolation proof.
  7. No new benchmark or full historical doc-comment audit was done in this gate-only resume. Those gaps in the earlier report remain outside this resume's scope. Visual review belongs to the orchestrator.

Rust gates: verbatim output excerpts

cargo fmt --check exited 0 with no output.

cargo clippy -p calternal-cli --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 20s

cargo test -p calternal-cli:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 40s
test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.21s

cargo clippy -p calternal-auth --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 52s

cargo test -p calternal-auth:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 49s
test result: ok. 63 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 88.25s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-search --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 53s

cargo test -p calternal-search:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 4m 53s
test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 39.70s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.14s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: FAILED. 20 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.45s

cargo clippy -p calternal-embed --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 24s

cargo test -p calternal-embed:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 08s
test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

cargo clippy -p calternal-fs --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.16s

cargo test -p calternal-fs:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 38.14s
test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.13s
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 23s

cargo test -p calternal-plugin-calendar:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 4m 53s
test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.21s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 15s

cargo test -p calternal-plugin-photos:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 57s
test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 8.30s
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 4m 07s

cargo test -p calternal-server:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 10s
test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 28.53s

Search follow-up: verbatim result lines

resume-search-isolated.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 3.94s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 3.23s

resume-search-remaining.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.10s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 10.58s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

resume-search-doc.log:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.77s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Web, contract, parity and classification: verbatim output

bun run --cwd apps/web check (exit 1):

$ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes and UI shape values use shared role tokens.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web
Getting Svelte diagnostics...

/home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:497:26
Error: Cannot find name 'failed'. Did you mean 'File'? (ts)
  function thumbFailed(key: string): void {
    const next = new Set(failed);
    next.add(key);

/home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:499:5
Error: Cannot find name 'failed'. Did you mean 'File'? (ts)
    next.add(key);
    failed = next;
  }

====================================
svelte-check found 2 errors and 0 warnings in 1 file
error: script "check" exited with code 1

bun run --cwd apps/web test (exit 1):

 FAIL  |component| src/lib/components/ThemePicker.svelte.test.ts > ThemePicker variants > offers the saved dark variant in Auto while the active scheme is light
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
 ❯ src/lib/components/ThemePicker.svelte.test.ts:31:2
     29|
     30| describe('ThemePicker variants', () => {
     31|  it('offers the saved dark variant in Auto while the active scheme is …
       |  ^
     32|   const onselectvariant = vi.fn();
     33|   const { getByRole, findByRole } = render(ThemePicker, {

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯


 Test Files  1 failed | 136 passed (137)
      Tests  1 failed | 914 passed (915)
   Start at  18:21:50
   Duration  332.86s (transform 55%, environment 19%, import 13%, tests 9%, setup 4%)

Environment  |component| jsdom was created 43 times · 338.89s total, 28% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

error: script "test" exited with code 1

Isolated ThemePicker rerun (exit 0):

$ vitest run src/lib/components/ThemePicker.svelte.test.ts -t "offers the saved dark variant in Auto while the active scheme is light"

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/merge-check/apps/web


 Test Files  1 passed (1)
      Tests  1 passed | 4 skipped (5)
   Start at  18:29:11
   Duration  12.81s (transform 62%, environment 19%, tests 8%, setup 8%, import 3%)

  Transform  |component| transforming modules took 7.75s · 62% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

bun run --cwd apps/web build (exit 0):

✓ built in 1m 56s
✓ built in 31ms
✓ built in 3m 39s
  Wrote site to "build"

bash packages/api-client/check-generated.sh (exit 1; diff posted in the finding comment):

Search isolated exit=0
Search remaining integrations exit=0
Search doc tests exit=0
Generated contract check exit=1

resume-parity.log (passed):

Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps

resume-xuser-classification.log (passed):

Cross-User classification gate: 311 operations classified
.....
----------------------------------------------------------------------
Ran 5 tests in 0.301s

OK

resume-admin-classification.log (passed):

Admin coverage: 39 reviewed operations; contract and Rust guards agree
..............
----------------------------------------------------------------------
Ran 14 tests in 1.296s

OK

Local normal lifecycle checks: verbatim output

DAV and Sidecar run (exit 0):

PASS live DAV: 100-href multiget (11945 bytes), all 100 calendar-data responses; DELETE retained and re-parented the attachment child.
PASS: normal photo edit-file pairing, hidden listing, rename, Trash and restore on the local server.

Attachment resume-attachment-e2e.log (exit 1):

403 |         await preview.waitFor();
404 |           assert.ok(await photoChip.isVisible(), `${width}px: the EXIF label remains visible`);
405 |           assert.ok(await preview.getByRole('button', { name: 'Open Boarding pass' }).isVisible());
406 |           assert.ok(await preview.getByRole('button', { name: 'Missing attachment Old gym scan. Remove link' }).isVisible());
407 |           await boardingPassChip.hover();
408 |           await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === 'boarding-pass.pdf', `${width}px: document tooltip did not update`);
                                                                                                   ^
TimeoutError: textContent: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')

  log: [ "  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ],

      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:94
      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19
      at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:17)
      at processTicksAndRejections (native:7:39)

Attachment resume-attachment-retry.log (exit 1):

361 |     await preview.waitFor();
362 |     const photoChip = preview.getByRole('button', { name: 'Open Photo · 16:33' });
363 |     await photoChip.waitFor();
364 |     await photoChip.hover();
365 |     const attachmentTooltip = page.locator('.warm-tooltip.visible');
366 |     await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === photoName, 'photo tooltip did not show the full camera file name');
                                                                                             ^
TimeoutError: textContent: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')

  log: [ "  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ],

      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:88
      at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19
      at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:11)
      at processTicksAndRejections (native:7:39)

Production evidence

Five screenshots from the first attachment run were uploaded to #427. They show the same real local fixtures in the production app. They do not prove the unfinished checks.

Decisions

  • The fetched origin/dev was f2d03f37af584f4259c0fc9119fd524b91332a50. git merge-tree --write-tree HEAD origin/dev exited 0 with no conflicts. Per the resume exception, no new merge was made and all gates stayed on the exact requested HEAD.
  • Gate-only work on this exact tree takes precedence over source repairs. Confirmed non-SLOW failures were filed on #427. No generated diff was committed.
  • Each timeout received at most one unchanged isolated/focused rerun. Original full-run failures remain in the record. No further retries will run.
  • No product design decision was made.

Cleanup and final status

cargo clean exited 0:

     Removed 22700 files, 14.6GiB total

apps/web/build and apps/web/.svelte-kit/output were removed. git diff --check exited 0 with no output. git status --porcelain=v1 produced no output. HEAD remains 3e5056d485e9021d2d1f708613e783b0b901b447. No tracked source or generated files changed.

Do not fast-forward this snapshot to dev as an all-green candidate. The failures and incomplete live isolation coverage require follow-up.

# Merge round 3 resume report State: not a green merge candidate. The requested gate runs are finished. Confirmed blockers remain in the unchanged snapshot. - Branch: `job/merge-round-3` - HEAD: `3e5056d485e9021d2d1f708613e783b0b901b447` - Built: fresh debug server and production web app for gate and normal local lifecycle checks. No source changes or new commits. - Files: evidence is under `artifacts/merge-round-3/resume-*` (ignored). Source files and checked-in generated files are unchanged. - No push, deploy or merge to dev. - Cargo: `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=6`, `TMPDIR=<worktree>/target/tmp`. The preset `CARGO_TARGET_DIR` was used. Each crate ran separately. ## Findings and known gaps 1. `bun run check` failed with two undeclared `failed` references in `packages/ui/src/components/calendar/AttachmentDeck.svelte:497` and `:499`. The unused `thumbFailed()` helper remains after thumbnail failure handling moved to `FileThumb`. This is a confirmed type error. Evidence was posted on #427. 2. Generated contract/client check failed. Fresh generation changes the Files Trash/restore summaries and adds missing client documentation and response declarations. The complete diff was posted on #427 and saved as `resume-generated.diff`. Generated files were restored to HEAD to preserve the exact requested tree. 3. Full Search tests failed in `rebuild_matches_incremental_results` while waiting for the new fixture to appear. One unchanged isolated rerun passed. The integration binaries skipped after Cargo stopped and the doc tests passed. The original gate remains exit 101. 4. Full web tests had one 5000ms ThemePicker timeout (914 passed). One unchanged isolated rerun passed. The original full gate remains exit 1. 5. Focused production attachment e2e failed twice on tooltip visibility: first at the final desktop/dark capture, then at the initial photo tooltip. These failures are not assumed SLOW-only. No assertions, fixtures or timeouts were changed. The final imported-list pagination assertions were not reached. The 1440px dark screenshot remains missing. 6. The full live two-User and authz matrices and hostile-input/exploit probe phases were not run. Safemode limits this run to the offline classification suites and normal local functional checks. Classification is not live isolation proof. 7. No new benchmark or full historical doc-comment audit was done in this gate-only resume. Those gaps in the earlier report remain outside this resume's scope. Visual review belongs to the orchestrator. ## Rust gates: verbatim output excerpts `cargo fmt --check` exited 0 with no output. `cargo clippy -p calternal-cli --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 20s ``` `cargo test -p calternal-cli`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 40s test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.21s ``` `cargo clippy -p calternal-auth --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 52s ``` `cargo test -p calternal-auth`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 6m 49s test result: ok. 63 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 88.25s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-search --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 53s ``` `cargo test -p calternal-search`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 4m 53s test result: ok. 36 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 39.70s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.14s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: FAILED. 20 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.45s ``` `cargo clippy -p calternal-embed --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 24s ``` `cargo test -p calternal-embed`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 08s test result: ok. 31 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.93s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` `cargo clippy -p calternal-fs --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.16s ``` `cargo test -p calternal-fs`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 38.14s test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.13s test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 23s ``` `cargo test -p calternal-plugin-calendar`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 4m 53s test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.21s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 15s ``` `cargo test -p calternal-plugin-photos`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 57s test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 8.30s 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 4m 07s ``` `cargo test -p calternal-server`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 10s test result: ok. 85 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 28.53s ``` ### Search follow-up: verbatim result lines `resume-search-isolated.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 3.94s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 20 filtered out; finished in 3.23s ``` `resume-search-remaining.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 1.10s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 10.58s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` `resume-search-doc.log`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 0.77s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` ## Web, contract, parity and classification: verbatim output `bun run --cwd apps/web check` (exit 1): ``` $ node scripts/check-type-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes and UI shape values use shared role tokens. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web Getting Svelte diagnostics... /home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:497:26 Error: Cannot find name 'failed'. Did you mean 'File'? (ts) function thumbFailed(key: string): void { const next = new Set(failed); next.add(key); /home/kayg/Developer/calternal-wt/merge-check/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:499:5 Error: Cannot find name 'failed'. Did you mean 'File'? (ts) next.add(key); failed = next; } ==================================== svelte-check found 2 errors and 0 warnings in 1 file error: script "check" exited with code 1 ``` `bun run --cwd apps/web test` (exit 1): ``` FAIL |component| src/lib/components/ThemePicker.svelte.test.ts > ThemePicker variants > offers the saved dark variant in Auto while the active scheme is light Error: Test timed out in 5000ms. If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout". ❯ src/lib/components/ThemePicker.svelte.test.ts:31:2 29| 30| describe('ThemePicker variants', () => { 31| it('offers the saved dark variant in Auto while the active scheme is … | ^ 32| const onselectvariant = vi.fn(); 33| const { getByRole, findByRole } = render(ThemePicker, { ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ Test Files 1 failed | 136 passed (137) Tests 1 failed | 914 passed (915) Start at 18:21:50 Duration 332.86s (transform 55%, environment 19%, import 13%, tests 9%, setup 4%) Environment |component| jsdom was created 43 times · 338.89s total, 28% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments error: script "test" exited with code 1 ``` Isolated ThemePicker rerun (exit 0): ``` $ vitest run src/lib/components/ThemePicker.svelte.test.ts -t "offers the saved dark variant in Auto while the active scheme is light" RUN v5.0.1 /home/kayg/Developer/calternal-wt/merge-check/apps/web Test Files 1 passed (1) Tests 1 passed | 4 skipped (5) Start at 18:29:11 Duration 12.81s (transform 62%, environment 19%, tests 8%, setup 8%, import 3%) Transform |component| transforming modules took 7.75s · 62% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` `bun run --cwd apps/web build` (exit 0): ``` ✓ built in 1m 56s ✓ built in 31ms ✓ built in 3m 39s Wrote site to "build" ``` `bash packages/api-client/check-generated.sh` (exit 1; diff posted in the finding comment): ``` Search isolated exit=0 Search remaining integrations exit=0 Search doc tests exit=0 Generated contract check exit=1 ``` `resume-parity.log` (passed): ``` Parity matrix: 190 web API actions, 122 shortcuts, 2 static commands, 136 menu actions, 31 settings groups, 172 actions with adapter gaps ``` `resume-xuser-classification.log` (passed): ``` Cross-User classification gate: 311 operations classified ..... ---------------------------------------------------------------------- Ran 5 tests in 0.301s OK ``` `resume-admin-classification.log` (passed): ``` Admin coverage: 39 reviewed operations; contract and Rust guards agree .............. ---------------------------------------------------------------------- Ran 14 tests in 1.296s OK ``` ## Local normal lifecycle checks: verbatim output DAV and Sidecar run (exit 0): ``` PASS live DAV: 100-href multiget (11945 bytes), all 100 calendar-data responses; DELETE retained and re-parented the attachment child. PASS: normal photo edit-file pairing, hidden listing, rename, Trash and restore on the local server. ``` Attachment `resume-attachment-e2e.log` (exit 1): ``` 403 | await preview.waitFor(); 404 | assert.ok(await photoChip.isVisible(), `${width}px: the EXIF label remains visible`); 405 | assert.ok(await preview.getByRole('button', { name: 'Open Boarding pass' }).isVisible()); 406 | assert.ok(await preview.getByRole('button', { name: 'Missing attachment Old gym scan. Remove link' }).isVisible()); 407 | await boardingPassChip.hover(); 408 | await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === 'boarding-pass.pdf', `${width}px: document tooltip did not update`); ^ TimeoutError: textContent: Timeout 30000ms exceeded. Call log:  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label') log: [ " - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ], at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:94 at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19 at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:408:17) at processTicksAndRejections (native:7:39) ``` Attachment `resume-attachment-retry.log` (exit 1): ``` 361 | await preview.waitFor(); 362 | const photoChip = preview.getByRole('button', { name: 'Open Photo · 16:33' }); 363 | await photoChip.waitFor(); 364 | await photoChip.hover(); 365 | const attachmentTooltip = page.locator('.warm-tooltip.visible'); 366 | await waitUntil(async () => await attachmentTooltip.locator('.warm-tooltip-label').textContent() === photoName, 'photo tooltip did not show the full camera file name'); ^ TimeoutError: textContent: Timeout 30000ms exceeded. Call log:  - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label') log: [ " - waiting for locator('.warm-tooltip.visible').locator('.warm-tooltip-label')" ], at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:88 at /home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:276:19 at runLogAttachmentProof (/home/kayg/Developer/calternal-wt/merge-check/apps/web/e2e/calendar.mjs:366:11) at processTicksAndRejections (native:7:39) ``` ## Production evidence Five screenshots from the first attachment run were uploaded to #427. They show the same real local fixtures in the production app. They do not prove the unfinished checks. - [resume-attachment-card-1440-paper.png](https://git.kayg.org/attachments/3e88e651-6cd6-4833-80e9-5910079a20c9) - [resume-attachment-card-390-paper.png](https://git.kayg.org/attachments/dbcf47e5-3816-482e-aa52-6aacd6eb6cf4) - [resume-attachment-card-390-tokyo-night.png](https://git.kayg.org/attachments/ff0f6de3-2b2b-455f-bfc6-c5ffc98f0ca6) - [resume-attachment-card-820-paper.png](https://git.kayg.org/attachments/584fc673-a8d7-4bc0-bab6-df73f5235b89) - [resume-attachment-card-820-tokyo-night.png](https://git.kayg.org/attachments/d9215079-574e-4fed-82ea-ab78c54a709f) ## Decisions - The fetched `origin/dev` was `f2d03f37af584f4259c0fc9119fd524b91332a50`. `git merge-tree --write-tree HEAD origin/dev` exited 0 with no conflicts. Per the resume exception, no new merge was made and all gates stayed on the exact requested HEAD. - Gate-only work on this exact tree takes precedence over source repairs. Confirmed non-SLOW failures were filed on #427. No generated diff was committed. - Each timeout received at most one unchanged isolated/focused rerun. Original full-run failures remain in the record. No further retries will run. - No product design decision was made. ## Cleanup and final status `cargo clean` exited 0: ``` Removed 22700 files, 14.6GiB total ``` `apps/web/build` and `apps/web/.svelte-kit/output` were removed. `git diff --check` exited 0 with no output. `git status --porcelain=v1` produced no output. HEAD remains `3e5056d485e9021d2d1f708613e783b0b901b447`. No tracked source or generated files changed. Do not fast-forward this snapshot to dev as an all-green candidate. The failures and incomplete live isolation coverage require follow-up.
Author
Owner

Starting merge round 3 on job/merge-round-3. HEAD: cb75820852a922f3d09cb596fd261b9cb0d0dd43; merge base target origin/dev: 15e17aeafc8ea160c109e62fba57f6961c39d21f. I will merge origin/dev, resolve both sides' intent, then investigate the attachment tooltip and run the requested gates and matrices.

Starting merge round 3 on `job/merge-round-3`. HEAD: `cb75820852a922f3d09cb596fd261b9cb0d0dd43`; merge base target `origin/dev`: `15e17aeafc8ea160c109e62fba57f6961c39d21f`. I will merge `origin/dev`, resolve both sides' intent, then investigate the attachment tooltip and run the requested gates and matrices.
Author
Owner

Search e2e finding: the real server had a 60-query search round-trip p50 of 68.8 ms and p95 of 265.2 ms, then the palette assertion failed with the Notes section missing. The Search test already recognized the 200 ms provider fan-out deadline but retried only when Log entries were absent; it stopped early when another seeded section was missing. I changed the retry condition to wait for the same four expected sections before asserting them. The expected section set is unchanged.

Search e2e finding: the real server had a 60-query search round-trip p50 of 68.8 ms and p95 of 265.2 ms, then the palette assertion failed with the Notes section missing. The Search test already recognized the 200 ms provider fan-out deadline but retried only when Log entries were absent; it stopped early when another seeded section was missing. I changed the retry condition to wait for the same four expected sections before asserting them. The expected section set is unchanged.
Author
Owner

Finding: Search's compact palette failed the centered-growth e2e after the resize observer measured 13 results. The rendered center was 447.17 px while the prior two-result palette center was 450.5 px (900 px viewport). search-dialog.svelte passed the measured top and height together but still transitioned top, contradicting its adjacent layout invariant and causing an observable drift during the transition. I removed the top transition and made the e2e wait for the measured growth before sampling settled bounds. The production Search e2e is being rerun after rebuilding the app.

Finding: Search's compact palette failed the centered-growth e2e after the resize observer measured 13 results. The rendered center was 447.17 px while the prior two-result palette center was 450.5 px (900 px viewport). `search-dialog.svelte` passed the measured `top` and `height` together but still transitioned `top`, contradicting its adjacent layout invariant and causing an observable drift during the transition. I removed the `top` transition and made the e2e wait for the measured growth before sampling settled bounds. The production Search e2e is being rerun after rebuilding the app.
Author
Owner

Finding: after the centered palette checks passed, Search e2e reported no section labels even though .group-head rows were present. The merged Search markup adds .search-group-blur as the first child before each heading label, so the old span:first-child selector matched nothing. I changed the test selector to span:first-of-type; the expected four result sections and their order remain unchanged. The e2e is being rerun.

Finding: after the centered palette checks passed, Search e2e reported no section labels even though `.group-head` rows were present. The merged Search markup adds `.search-group-blur` as the first child before each heading label, so the old `span:first-child` selector matched nothing. I changed the test selector to `span:first-of-type`; the expected four result sections and their order remain unchanged. The e2e is being rerun.
Author
Owner

Finding: the Search e2e now locates the four result headings correctly and passes the responsive centered-growth checks. It next sampled the sticky blur at opacity 0.999997 while the CSS transition was still active, then failed the existing exact 1 assertion. I changed the wait to require opacity 1 and no active transition before reading paint; the expected rendering assertion is unchanged.

Finding: the Search e2e now locates the four result headings correctly and passes the responsive centered-growth checks. It next sampled the sticky blur at opacity `0.999997` while the CSS transition was still active, then failed the existing exact `1` assertion. I changed the wait to require opacity `1` and no active transition before reading paint; the expected rendering assertion is unchanged.
Author
Owner

Finding: after waiting for the blur transition to finish, the Search e2e still failed its unchanged wash assertion because getComputedStyle(...).backgroundImage serializes the CSS transparent stop as rgba(0, 0, 0, 0). I kept the existing /linear-gradient.*transparent/ expectation and changed the probe to inspect the shared --glass-header-wash token, whose declared gradient expresses the same contract. The Search e2e is being rerun.

Finding: after waiting for the blur transition to finish, the Search e2e still failed its unchanged wash assertion because `getComputedStyle(...).backgroundImage` serializes the CSS `transparent` stop as `rgba(0, 0, 0, 0)`. I kept the existing `/linear-gradient.*transparent/` expectation and changed the probe to inspect the shared `--glass-header-wash` token, whose declared gradient expresses the same contract. The Search e2e is being rerun.
Author
Owner

Finding: the sticky blur checks now reach paint validation, where Chromium serializes transparent mask stops as rgba(0, 0, 0, 0), so the unchanged transparent assertion failed on each computed mask. I kept the CSS contract and normalize that CSSOM spelling in the probe before checking each gradient. No UI expectation changed; Search is being rerun.

Finding: the sticky blur checks now reach paint validation, where Chromium serializes transparent mask stops as `rgba(0, 0, 0, 0)`, so the unchanged `transparent` assertion failed on each computed mask. I kept the CSS contract and normalize that CSSOM spelling in the probe before checking each gradient. No UI expectation changed; Search is being rerun.
Author
Owner

Finding: the unchanged Search preview assertion waits for the highlighted text to fit inside its scroll pane. It failed with the mark 0.40625 px above the pane edge (mark.top=178.59375, pane.top=179, scrollTop=72), after SearchPreview called scrollIntoView({ block: 'nearest' }). I added a 1 px scroll-margin-block on preview marks so native nearest scrolling leaves the highlight inside the pane and preserves the existing assertion.

Finding: the unchanged Search preview assertion waits for the highlighted text to fit inside its scroll pane. It failed with the mark 0.40625 px above the pane edge (`mark.top=178.59375`, `pane.top=179`, `scrollTop=72`), after `SearchPreview` called `scrollIntoView({ block: 'nearest' })`. I added a 1 px `scroll-margin-block` on preview marks so native nearest scrolling leaves the highlight inside the pane and preserves the existing assertion.
Author
Owner

Finding: the Search e2e passed through keyboard preview, Search actions, facets, deep links, saved searches and Files scope, then the Calendar integration helper timed out looking for header view controls. With the desktop sidebar open, the merged Calendar UI correctly places sub-views there under DESIGN §34, so the header switcher is absent. I extended the helper to verify the selected Week link in Calendar navigation; the view assertion stays the same.

Finding: the Search e2e passed through keyboard preview, Search actions, facets, deep links, saved searches and Files scope, then the Calendar integration helper timed out looking for header view controls. With the desktop sidebar open, the merged Calendar UI correctly places sub-views there under DESIGN §34, so the header switcher is absent. I extended the helper to verify the selected Week link in Calendar navigation; the view assertion stays the same.
Author
Owner

The live #483 authorization matrix completed 2,055 requests and found 84 failures. For denied admin routes, the bearer API/CLI surface gate returns plain text with HTTP 403 before the role guard runs, so the required forbidden error code is absent. Two valid /auth/sessions/revoke-all Web-cookie calls also returned 403 after the matrix's long sweep. I am preserving the matrix's exact status and error-code expectations; I will make surface-gate denials use the API error envelope and keep the revocation fixture's recent-assertion precondition fresh immediately before that request.

The live #483 authorization matrix completed 2,055 requests and found 84 failures. For denied admin routes, the bearer API/CLI surface gate returns plain text with HTTP 403 before the role guard runs, so the required `forbidden` error code is absent. Two valid `/auth/sessions/revoke-all` Web-cookie calls also returned 403 after the matrix's long sweep. I am preserving the matrix's exact status and error-code expectations; I will make surface-gate denials use the API error envelope and keep the revocation fixture's recent-assertion precondition fresh immediately before that request.
Author
Owner

The focused admin authorization rerun passed after the two fixes. It completed all 2,055 requests across 331 OpenAPI operations with exit code 0 and no failure rows. The bearer surface gate now returns the shared API forbidden envelope, and the fixture restores its disposable Web session's five-minute assertion window immediately before the authorized revoke-all request. No matrix status or error-code expectation changed.

The focused admin authorization rerun passed after the two fixes. It completed all 2,055 requests across 331 OpenAPI operations with exit code 0 and no failure rows. The bearer surface gate now returns the shared API `forbidden` envelope, and the fixture restores its disposable Web session's five-minute assertion window immediately before the authorized revoke-all request. No matrix status or error-code expectation changed.
Author
Owner

cargo test -p calternal-server exposed one fixture failure: the legacy Appearance migration test moved the file on disk but its temporary DB lacked the Notes migration tables required by the shared Files move hook (rewrite_log_attachment_targets_locked checks Note links for every moved item). The route logged and tolerated that migration error, leaving the fixture's Index assertion stale. The expectation is correct; I added the Notes migration to the Appearance test database setup and will rerun the server crate gate.

`cargo test -p calternal-server` exposed one fixture failure: the legacy Appearance migration test moved the file on disk but its temporary DB lacked the Notes migration tables required by the shared Files move hook (`rewrite_log_attachment_targets_locked` checks Note links for every moved item). The route logged and tolerated that migration error, leaving the fixture's Index assertion stale. The expectation is correct; I added the Notes migration to the Appearance test database setup and will rerun the server crate gate.
Author
Owner

#427 — merge round 3 final report

Merged origin/dev at 15e17aeafc8ea160c109e62fba57f6961c39d21f into job/merge-round-3; the merge is f1b067bde. No push, deployment, or merge into dev was done.

Head: cc25c441b7a974185622a1dee853cf38686d2b67

What changed

  • Unified uploadBytes options as { conflictPolicy, completeByPatch, photosUpload, patchChunkBytes } and updated its callers. Kept the cross-User route and case inventory from both branches.
  • Kept bearer API and CLI authorization denials in the shared error envelope. Refreshed only the disposable Web session assertion timestamp before the delayed authorized revoke-all check; no expected status or error code changed.
  • Added Notes migrations to the Appearance test Home because the Files move hook reads Notes link tables (issue #422).
  • Fixed the attachment tooltip e2e proof. Its polling used an auto-waiting Playwright locator and the pointer could stay over a newly mounted chip without a new pointerover; it now reads the DOM without auto-wait and moves to a neutral point before hover. The product tooltip itself worked.
  • Stabilized Search palette sizing, result/preview waits and responsive checks. Corrected the valid Appearance concurrency fixture to omit the retired auto_scheme.location field; its rejection case remains unchanged.
  • Extended Files Tus validation to the legacy .calternal/backgrounds/ path during the DESIGN §35 migration. Both legacy and visible background paths get the 20 MB cap and image decode check; rejected uploads leave no staged row.

Finishing files: apps/web/e2e/harness.mjs, apps/web/e2e/calendar.mjs, apps/web/e2e/search.mjs, apps/web/src/lib/components/search-dialog.svelte, apps/web/src/lib/search/SearchPreview.svelte, crates/calternal-server/src/wire.rs, crates/calternal-server/src/appearance.rs, crates/plugins/files/src/lib.rs, crates/plugins/files/src/uploads.rs, tests/adversarial/authz_matrix.py, tests/adversarial/xuser_matrix.py, and tests/adversarial/attack.py. The benchmark is bench/merge-round-3.mjs, wired through bench/run.sh. The starting branch also includes the orchestrator changes to contracts/openapi.json, packages/api-client/src/generated.ts, and packages/ui/src/components/calendar/AttachmentDeck.svelte.

Gates

bun run check output:

$ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test summary:

 Test Files  140 passed (140)
      Tests  930 passed (930)
   Start at  22:15:28
   Duration  50.92s (transform 49%, environment 20%, import 16%, tests 11%, setup 4%)

Attachment e2e:

calendar attachment e2e: lifecycle and responsive proof passed

Search e2e passed its functional assertions, then failed only its local performance assertions. Exact result:

keystroke to first result frame: p50=175.8ms p95=359.0ms (n=39)
main-thread long tasks: 19 over 50ms, max=724.0ms
AssertionError [ERR_ASSERTION]: palette first-result p95 is within 50 ms: {"p50":175.80000001192093,"p95":359,"count":39}

Two-User isolation matrix output:

==== ROUND 2 FINDINGS 0
==== ROUND 2 SLOW 0
Two-User OpenAPI matrix: 331 operations classified; 159 operations replayed; 718 A-ID vs missing-ID comparisons across B, C, D and anonymous; 22 identifier routes classified with no local fixture factory; median absolute timing delta 0.3 ms
Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures
Revoked Share timing control: identical HTTP 404 profiles; median delta 1.3 ms across 12 alternating pairs

Admin authorization rerun (from the captured #427 result):

The focused admin authorization rerun passed after the two fixes. It completed all 2,055 requests across 331 OpenAPI operations with exit code 0 and no failure rows. The bearer surface gate now returns the shared API `forbidden` envelope, and the fixture restores its disposable Web session's five-minute assertion window immediately before the authorized revoke-all request. No matrix status or error-code expectation changed.

Rust gates used CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 CARGO_TARGET_DIR=/mnt/hdd/targets/merge-check TMPDIR=$PWD/target/tmp. cargo fmt --check exited 0 with no output. For each package, these are the final clippy -D warnings and test summary lines:

$ cargo clippy -p calternal-cli --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 53s
$ cargo test -p calternal-cli
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.76s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.12s
$ cargo clippy -p calternal-dav --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 20s
$ cargo test -p calternal-dav
test result: ok. 41 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-fs --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.00s
$ cargo test -p calternal-fs
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.39s
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.88s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-location --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 48.66s
$ cargo test -p calternal-location
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-notes-core --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 35s
$ cargo test -p calternal-notes-core
test result: ok. 515 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.23s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.50s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-plugin --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 06s
$ cargo test -p calternal-plugin
test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.69s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 36s
$ cargo test -p calternal-plugin-calendar
test result: ok. 80 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.45s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 20.40s
$ cargo test -p calternal-plugin-files
test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 147.60s
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 2m 14s
$ cargo test -p calternal-plugin-mail
test result: ok. 35 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.71s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 20s
$ cargo test -p calternal-plugin-notes
test result: ok. 131 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 123.88s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$ cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 10s
$ cargo test -p calternal-plugin-photos
test result: ok. 46 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.21s
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 34.31s
$ cargo test -p calternal-server
test result: ok. 93 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 50.76s

The one API-only adversarial round ended with the server alive. Its output reported ==== FINDINGS 150 (128 SLOW rows and 22 other rows). The direct background-path failures came from the binary built before commit 6c4f396e6; the Files integration gate passed after the fix. The valid Appearance concurrency fixture was corrected in cc25c441b after the run; I did not repeat the one-round campaign.

Non-SLOW results were reported on existing issues: tag rename/reconcile/rebuild returned 409 and left the probe Tag page empty (#532); Calendar Event-from-Log, Journal and Reminders requests timed out and the completion readback did not show the updated VTODO (#250, #267). The server reported server alive at end: True. The DAV Files credential setup also returned 403. A DAV PUT that updated an existing resource returned 204 while the probe expected 201; it then retained the old ETag and its follow-on move/delete got 412. I kept those existing DAV expectations unchanged for owner review.

Local performance profile

bench/merge-round-3.mjs ran once locally against 1,000 Log attachments and a 24-request burst. It recorded load average before 23.69 / 24.80 / 26.29 and after 27.63 / 25.61 / 26.53. Results:

average range reads: p50=104.7 ms, p95=156.9 ms (10 samples)
24-request burst: elapsed=3777.9 ms, p50=1699.5 ms, p95=3405.7 ms (24 samples)
server resources: mean RSS=161987856 bytes, peak RSS=218337280 bytes, mean CPU=82.39%, peak CPU=170%, CPU time=4.17 s

docs/perf/baseline.json has no 1,000-attachment profile, so there is no like-for-like baseline. The Search first-result p95 was 359.0 ms (50 ms target), with 19 main-thread tasks over 50 ms and max 724.0 ms; I added the local result to #434. These are shared-host diagnostics, not quiet-host regression measurements.

Visual evidence

Attached 24 production-build screenshots to this issue. Calendar attachment cards cover 390, 820 and 1440 px in Paper and Tokyo Night. Search covers few, end and many results at those widths in Paper and Tokyo Night.

Calendar attachment proof:

Search palette proof:

Decisions and gaps

  • DESIGN §35 describes legacy background migration but does not state whether new Tus writes may target its source folder. I kept the folder guarded until migration completes and applied the same size and decoder checks as the visible destination.
  • DESIGN §47 and issue #391 assign saved-place writes to Location. The concurrency probe now uses only valid Appearance fields; the dedicated retired-field rejection case and its 4xx check remain unchanged.
  • The broad API-only round was not repeated after the background guard and fixture correction. Files route tests passed after the guard. Search e2e remains red only on the local performance budget. Tag and mixed-load outcomes remain open on #532, #250 and #267.
  • Build output was cleaned: Removed 24175 files, 15.3GiB total; apps/web/build and apps/web/.svelte-kit were removed.

Related findings were recorded on #551, #553, #532, #250, #267 and #434. No issues were closed.

#427 — merge round 3 final report Merged `origin/dev` at `15e17aeafc8ea160c109e62fba57f6961c39d21f` into `job/merge-round-3`; the merge is `f1b067bde`. No push, deployment, or merge into `dev` was done. **Head:** `cc25c441b7a974185622a1dee853cf38686d2b67` ## What changed - Unified `uploadBytes` options as `{ conflictPolicy, completeByPatch, photosUpload, patchChunkBytes }` and updated its callers. Kept the cross-User route and case inventory from both branches. - Kept bearer API and CLI authorization denials in the shared error envelope. Refreshed only the disposable Web session assertion timestamp before the delayed authorized revoke-all check; no expected status or error code changed. - Added Notes migrations to the Appearance test Home because the Files move hook reads Notes link tables (issue #422). - Fixed the attachment tooltip e2e proof. Its polling used an auto-waiting Playwright locator and the pointer could stay over a newly mounted chip without a new `pointerover`; it now reads the DOM without auto-wait and moves to a neutral point before hover. The product tooltip itself worked. - Stabilized Search palette sizing, result/preview waits and responsive checks. Corrected the valid Appearance concurrency fixture to omit the retired `auto_scheme.location` field; its rejection case remains unchanged. - Extended Files Tus validation to the legacy `.calternal/backgrounds/` path during the DESIGN §35 migration. Both legacy and visible background paths get the 20 MB cap and image decode check; rejected uploads leave no staged row. Finishing files: `apps/web/e2e/harness.mjs`, `apps/web/e2e/calendar.mjs`, `apps/web/e2e/search.mjs`, `apps/web/src/lib/components/search-dialog.svelte`, `apps/web/src/lib/search/SearchPreview.svelte`, `crates/calternal-server/src/wire.rs`, `crates/calternal-server/src/appearance.rs`, `crates/plugins/files/src/lib.rs`, `crates/plugins/files/src/uploads.rs`, `tests/adversarial/authz_matrix.py`, `tests/adversarial/xuser_matrix.py`, and `tests/adversarial/attack.py`. The benchmark is `bench/merge-round-3.mjs`, wired through `bench/run.sh`. The starting branch also includes the orchestrator changes to `contracts/openapi.json`, `packages/api-client/src/generated.ts`, and `packages/ui/src/components/calendar/AttachmentDeck.svelte`. ## Gates `bun run check` output: ```text $ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-check/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` summary: ```text Test Files 140 passed (140) Tests 930 passed (930) Start at 22:15:28 Duration 50.92s (transform 49%, environment 20%, import 16%, tests 11%, setup 4%) ``` Attachment e2e: ```text calendar attachment e2e: lifecycle and responsive proof passed ``` Search e2e passed its functional assertions, then failed only its local performance assertions. Exact result: ```text keystroke to first result frame: p50=175.8ms p95=359.0ms (n=39) main-thread long tasks: 19 over 50ms, max=724.0ms AssertionError [ERR_ASSERTION]: palette first-result p95 is within 50 ms: {"p50":175.80000001192093,"p95":359,"count":39} ``` Two-User isolation matrix output: ```text ==== ROUND 2 FINDINGS 0 ==== ROUND 2 SLOW 0 Two-User OpenAPI matrix: 331 operations classified; 159 operations replayed; 718 A-ID vs missing-ID comparisons across B, C, D and anonymous; 22 identifier routes classified with no local fixture factory; median absolute timing delta 0.3 ms Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures Revoked Share timing control: identical HTTP 404 profiles; median delta 1.3 ms across 12 alternating pairs ``` Admin authorization rerun (from the captured #427 result): ```text The focused admin authorization rerun passed after the two fixes. It completed all 2,055 requests across 331 OpenAPI operations with exit code 0 and no failure rows. The bearer surface gate now returns the shared API `forbidden` envelope, and the fixture restores its disposable Web session's five-minute assertion window immediately before the authorized revoke-all request. No matrix status or error-code expectation changed. ``` Rust gates used `CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 CARGO_TARGET_DIR=/mnt/hdd/targets/merge-check TMPDIR=$PWD/target/tmp`. `cargo fmt --check` exited 0 with no output. For each package, these are the final `clippy -D warnings` and `test` summary lines: ```text $ cargo clippy -p calternal-cli --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 53s $ cargo test -p calternal-cli test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.76s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.12s $ cargo clippy -p calternal-dav --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 20s $ cargo test -p calternal-dav test result: ok. 41 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-fs --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.00s $ cargo test -p calternal-fs test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.39s test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.88s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-location --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 48.66s $ cargo test -p calternal-location test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-notes-core --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 35s $ cargo test -p calternal-notes-core test result: ok. 515 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.23s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.50s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-plugin --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 06s $ cargo test -p calternal-plugin test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.69s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 36s $ cargo test -p calternal-plugin-calendar test result: ok. 80 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.45s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 20.40s $ cargo test -p calternal-plugin-files test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 147.60s 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 2m 14s $ cargo test -p calternal-plugin-mail test result: ok. 35 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.71s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 20s $ cargo test -p calternal-plugin-notes test result: ok. 131 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 123.88s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s $ cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 10s $ cargo test -p calternal-plugin-photos test result: ok. 46 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.21s 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 34.31s $ cargo test -p calternal-server test result: ok. 93 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 50.76s ``` The one API-only adversarial round ended with the server alive. Its output reported `==== FINDINGS 150` (128 `SLOW` rows and 22 other rows). The direct background-path failures came from the binary built before commit `6c4f396e6`; the Files integration gate passed after the fix. The valid Appearance concurrency fixture was corrected in `cc25c441b` after the run; I did not repeat the one-round campaign. Non-SLOW results were reported on existing issues: tag rename/reconcile/rebuild returned 409 and left the probe Tag page empty (#532); Calendar Event-from-Log, Journal and Reminders requests timed out and the completion readback did not show the updated VTODO (#250, #267). The server reported `server alive at end: True`. The DAV Files credential setup also returned 403. A DAV PUT that updated an existing resource returned 204 while the probe expected 201; it then retained the old ETag and its follow-on move/delete got 412. I kept those existing DAV expectations unchanged for owner review. ## Local performance profile `bench/merge-round-3.mjs` ran once locally against 1,000 Log attachments and a 24-request burst. It recorded load average before `23.69 / 24.80 / 26.29` and after `27.63 / 25.61 / 26.53`. Results: ```text average range reads: p50=104.7 ms, p95=156.9 ms (10 samples) 24-request burst: elapsed=3777.9 ms, p50=1699.5 ms, p95=3405.7 ms (24 samples) server resources: mean RSS=161987856 bytes, peak RSS=218337280 bytes, mean CPU=82.39%, peak CPU=170%, CPU time=4.17 s ``` `docs/perf/baseline.json` has no 1,000-attachment profile, so there is no like-for-like baseline. The Search first-result p95 was 359.0 ms (50 ms target), with 19 main-thread tasks over 50 ms and max 724.0 ms; I added the local result to #434. These are shared-host diagnostics, not quiet-host regression measurements. ## Visual evidence Attached 24 production-build screenshots to this issue. Calendar attachment cards cover 390, 820 and 1440 px in Paper and Tokyo Night. Search covers few, end and many results at those widths in Paper and Tokyo Night. Calendar attachment proof: - [attachment-card-1440-paper.png](https://git.kayg.org/attachments/93355a0e-5c69-4d1d-beea-01281058cb54) - [attachment-card-1440-tokyo-night.png](https://git.kayg.org/attachments/d691f304-d008-49c6-9c10-1cdda2434d99) - [attachment-card-390-paper.png](https://git.kayg.org/attachments/6696c433-9293-4150-ba97-41377e649566) - [attachment-card-390-tokyo-night.png](https://git.kayg.org/attachments/3e2b13c5-acb5-4e50-a3c6-e2fc1af3771c) - [attachment-card-820-paper.png](https://git.kayg.org/attachments/860952c6-e1d6-40b7-8178-40c6d198e72a) - [attachment-card-820-tokyo-night.png](https://git.kayg.org/attachments/709cc0d6-fedb-4399-9137-7fa1b5d0d67e) Search palette proof: - [search-palette-end-1440-paper-white.png](https://git.kayg.org/attachments/4222ae45-9b54-49aa-914d-484108f01353) - [search-palette-end-1440-tokyo-night.png](https://git.kayg.org/attachments/1803b6c3-7dd7-40ec-9b3e-97672c8b8d77) - [search-palette-end-390-paper-white.png](https://git.kayg.org/attachments/7dddbea7-605f-4881-a866-975397cf1ad4) - [search-palette-end-390-tokyo-night.png](https://git.kayg.org/attachments/c9d54c88-b796-4007-b94d-246ab0801d7a) - [search-palette-end-820-paper-white.png](https://git.kayg.org/attachments/7c9ca668-b268-4e1a-a676-3abd6b373bbd) - [search-palette-end-820-tokyo-night.png](https://git.kayg.org/attachments/31ad4539-cae9-44c7-94ca-a1718b7fa7f5) - [search-palette-few-1440-paper-white.png](https://git.kayg.org/attachments/66bf1e95-bb86-4640-8913-03b28f2e5cd3) - [search-palette-few-1440-tokyo-night.png](https://git.kayg.org/attachments/5715651d-f46e-4961-a729-b517aec546d6) - [search-palette-few-390-paper-white.png](https://git.kayg.org/attachments/3d51a1d7-fb0f-4eb4-8d7d-ee4c2be980ad) - [search-palette-few-390-tokyo-night.png](https://git.kayg.org/attachments/64c49daa-9c05-4967-a7f9-cfd3c2365477) - [search-palette-few-820-paper-white.png](https://git.kayg.org/attachments/403ce9c2-938e-4e19-a5c1-966dd41272a4) - [search-palette-few-820-tokyo-night.png](https://git.kayg.org/attachments/6f9ddc22-5915-454b-a2bb-c6e7478ee16b) - [search-palette-many-1440-paper-white.png](https://git.kayg.org/attachments/49ac7051-683c-46c8-b795-a00ca85d6048) - [search-palette-many-1440-tokyo-night.png](https://git.kayg.org/attachments/073f389a-12bf-4923-aa28-bc7b491e30c3) - [search-palette-many-390-paper-white.png](https://git.kayg.org/attachments/cf4c6df0-0908-4855-8bdc-dc1df40d3c7f) - [search-palette-many-390-tokyo-night.png](https://git.kayg.org/attachments/0b93e06e-a8b1-4d5a-82e4-657827d85ee1) - [search-palette-many-820-paper-white.png](https://git.kayg.org/attachments/66438830-223b-4e8f-b57a-f1eb4480682d) - [search-palette-many-820-tokyo-night.png](https://git.kayg.org/attachments/0029e8b0-5b15-46c1-a287-f3d7b7c3f432) ## Decisions and gaps - DESIGN §35 describes legacy background migration but does not state whether new Tus writes may target its source folder. I kept the folder guarded until migration completes and applied the same size and decoder checks as the visible destination. - DESIGN §47 and issue #391 assign saved-place writes to Location. The concurrency probe now uses only valid Appearance fields; the dedicated retired-field rejection case and its 4xx check remain unchanged. - The broad API-only round was not repeated after the background guard and fixture correction. Files route tests passed after the guard. Search e2e remains red only on the local performance budget. Tag and mixed-load outcomes remain open on #532, #250 and #267. - Build output was cleaned: `Removed 24175 files, 15.3GiB total`; `apps/web/build` and `apps/web/.svelte-kit` were removed. Related findings were recorded on #551, #553, #532, #250, #267 and #434. No issues were closed.
Author
Owner

Starting merge round 4 on job/merge-round-4, based on cc25c441b7a974185622a1dee853cf38686d2b67 (round 3 / origin/dev). I have read CLAUDE.md, CONTEXT.md, and the relevant navigation, chrome, search, deep-link, plugin, and settings decisions in docs/DESIGN.md. I am merging the approved branches in the requested order and will report conflict resolutions and gate output here.

Starting merge round 4 on `job/merge-round-4`, based on `cc25c441b7a974185622a1dee853cf38686d2b67` (round 3 / `origin/dev`). I have read `CLAUDE.md`, `CONTEXT.md`, and the relevant navigation, chrome, search, deep-link, plugin, and settings decisions in `docs/DESIGN.md`. I am merging the approved branches in the requested order and will report conflict resolutions and gate output here.
Author
Owner

Finding during tray-order conflict resolution: this branch stores a five-item slice in visibleModes, while the round-3 tree keeps the complete enabled list and uses width limits only for the visible window. I kept the uncapped list and the 5-desktop/3-phone window. The branch's older Appearance schema also put Location inside auto_scheme; the current tree has a dedicated Location API. I preserved that API and added the branch's separate account-scoped mode-order endpoint and hostile-input probe.

Finding during `tray-order` conflict resolution: this branch stores a five-item slice in `visibleModes`, while the round-3 tree keeps the complete enabled list and uses width limits only for the visible window. I kept the uncapped list and the 5-desktop/3-phone window. The branch's older Appearance schema also put Location inside `auto_scheme`; the current tree has a dedicated Location API. I preserved that API and added the branch's separate account-scoped mode-order endpoint and hostile-input probe.
Author
Owner

The title-plain-526 merge conflicted in the layout sweep, Search e2e and Photos view. I kept the round-3 Mail/Money fixture path and added the header-only Photos fixture path; Search now checks that Calendar Week content loads while the root title has no view menu; Photos uses the date as its ModeHeader title with breadcrumbs and retains the Backgrounds folder view.

The `title-plain-526` merge conflicted in the layout sweep, Search e2e and Photos view. I kept the round-3 Mail/Money fixture path and added the header-only Photos fixture path; Search now checks that Calendar Week content loads while the root title has no view menu; Photos uses the date as its ModeHeader title with breadcrumbs and retains the Backgrounds folder view.
Author
Owner

In align-538, the Files alignment changes overlapped the #448 selection-column collapse and #527 reduced-motion handling. I kept the selection column dynamic (zero until selection marks are visible), used the shared icon-size token inside IconLabel, and retained motionDurationMs in both shared sidebar rows. The Files e2e keeps its selection-only and keyboard flows and now also runs the alignment matrix in the normal production run.

In `align-538`, the Files alignment changes overlapped the #448 selection-column collapse and #527 reduced-motion handling. I kept the selection column dynamic (zero until selection marks are visible), used the shared icon-size token inside IconLabel, and retained `motionDurationMs` in both shared sidebar rows. The Files e2e keeps its selection-only and keyboard flows and now also runs the alignment matrix in the normal production run.
Author
Owner

In the Search count merge, I combined the #544 palette settle check with the existing #427 ResizeObserver-height wait. This preserves both the Show all animation check and the later measured-height check. I also retained the #483 stable destination annotation beside the new accessible group/count row naming, and kept both the Tab Bar and Search Index benchmark report checks.

In the Search count merge, I combined the #544 palette settle check with the existing #427 ResizeObserver-height wait. This preserves both the Show all animation check and the later measured-height check. I also retained the #483 stable destination annotation beside the new accessible group/count row naming, and kept both the Tab Bar and Search Index benchmark report checks.
Author
Owner

The Toast benchmark conflicted with the shared report and runner. I retained the Tab Bar, Search Index, Calendar attachment and path-menu profiles, then added the Undo-ring average and burst profile. The generated local report had Markdown hard-wrap whitespace that failed git diff --check; I removed only those trailing spaces before committing.

The Toast benchmark conflicted with the shared report and runner. I retained the Tab Bar, Search Index, Calendar attachment and path-menu profiles, then added the Undo-ring average and burst profile. The generated local report had Markdown hard-wrap whitespace that failed `git diff --check`; I removed only those trailing spaces before committing.
Author
Owner

In the Calendar preview merge, #421's append-attachment route overlapped #427's Missing-link removal route. I kept both routes, both Journal client calls, and both ItemPreview callbacks. The MCP allowlist now includes the existing Mail Reader tool plus the new duplicate and Journal-attachment tools. The API contract and generated client remain for the requested final regeneration after the ordered merges.

In the Calendar preview merge, #421's append-attachment route overlapped #427's Missing-link removal route. I kept both routes, both Journal client calls, and both ItemPreview callbacks. The MCP allowlist now includes the existing Mail Reader tool plus the new duplicate and Journal-attachment tools. The API contract and generated client remain for the requested final regeneration after the ordered merges.
Author
Owner

Analytics merge: kept the #504 chart hover/performance helpers and cursor-position tests alongside #381 responsive heatmap screenshots and #421 shared tooltip positioning tests. The merged harness exposes both --layout-screenshots-only and --perf-only paths. The resolved merge is committed as 9ba68beb3; git diff --check passed.

Analytics merge: kept the #504 chart hover/performance helpers and cursor-position tests alongside #381 responsive heatmap screenshots and #421 shared tooltip positioning tests. The merged harness exposes both --layout-screenshots-only and --perf-only paths. The resolved merge is committed as 9ba68beb3; `git diff --check` passed.
Author
Owner

Drag-snap merge: resolved Calendar overlaps by keeping the #421 Cmd+I anchored-preview helper beside #536 dragging settings helpers; preserved both provider-zone composer iCalendar updates and drag-only non-recurring move adapters; kept conditional DELETE and ETag-conditional PUT handlers in the CalDAV fixture. The bench help text covers both toast and snap profiles. Merge commit: b06402852. git diff --check passed.

Drag-snap merge: resolved Calendar overlaps by keeping the #421 Cmd+I anchored-preview helper beside #536 dragging settings helpers; preserved both provider-zone composer iCalendar updates and drag-only non-recurring move adapters; kept conditional DELETE and ETag-conditional PUT handlers in the CalDAV fixture. The bench help text covers both toast and snap profiles. Merge commit: b06402852. `git diff --check` passed.
Author
Owner

Settings-shortcut merge: combined host-specific browser fallbacks, screen-reader descriptions, IconLabel rows and reduced-motion opening behavior. I retained Cmd+I for the inspector because DESIGN §34 assigns that binding; the conflicting branch value used Cmd+Shift+Period. I also kept #526 plain mode titles and sidebar sub-views, so no title menu was restored. Commit: df47b06f4. git diff --cached --check passed.

Settings-shortcut merge: combined host-specific browser fallbacks, screen-reader descriptions, IconLabel rows and reduced-motion opening behavior. I retained Cmd+I for the inspector because DESIGN §34 assigns that binding; the conflicting branch value used Cmd+Shift+Period. I also kept #526 plain mode titles and sidebar sub-views, so no title menu was restored. Commit: df47b06f4. `git diff --cached --check` passed.
Author
Owner

Background-flash/user-storage merge: kept the existing first-paint mode-order store while placing its browser persistence behind userStorage; joined synchronous session cleanup with mode-order invalidation; added dual-name support for the validated prebuilt media runtime. Removed one extra blank EOF line found by git diff --cached --check. Commit: b650681c8.

Background-flash/user-storage merge: kept the existing first-paint mode-order store while placing its browser persistence behind userStorage; joined synchronous session cleanup with mode-order invalidation; added dual-name support for the validated prebuilt media runtime. Removed one extra blank EOF line found by `git diff --cached --check`. Commit: b650681c8.
Author
Owner

Tasks merge: retained Task anchor metrics and test output beside the Tab Bar, Search Index, toast, attachment, and Analytics reporting. Merged the Task profile into bench/run.sh, preserved both measured baseline workloads via a no-conflict recursive merge of the two JSON sides, and kept the adversarial reconciliation uniqueness probe. Commit: d8e93b4bc. git diff --check passed.

Tasks merge: retained Task anchor metrics and test output beside the Tab Bar, Search Index, toast, attachment, and Analytics reporting. Merged the Task profile into `bench/run.sh`, preserved both measured baseline workloads via a no-conflict recursive merge of the two JSON sides, and kept the adversarial reconciliation uniqueness probe. Commit: d8e93b4bc. `git diff --check` passed.
Author
Owner

Notes bridge merge: combined CalDAV allowed-network configuration with the optional IMAP listener; kept the #391 Saved-place projection, #531 task indexing and #428 IMAP change notifications. The notes plugin migrations overlap: kept #391 as migration 20 and renumbered the three Notes IMAP migrations to 21–23 with matching filenames. Cargo.lock now contains both location/embed and IMAP dependency trees (861 packages; TOML parse passed). Commit: f99a49124.

Notes bridge merge: combined CalDAV allowed-network configuration with the optional IMAP listener; kept the #391 Saved-place projection, #531 task indexing and #428 IMAP change notifications. The notes plugin migrations overlap: kept #391 as migration 20 and renumbered the three Notes IMAP migrations to 21–23 with matching filenames. Cargo.lock now contains both location/embed and IMAP dependency trees (861 packages; TOML parse passed). Commit: f99a49124.
Author
Owner

Merged origin/job/parity-484 as 231012153. Conflict resolution keeps #428 duplicate/Journal attachment tools, #431 Calendar tools, #555 session hints, and #391 Location docs alongside generated WebMCP actions. The e2e registration count is 25 fixed tools plus generated registry names; the parity branch's baseline of 22 omitted three tools merged earlier in this round. I used the parity branch's current exception schema because its checker accepts route-specific presentation/onboarding exclusions, while the older file held retired generated pending lists. I will regenerate the matrix from the merged contract after the last branch.

Merged origin/job/parity-484 as 231012153. Conflict resolution keeps #428 duplicate/Journal attachment tools, #431 Calendar tools, #555 session hints, and #391 Location docs alongside generated WebMCP actions. The e2e registration count is 25 fixed tools plus generated registry names; the parity branch's baseline of 22 omitted three tools merged earlier in this round. I used the parity branch's current exception schema because its checker accepts route-specific presentation/onboarding exclusions, while the older file held retired generated pending lists. I will regenerate the matrix from the merged contract after the last branch.
Author
Owner

Merged origin/job/crash-525 as 70c6aa464. The parity dispatch task and #525 worker-stack headroom are complementary: the dispatch boundary cancels handler futures with the request and removes outer Tower frames; the 4 MiB worker stack covers the remaining synchronous filesystem/Tower frames. I kept both, with their cancellation test and cross-source rename storm/profile. Resolved docs/perf/baseline.json by combining its non-overlapping task/toast and Tag rename metrics; run.sh's conflict was only the profile-sampling comment.

Merged origin/job/crash-525 as 70c6aa464. The parity dispatch task and #525 worker-stack headroom are complementary: the dispatch boundary cancels handler futures with the request and removes outer Tower frames; the 4 MiB worker stack covers the remaining synchronous filesystem/Tower frames. I kept both, with their cancellation test and cross-source rename storm/profile. Resolved docs/perf/baseline.json by combining its non-overlapping task/toast and Tag rename metrics; run.sh's conflict was only the profile-sampling comment.
Author
Owner

Build finding: cargo run -p calternal-server -- openapi stopped while compiling calternal-plugin: error[E0063] missing field default_user_enabled in initializer of PluginAccessState at crates/calternal-plugin/src/lib.rs:1362. I am checking the new field’s existing fixture values before applying the minimal fix.

Build finding: cargo run -p calternal-server -- openapi stopped while compiling calternal-plugin: error[E0063] missing field default_user_enabled in initializer of PluginAccessState at crates/calternal-plugin/src/lib.rs:1362. I am checking the new field’s existing fixture values before applying the minimal fix.
Author
Owner

Build finding: server contract generation then stopped in crates/plugins/notes/src/lib.rs because build_new_day_file is imported from calternal_notes_core and also defined locally at line 1299. I am comparing both implementations to keep the shared Notes Core invariant without changing Note behavior.

Build finding: server contract generation then stopped in crates/plugins/notes/src/lib.rs because build_new_day_file is imported from calternal_notes_core and also defined locally at line 1299. I am comparing both implementations to keep the shared Notes Core invariant without changing Note behavior.
Author
Owner

Follow-up build finding after the Notes helper fix: calternal-server has stale call sites after the PluginState API change. notes_imap.rs:235 calls is_enabled with two arguments, but the current method needs default_user_enabled and user_id. In mcp.rs, AppPasswordAccess::Read/Write call sites do not match call_api and the type is not imported for production code. I am tracing the access guard and plugin defaults so the fixes preserve current authorization.

Follow-up build finding after the Notes helper fix: calternal-server has stale call sites after the PluginState API change. notes_imap.rs:235 calls is_enabled with two arguments, but the current method needs default_user_enabled and user_id. In mcp.rs, AppPasswordAccess::Read/Write call sites do not match call_api and the type is not imported for production code. I am tracing the access guard and plugin defaults so the fixes preserve current authorization.
Author
Owner

Contract freshness finding: rebuilding calternal-server openapi from the merged sources added four operations missing from the previous generation: GET/PUT /api/v1/preferences/mode-order, POST /api/v1/calendar/items/duplicate, and POST /api/v1/notes/journal/entries/{block_id}/attachments. Regeneration now reports 333 registry operations, 315 generated tools, and 0 parity adapter gaps. I am rerunning the clean-generation check against the rebuilt server binary.

Contract freshness finding: rebuilding calternal-server openapi from the merged sources added four operations missing from the previous generation: GET/PUT /api/v1/preferences/mode-order, POST /api/v1/calendar/items/duplicate, and POST /api/v1/notes/journal/entries/{block_id}/attachments. Regeneration now reports 333 registry operations, 315 generated tools, and 0 parity adapter gaps. I am rerunning the clean-generation check against the rebuilt server binary.
Author
Owner

Web gate finding: the userStorage source guard found two direct localStorage calls for the saved Settings destination in apps/web/src/routes/+layout.svelte. Both now use the per-User userStorage facade, so Settings history stays isolated across Users. Verification: bun run --cwd apps/web check passed with 0 Svelte errors and 0 warnings; bun run --cwd apps/web test passed 148 files / 1011 tests.

Web gate finding: the userStorage source guard found two direct localStorage calls for the saved Settings destination in apps/web/src/routes/+layout.svelte. Both now use the per-User userStorage facade, so Settings history stays isolated across Users. Verification: bun run --cwd apps/web check passed with 0 Svelte errors and 0 warnings; bun run --cwd apps/web test passed 148 files / 1011 tests.
Author
Owner

Round 4 E2E progress: the merged Files suite exposed three harness issues before its device/theme matrix could complete. files.mjs redeclared the shared pdfFixture, so the duplicate was removed in favor of the existing harness helper. setThemeContext() relied on a userStorage test seam without installing it; the helper now installs the seam. The phone touch probe targeted a folder row overlapped by the floating header (tap coordinate 195,38); scrolling the row to the center before dispatching touch made the probe hit the intended target. The Files keyboard and rename flow now passes at phone, tablet, and desktop widths in both themes. Screenshot cropping also needed the host Nix libstdc++.so.6 directory in LD_LIBRARY_PATH; sharp then loaded successfully. The full Files suite is continuing through selection, shared, Recent/Trash, and remaining checks.

Round 4 E2E progress: the merged Files suite exposed three harness issues before its device/theme matrix could complete. `files.mjs` redeclared the shared `pdfFixture`, so the duplicate was removed in favor of the existing harness helper. `setThemeContext()` relied on a userStorage test seam without installing it; the helper now installs the seam. The phone touch probe targeted a folder row overlapped by the floating header (tap coordinate 195,38); scrolling the row to the center before dispatching touch made the probe hit the intended target. The Files keyboard and rename flow now passes at phone, tablet, and desktop widths in both themes. Screenshot cropping also needed the host Nix `libstdc++.so.6` directory in `LD_LIBRARY_PATH`; `sharp` then loaded successfully. The full Files suite is continuing through selection, shared, Recent/Trash, and remaining checks.
Author
Owner

Further Files E2E evidence: the Recent/Trash phone flow asserted that a long-press showed .marks-visible, then asserted it was hidden before invoking Done. I moved the existing Done action before the hidden-marks assertion, preserving the true and false expectations and adding a short comment for the state transition. The phone row is centered before touch dispatch. The isolated Files selection matrix then passed at phone, tablet, and desktop in both themes, with zero CSP reports. A separate loaded full run timed out resolving the alpha row after a select-all toggle; the focused selection matrix reproduced the state at all six configurations and the row remained present with aria-selected=false, so this timeout did not reproduce under the focused run.

Further Files E2E evidence: the Recent/Trash phone flow asserted that a long-press showed `.marks-visible`, then asserted it was hidden before invoking Done. I moved the existing Done action before the hidden-marks assertion, preserving the `true` and `false` expectations and adding a short comment for the state transition. The phone row is centered before touch dispatch. The isolated Files selection matrix then passed at phone, tablet, and desktop in both themes, with zero CSP reports. A separate loaded full run timed out resolving the alpha row after a select-all toggle; the focused selection matrix reproduced the state at all six configurations and the row remained present with `aria-selected=false`, so this timeout did not reproduce under the focused run.
Author
Owner

Files E2E finding (performance-only): the full production-build run passed the phone/tablet/desktop selection, keyboard rename, Recent/Trash evidence, drag-to-pin, and breadcrumb drop flows with 0 CSP reports across 33 pages. It stopped at the existing request-coalescing assertion after the breadcrumb move/reload scenario: GET /api/v1/files/entries?path=Inbox&limit=500&sort=name occurred twice. The list remained usable and the preceding move/stat checks passed. I kept the existing expected request count unchanged. This is a duplicate read only; it is not a crash, 5xx, data loss, authorization or security issue.

Files E2E finding (performance-only): the full production-build run passed the phone/tablet/desktop selection, keyboard rename, Recent/Trash evidence, drag-to-pin, and breadcrumb drop flows with 0 CSP reports across 33 pages. It stopped at the existing request-coalescing assertion after the breadcrumb move/reload scenario: `GET /api/v1/files/entries?path=Inbox&limit=500&sort=name` occurred twice. The list remained usable and the preceding move/stat checks passed. I kept the existing expected request count unchanged. This is a duplicate read only; it is not a crash, 5xx, data loss, authorization or security issue.
Author
Owner

Search E2E finding (merge contract mismatch): after switching its fixture User into Money and using the shared #555 auth/theme setup, the #545 indexing indicator checks passed at 390, 820 and 1440 px in both themes with no CSP reports. The suite then timed out waiting for Navigate count 7; its expected title list is Calendar, Files, Photos, Mail, Analytics, Money and Ask. The merged PLUGIN_NAVIGATION registry also contains Notes, and Notes is a non-toggleable Core mode, so SearchAccess includes Notes for this User. With Money enabled, the Search provider can expose eight enabled mode rows. I did not change the existing assertion or hide Notes in runtime code because that would resolve a design conflict between #34 and #544 beyond this merge's authorized behavior. This is not a 5xx, crash, authorization or isolation finding; the full Search E2E remains blocked on whether Notes belongs in this Navigate group.

Search E2E finding (merge contract mismatch): after switching its fixture User into Money and using the shared #555 auth/theme setup, the #545 indexing indicator checks passed at 390, 820 and 1440 px in both themes with no CSP reports. The suite then timed out waiting for Navigate count `7`; its expected title list is Calendar, Files, Photos, Mail, Analytics, Money and Ask. The merged `PLUGIN_NAVIGATION` registry also contains Notes, and Notes is a non-toggleable Core mode, so SearchAccess includes Notes for this User. With Money enabled, the Search provider can expose eight enabled mode rows. I did not change the existing assertion or hide Notes in runtime code because that would resolve a design conflict between #34 and #544 beyond this merge's authorized behavior. This is not a 5xx, crash, authorization or isolation finding; the full Search E2E remains blocked on whether Notes belongs in this Navigate group.
Author
Owner

Calendar E2E finding: the real event opens in the composer as 09:00 - 10:30 Morning review #area/work, while the existing expectation at apps/web/e2e/calendar.mjs:1231 and its keyboard-reopen check expects 09:00 - 10:30 Morning review #work. I left the expected text and the nested tag value unchanged. The run stopped at this assertion before the remaining Calendar flow.

Calendar E2E finding: the real event opens in the composer as `09:00 - 10:30 Morning review #area/work`, while the existing expectation at `apps/web/e2e/calendar.mjs:1231` and its keyboard-reopen check expects `09:00 - 10:30 Morning review #work`. I left the expected text and the nested tag value unchanged. The run stopped at this assertion before the remaining Calendar flow.
Author
Owner

Finding (reload-safe boot #555 E2E): the setup navigated User A to /money and waited for the real empty-budget state, but Money is a User opt-in and the new User started with it disabled. The initial run timed out at that wait. I added the same explicit PUT /api/v1/plugins/money/me {enabled:true} setup used by Search #544, preserving the empty-state and isolation assertions; I am rerunning the isolation E2E now.

Finding (reload-safe boot #555 E2E): the setup navigated User A to `/money` and waited for the real empty-budget state, but Money is a User opt-in and the new User started with it disabled. The initial run timed out at that wait. I added the same explicit `PUT /api/v1/plugins/money/me {enabled:true}` setup used by Search #544, preserving the empty-state and isolation assertions; I am rerunning the isolation E2E now.
Author
Owner

Finding (Analytics #504 E2E): the real seeded Analytics run reported one KPI value/delta-chip intersection at 390 px (Total tracked, 313h 40m). Its 1440 px and 1024 px checks passed. Chart-render profiling also observed CPU tasks over 50 ms during range changes (worst 117 ms); this is SLOW-only load evidence, not a merge blocker under the performance rule. The run then stopped during the screenshot material matrix because its fresh BrowserContext did not install the #555 userStorage test seam before setTheme read it. I will fix that fixture setup and rerun; I am preserving the layout assertion while investigating the 390 px intersection.

Finding (Analytics #504 E2E): the real seeded Analytics run reported one KPI value/delta-chip intersection at 390 px (`Total tracked`, `313h 40m`). Its 1440 px and 1024 px checks passed. Chart-render profiling also observed CPU tasks over 50 ms during range changes (worst 117 ms); this is SLOW-only load evidence, not a merge blocker under the performance rule. The run then stopped during the screenshot material matrix because its fresh BrowserContext did not install the #555 `userStorage` test seam before `setTheme` read it. I will fix that fixture setup and rerun; I am preserving the layout assertion while investigating the 390 px intersection.
Author
Owner

Analytics follow-up: after adding 2 px of float clearance, a production-browser probe seeded 23 real 14-hour Log entries plus the indexed prior-year Daily Note and reran the same 390 px range-box calculation. It returned [] for KPI/chip intersections. bun apps/web/e2e/analytics.mjs --screenshots ... --screenshots-only --material-matrix passed and produced the Paper/Noir color/photo screenshot matrix at 390, 820 and 1440 px plus hover crops. The full interaction run later timed out waiting for a time-of-day heatmap mark at 30 s while the server logged 4–6.5 s SQLite pool waits; classify that run as SLOW host load. The trace also measured render CPU tasks up to 91 ms; logged as performance follow-up, not a merge gate.

Analytics follow-up: after adding 2 px of float clearance, a production-browser probe seeded 23 real 14-hour Log entries plus the indexed prior-year Daily Note and reran the same 390 px range-box calculation. It returned `[]` for KPI/chip intersections. `bun apps/web/e2e/analytics.mjs --screenshots ... --screenshots-only --material-matrix` passed and produced the Paper/Noir color/photo screenshot matrix at 390, 820 and 1440 px plus hover crops. The full interaction run later timed out waiting for a time-of-day heatmap mark at 30 s while the server logged 4–6.5 s SQLite pool waits; classify that run as SLOW host load. The trace also measured render CPU tasks up to 91 ms; logged as performance follow-up, not a merge gate.
Author
Owner

Finding (Tasks #531 screenshot E2E): it stopped before the first screenshot in seedThemeInDocument because each fresh screenshot BrowserContext lacked the #555 window.__userStorageTest seam. I will install the shared seam before navigating to /readyz, then rerun the same six phone/tablet/desktop light/dark captures; Task contents and overflow assertions remain unchanged.

Finding (Tasks #531 screenshot E2E): it stopped before the first screenshot in `seedThemeInDocument` because each fresh screenshot BrowserContext lacked the #555 `window.__userStorageTest` seam. I will install the shared seam before navigating to `/readyz`, then rerun the same six phone/tablet/desktop light/dark captures; Task contents and overflow assertions remain unchanged.
Author
Owner

Tasks #531 E2E follow-up: the #555 theme-seam setup works and the suite now reaches the phone sheet. At 390 px, .sheet-title is All-day items for Thursday, 1 October; the body heading and unchanged expectation are All-day items. The dialog's accessible label already retains the full date. PopoverSurface has an explicit sheetTitle prop, but this call site does not set it. I will pass the concise body heading as sheetTitle, preserving the dated dialog label and all test expectations.

Tasks #531 E2E follow-up: the #555 theme-seam setup works and the suite now reaches the phone sheet. At 390 px, `.sheet-title` is `All-day items for Thursday, 1 October`; the body heading and unchanged expectation are `All-day items`. The dialog's accessible label already retains the full date. `PopoverSurface` has an explicit `sheetTitle` prop, but this call site does not set it. I will pass the concise body heading as `sheetTitle`, preserving the dated dialog label and all test expectations.
Author
Owner

Tasks #531 follow-up: bun apps/web/e2e/calendar-task-overflow.mjs --screenshots artifacts/merge-r4/tasks passed after setting the explicit concise phone-sheet title. It saved all six real production captures: 390, 820 and 1440 px in light and dark. The suite verified each Task appears once and the date remains in the accessible dialog label. No test expectation changed.

Tasks #531 follow-up: `bun apps/web/e2e/calendar-task-overflow.mjs --screenshots artifacts/merge-r4/tasks` passed after setting the explicit concise phone-sheet title. It saved all six real production captures: 390, 820 and 1440 px in light and dark. The suite verified each Task appears once and the date remains in the accessible dialog label. No test expectation changed.
Author
Owner

Finding (Toast #539 × keyboard motion #527): a focused toast remains mounted and live (data-removed=false, 4000 ms ring duration), but after F6 its ring has animation-name:none and animation-play-state:running. The matched #527 rule from packages/ui/src/tokens.css applies animation:none !important to every descendant when data-input="keyboard"; it cancels the ring's timer visualization before the #539 focus pause can work. Reduced motion is false and the ring selector matches. I will exempt this timer indicator while retaining its focus/page-hidden pause rules; the E2E assertion remains unchanged.

Finding (Toast #539 × keyboard motion #527): a focused toast remains mounted and live (`data-removed=false`, 4000 ms ring duration), but after F6 its ring has `animation-name:none` and `animation-play-state:running`. The matched #527 rule from `packages/ui/src/tokens.css` applies `animation:none !important` to every descendant when `data-input="keyboard"`; it cancels the ring's timer visualization before the #539 focus pause can work. Reduced motion is false and the ring selector matches. I will exempt this timer indicator while retaining its focus/page-hidden pause rules; the E2E assertion remains unchanged.
Author
Owner

Toast follow-up: the keyboard-mode CSS override now makes the original animation-play-state === 'paused' assertion pass. The focused smoke found a one-frame settling edge: the first offset was 6.2483, then the computed-paused animation advanced to 6.6650 (about 17 ms at a 4 s duration) before remaining paused. I will wait two animation frames before taking the unchanged 700 ms baseline; the <0.1 stability assertion stays intact.

Toast follow-up: the keyboard-mode CSS override now makes the original `animation-play-state === 'paused'` assertion pass. The focused smoke found a one-frame settling edge: the first offset was 6.2483, then the computed-paused animation advanced to 6.6650 (about 17 ms at a 4 s duration) before remaining paused. I will wait two animation frames before taking the unchanged 700 ms baseline; the `<0.1` stability assertion stays intact.
Author
Owner

Toast #539/#527 follow-up: the keyboard input-modality exemption preserves the live ring while focus and hidden-page states still pause it. The full bun apps/web/e2e/toast-ring.mjs --screenshots ... run passed: geometry at 390/820/1440 in Paper and Tokyo Night, 0/25/75% drain frames, hover pause/resume, F6 pause, Escape dismissal, and the Undo toast's observed 8398 ms lifetime. The unchanged 700 ms offset check now starts after two animation frames.

Toast #539/#527 follow-up: the keyboard input-modality exemption preserves the live ring while focus and hidden-page states still pause it. The full `bun apps/web/e2e/toast-ring.mjs --screenshots ...` run passed: geometry at 390/820/1440 in Paper and Tokyo Night, 0/25/75% drain frames, hover pause/resume, F6 pause, Escape dismissal, and the Undo toast's observed 8398 ms lifetime. The unchanged 700 ms offset check now starts after two animation frames.
Author
Owner

Settings shortcut #541 E2E follow-up: the seeded Note loads with its title and body, but this local server falls back to the documented Markdown editor (Not live: changes save when you pause). That editor has the Note's role="textbox" and accessible name, but no .cal-prose class; the live editor uses .cal-prose. The timeout is therefore a selector mismatch, not a missing Note. I will target the shared accessible Note textbox and keep the comma-content and no-Settings assertions unchanged.

Settings shortcut #541 E2E follow-up: the seeded Note loads with its title and body, but this local server falls back to the documented Markdown editor (`Not live: changes save when you pause`). That editor has the Note's `role="textbox"` and accessible name, but no `.cal-prose` class; the live editor uses `.cal-prose`. The timeout is therefore a selector mismatch, not a missing Note. I will target the shared accessible Note textbox and keep the comma-content and no-Settings assertions unchanged.
Author
Owner

Settings shortcut E2E: two test setup issues surfaced in the production build. On phone width, the open Navigation Dialog owns the first Escape and closes before Settings, so the helper now follows the actual overlay stack before continuing; the existing product assertions remain unchanged. The Note typing guard now locates the accessible Markdown fallback editor. Chromium 1.63's bundled browser blocked the real local collaboration WebSocket with ERR_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS; the E2E now grants local-network-access only to its throwaway localhost origin so it exercises the server-backed editor and can retain the console-error assertion. Both are test setup changes, not product behavior changes.

Settings shortcut E2E: two test setup issues surfaced in the production build. On phone width, the open Navigation Dialog owns the first Escape and closes before Settings, so the helper now follows the actual overlay stack before continuing; the existing product assertions remain unchanged. The Note typing guard now locates the accessible Markdown fallback editor. Chromium 1.63's bundled browser blocked the real local collaboration WebSocket with `ERR_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS`; the E2E now grants `local-network-access` only to its throwaway localhost origin so it exercises the server-backed editor and can retain the console-error assertion. Both are test setup changes, not product behavior changes.
Author
Owner

Adversarial round update: the admin authorization matrix passed all 2,071 requests over the 335 OpenAPI operations, including all 39 admin operations. The two-User matrix stopped during fixture setup: xuser_matrix.py creates a Money budget for User A without enabling the optional Money plugin; #555 moved plugin availability to a per-User setting, so the fixture receives HTTP 404 before isolation assertions run. I will repair the fixture setup and run that focused matrix. Search chaos also reports admin_rebuild_search_index as expected 200 / actual 202; the route already documented and returned 202 on the cc25c441b base, so I left the old expectation unchanged. The runner's nested Calendar/Mail/Money browser scripts also could not find the custom-target release server because CALTERNAL_SERVER_BIN was not set; the separately requested Calendar and Money web E2Es already passed or have their own recorded outcome. The appearance 3 MiB proxy probe returned the proxy's synthetic 502; I am checking it against the direct-backend result and host load before classifying it.

Adversarial round update: the admin authorization matrix passed all 2,071 requests over the 335 OpenAPI operations, including all 39 admin operations. The two-User matrix stopped during fixture setup: `xuser_matrix.py` creates a Money budget for User A without enabling the optional Money plugin; #555 moved plugin availability to a per-User setting, so the fixture receives HTTP 404 before isolation assertions run. I will repair the fixture setup and run that focused matrix. Search chaos also reports `admin_rebuild_search_index` as expected 200 / actual 202; the route already documented and returned 202 on the `cc25c441b` base, so I left the old expectation unchanged. The runner's nested Calendar/Mail/Money browser scripts also could not find the custom-target release server because `CALTERNAL_SERVER_BIN` was not set; the separately requested Calendar and Money web E2Es already passed or have their own recorded outcome. The appearance 3 MiB proxy probe returned the proxy's synthetic 502; I am checking it against the direct-backend result and host load before classifying it.
Author
Owner

Adversarial isolation follow-up: I verified the Photos shared-timeline mismatch is a false positive in the probe, not cross-User leakage. Its unexpected item 875a63cb-6a35-4906-98f4-46f86f05aefa is A's own Photos/2024/2024-06-02/share-probe.jpg, uploaded after the probe captured A's expected ID list; the database row has owner_id=A, and B's row is the authorized shared view with viewer_id=B. The assertion compares against a stale pre-upload list. Separately, I committed 9927a6ec2 to enable the optional Money Plugin for both Users in xuser_matrix.py, so the required ownership matrix can reach its Money assertions instead of failing on fixture setup.

Adversarial isolation follow-up: I verified the Photos shared-timeline mismatch is a false positive in the probe, not cross-User leakage. Its unexpected item `875a63cb-6a35-4906-98f4-46f86f05aefa` is A's own `Photos/2024/2024-06-02/share-probe.jpg`, uploaded after the probe captured A's expected ID list; the database row has `owner_id=A`, and B's row is the authorized shared view with `viewer_id=B`. The assertion compares against a stale pre-upload list. Separately, I committed `9927a6ec2` to enable the optional Money Plugin for both Users in `xuser_matrix.py`, so the required ownership matrix can reach its Money assertions instead of failing on fixture setup.
Author
Owner

The focused two-User matrix passed after the #555 fixture update. Verbatim summary: Two-User OpenAPI matrix: 335 operations classified; 159 operations replayed; 733 A-ID vs missing-ID comparisons across B, C, D and anonymous; 22 identifier routes classified with no local fixture factory; median absolute timing delta 1.2 ms and Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures. The admin authorization matrix also passed 2,071 requests, including 39 admin operations.

The focused two-User matrix passed after the #555 fixture update. Verbatim summary: `Two-User OpenAPI matrix: 335 operations classified; 159 operations replayed; 733 A-ID vs missing-ID comparisons across B, C, D and anonymous; 22 identifier routes classified with no local fixture factory; median absolute timing delta 1.2 ms` and `Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures`. The admin authorization matrix also passed 2,071 requests, including 39 admin operations.
Author
Owner

Merge round 4 report

Branch: job/merge-round-4
Base: origin/dev at cc25c441b
Head: a6fd7f7040972d476575c06a4c363e21070bb42d

All 16 approved branches were merged in this order: tray-order → kbd-motion-527 → title-plain-526 → align-538 → search-count-544 → toast-539 → preview-421 → analytics-504 → drag-snap-536 → settings-key-541 → bg-flash → tasks-500-531 → perf-494 → notes-bridge → parity-484 → crash-525. No push, deploy, or merge to dev was made. The local merge of origin/dev was already up to date before final gates.

Built and changed

The round combines the Tab Bar ordering and width, keyboard motion, plain titles, IconLabel alignment, Search counts, Toast ring, Calendar preview, Analytics, Calendar drag snapping, Settings shortcut, user-scoped background storage, Tasks, performance profiles, Notes bridge, generated parity registry, and Tag rename stack fix. The contract and API client were regenerated; packages/api-client/check-generated.sh exited 0.

Files: 326 tracked files changed (163 under apps, 62 under crates, 41 under packages, 19 under bench, 18 under docs, 12 under tests, plus contracts, scripts, lockfile, and CI/docs). Highlights: apps/web/src/lib/userStorage.ts, apps/web/src/lib/components/AppToaster.svelte, apps/web/src/routes/calendar/[view]/[date]/+page.svelte, packages/ui/src/components/calendar/TimeGrid.svelte, packages/api-client/src/generated.ts, contracts/openapi.json, contracts/actions.json, and the Notes IMAP/Tasks routes in crates/plugins/notes and crates/calternal-server.

Conflict decisions:

  • Kept the dev removal of the five-tab cap while sizing the Tab Bar to min(enabled, 5) on desktop and three tabs on phones.
  • Kept both Tag rename stack safeguards: API dispatch isolation and handler stack headroom address separate layers.
  • Added the minimal Plugin/MCP route integration needed by the merged APIs. Regeneration follows that merged contract.
  • The Money matrix explicitly enables the optional Plugin for Users A and B and accepts the route's 204 success response. This changes only adversarial fixture setup.

Gates

cargo fmt --check exited 0 with empty stdout. Per-crate Clippy commands exited 0. The terminal completion lines were:

calternal-api:     Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.81s
calternal-auth:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.75s
calternal-cli:     Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 32s
calternal-imap:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 24s
calternal-notes-core: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 47s
calternal-plugin:  Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 35s
calternal-plugin-calendar: Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 05s
calternal-plugin-files: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 43s
calternal-plugin-money: Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.84s
calternal-plugin-notes: Finished `dev` profile [unoptimized + debuginfo] target(s) in 57.87s
calternal-server: Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 41s

cargo build --release -p calternal-server completed with:

Finished `release` profile [optimized] target(s) in 24m 37s

cargo test passed for every changed crate. Verbatim test result lines retained in the gate logs:

calternal-auth: test result: ok. 65 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 35.01s
calternal-cli: test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.76s
calternal-cli: test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.09s
calternal-imap: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
calternal-imap: test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
calternal-imap: test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
calternal-imap: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
calternal-imap: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
calternal-imap: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
calternal-notes-core: test result: ok. 519 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s
calternal-notes-core: test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.68s
calternal-notes-core: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
calternal-notes-core: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s
calternal-notes-core: test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
calternal-plugin: test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.23s
calternal-plugin-calendar: test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.57s
calternal-plugin-calendar: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
calternal-plugin-calendar: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s
calternal-plugin-files: test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 205.33s
calternal-plugin-money: test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.27s
calternal-plugin-notes: test result: ok. 162 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 73.18s
calternal-plugin-notes: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s
calternal-server: test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 78.45s

calternal-api also passed: 9 unit tests and 0 doc tests. Its direct gate output was not retained in the sidecar logs.

bun run check output:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-r4/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Final bun run test output:

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/merge-r4/apps/web

 Test Files  148 passed (148)
      Tests  1011 passed (1011)
   Start at  07:28:54
   Duration  177.27s (transform 50%, environment 21%, import 13%, tests 12%, setup 4%)

E2E and security results

  • Calendar preview #421 passed interactions and screenshots. Task overflow passed at 390/820/1440 in both themes. Toast ring passed, including keyboard mode and an 8,398 ms Undo lifetime. Settings shortcut #541 passed. Reload-safe boot #555 passed signout, expired-live, and expired-document transitions.
  • Files E2E stops on its unchanged request-count assertion: it observed two GET /api/v1/files/entries?path=Inbox&limit=500&sort=name calls and expected none.
  • Search E2E stops on its unchanged group-count assertion: the live Navigation has eight groups (Notes plus enabled Money); the fixture expects seven.
  • Calendar E2E stops on its unchanged tag text assertion: actual 09:00 - 10:30 Morning review #area/work, expected ... #work. The drag snap interaction ran before that assertion.
  • Analytics screenshots cover phone/tablet/desktop and both themes; the 390 px overlap was fixed and smoke-checked. The full interactive Analytics E2E timed out under shared-host load.
  • The Two-User matrix passed: 335 operations classified, 159 replayed, 733 A-ID/missing-ID comparisons, 97 ownership comparisons, zero denial failures. The Admin authorization matrix passed 2,071 requests over 335 OpenAPI operations, including all 39 admin operations.
  • Tag rename storm passed 100/100 operations with no Tokio stack overflow. Hostile-bytes findings: 0. The server was alive at the end. Encoded %2e%2e segments and a*b were literal folder names; actual traversal and reserved-name cases were rejected.
  • The full tests/adversarial/run.sh invocation was red, so the round is not green. Its Search chaos fixture expects the staged rebuild route to return 200; the route returns 202, which is also the behavior on base cc25c441b. The Money 404 in the first matrix attempt was fixture opt-in; after enabling Money for A and B and accepting 204, the focused matrix passed.
  • The main adversarial run supplied the release server path, which skips run.sh's debug build. Nested Calendar/Mail/Money checks could not find the debug server, and the sync section lacked calternald. A focused ROUND2_SECTIONS=cli,sync run built the debug binaries: sync passed. The CLI probe remains red because API name validation rejects its control-character filenames before listing (only two safe names remain), and its retry assertion expects agent while the fixture user is owner. Existing CLI unit tests cover escaping; I left the adversarial expectations unchanged.
  • The appearance oversized-body probe returned a synthetic 502 from editor-proxy (local adversarial server is unavailable); the direct backend returned the required 413 and the health checks passed. This is a probe/proxy failure, not a product route 5xx.
  • Unresolved non-SLOW findings for owner review: Notes restart probe saw the edit missing once, failed to receive the second stale-epoch report within 20 seconds, saw the body change from 44 to 63 bytes after rebuild, and saw the legacy client sync into the new epoch. This may be data loss and blocks merge until resolved. The adversarial run also reported a Reminders completion not stored, a Notes submission login failure/broken pipe, missing PDF thumbnail, unexpected saved system appearance default, DAV alarm 204 vs expected 201, DAV cross-date move/delete 412 vs expected 204, and Calendar burst expected 120 photos but found 0. These were not changed because the job forbids changing existing expectations and limits behavior changes to merge resolution. SLOW latency/timeouts under concurrent load are recorded in the adversarial log; they are load findings.

Performance

The feature branches include hot-path profiles and recorded results. No new aggregate profile was run for the integrated snapshot; the local host was running concurrent builds and browser jobs. Stored numbers include:

  • Notes 10k import: issue baseline 1,069.7 s upload, p50 104.3 ms, p95 187.0 ms, CPU 1,547.6 s; recorded run 991.0 s, p50 97.98 ms, p95 221.26 ms, CPU 352.4 s.
  • Calendar snap: average p95 13.27 µs; worst p95 70.17 µs. The 49.9 ms Calendar grid frame reference is a different workload, not a direct comparison.
  • Task anchors: 96 tasks/request, average p50 1,151.66 ms and p95 3,896.05 ms; 24-request burst p95 15,748.3 ms.
  • Tag rename: 100 renames, p50 2,529.9 ms, p95 6,741.1 ms, mean RSS 667,215,650 bytes. Toast ring initial profile: p95 118.5 ms at phone and 126.8 ms at desktop; 50-toast burst p95 2,339.8 ms and 1,012 ms respectively.

These measurements are from the recorded feature runs, not a fresh round-4 integrated comparison. Performance is not a merge gate.

Decisions and remaining gaps

No new product behavior decision was needed outside the approved branches. Integration choices are listed above. Do not merge this round yet: resolve the Notes restart/data-integrity finding and review the other non-SLOW adversarial findings, then repair the stale/unprovisioned E2E fixtures without changing their existing assertions.

Screenshots are attached (Money captures were excluded): Files selection, Files Recent, feature E2Es, Calendar preview cards, Calendar week hover, User-storage appearance/login.

Cleanup: cargo clean reported Removed 33908 files, 19.8GiB total; apps/web/build was removed. Worktree is clean. No push or deploy was made.

## Merge round 4 report Branch: `job/merge-round-4` Base: `origin/dev` at `cc25c441b` Head: `a6fd7f7040972d476575c06a4c363e21070bb42d` All 16 approved branches were merged in this order: `tray-order` → `kbd-motion-527` → `title-plain-526` → `align-538` → `search-count-544` → `toast-539` → `preview-421` → `analytics-504` → `drag-snap-536` → `settings-key-541` → `bg-flash` → `tasks-500-531` → `perf-494` → `notes-bridge` → `parity-484` → `crash-525`. No push, deploy, or merge to `dev` was made. The local merge of `origin/dev` was already up to date before final gates. ### Built and changed The round combines the Tab Bar ordering and width, keyboard motion, plain titles, IconLabel alignment, Search counts, Toast ring, Calendar preview, Analytics, Calendar drag snapping, Settings shortcut, user-scoped background storage, Tasks, performance profiles, Notes bridge, generated parity registry, and Tag rename stack fix. The contract and API client were regenerated; `packages/api-client/check-generated.sh` exited 0. Files: 326 tracked files changed (163 under `apps`, 62 under `crates`, 41 under `packages`, 19 under `bench`, 18 under `docs`, 12 under `tests`, plus contracts, scripts, lockfile, and CI/docs). Highlights: `apps/web/src/lib/userStorage.ts`, `apps/web/src/lib/components/AppToaster.svelte`, `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, `packages/ui/src/components/calendar/TimeGrid.svelte`, `packages/api-client/src/generated.ts`, `contracts/openapi.json`, `contracts/actions.json`, and the Notes IMAP/Tasks routes in `crates/plugins/notes` and `crates/calternal-server`. Conflict decisions: - Kept the `dev` removal of the five-tab cap while sizing the Tab Bar to `min(enabled, 5)` on desktop and three tabs on phones. - Kept both Tag rename stack safeguards: API dispatch isolation and handler stack headroom address separate layers. - Added the minimal Plugin/MCP route integration needed by the merged APIs. Regeneration follows that merged contract. - The Money matrix explicitly enables the optional Plugin for Users A and B and accepts the route's 204 success response. This changes only adversarial fixture setup. ### Gates `cargo fmt --check` exited 0 with empty stdout. Per-crate Clippy commands exited 0. The terminal completion lines were: ```text calternal-api: Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.81s calternal-auth: Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.75s calternal-cli: Finished `dev` profile [unoptimized + debuginfo] target(s) in 7m 32s calternal-imap: Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 24s calternal-notes-core: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 47s calternal-plugin: Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 35s calternal-plugin-calendar: Finished `dev` profile [unoptimized + debuginfo] target(s) in 11m 05s calternal-plugin-files: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 43s calternal-plugin-money: Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.84s calternal-plugin-notes: Finished `dev` profile [unoptimized + debuginfo] target(s) in 57.87s calternal-server: Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 41s ``` `cargo build --release -p calternal-server` completed with: ```text Finished `release` profile [optimized] target(s) in 24m 37s ``` `cargo test` passed for every changed crate. Verbatim test result lines retained in the gate logs: ```text calternal-auth: test result: ok. 65 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 35.01s calternal-cli: test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.76s calternal-cli: test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.09s calternal-imap: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s calternal-imap: test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s calternal-imap: test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s calternal-imap: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s calternal-imap: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-imap: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s calternal-notes-core: test result: ok. 519 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s calternal-notes-core: test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.68s calternal-notes-core: test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s calternal-notes-core: test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s calternal-notes-core: test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s calternal-plugin: test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.23s calternal-plugin-calendar: test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.57s calternal-plugin-calendar: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s calternal-plugin-calendar: test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s calternal-plugin-files: test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 205.33s calternal-plugin-money: test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.27s calternal-plugin-notes: test result: ok. 162 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 73.18s calternal-plugin-notes: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s calternal-server: test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 78.45s ``` `calternal-api` also passed: 9 unit tests and 0 doc tests. Its direct gate output was not retained in the sidecar logs. `bun run check` output: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-r4/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Final `bun run test` output: ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/merge-r4/apps/web Test Files 148 passed (148) Tests 1011 passed (1011) Start at 07:28:54 Duration 177.27s (transform 50%, environment 21%, import 13%, tests 12%, setup 4%) ``` ### E2E and security results - Calendar preview #421 passed interactions and screenshots. Task overflow passed at 390/820/1440 in both themes. Toast ring passed, including keyboard mode and an 8,398 ms Undo lifetime. Settings shortcut #541 passed. Reload-safe boot #555 passed signout, expired-live, and expired-document transitions. - Files E2E stops on its unchanged request-count assertion: it observed two `GET /api/v1/files/entries?path=Inbox&limit=500&sort=name` calls and expected none. - Search E2E stops on its unchanged group-count assertion: the live Navigation has eight groups (Notes plus enabled Money); the fixture expects seven. - Calendar E2E stops on its unchanged tag text assertion: actual `09:00 - 10:30 Morning review #area/work`, expected `... #work`. The drag snap interaction ran before that assertion. - Analytics screenshots cover phone/tablet/desktop and both themes; the 390 px overlap was fixed and smoke-checked. The full interactive Analytics E2E timed out under shared-host load. - The Two-User matrix passed: 335 operations classified, 159 replayed, 733 A-ID/missing-ID comparisons, 97 ownership comparisons, zero denial failures. The Admin authorization matrix passed 2,071 requests over 335 OpenAPI operations, including all 39 admin operations. - Tag rename storm passed 100/100 operations with no Tokio stack overflow. Hostile-bytes findings: 0. The server was alive at the end. Encoded `%2e%2e` segments and `a*b` were literal folder names; actual traversal and reserved-name cases were rejected. - The full `tests/adversarial/run.sh` invocation was red, so the round is not green. Its Search chaos fixture expects the staged rebuild route to return 200; the route returns 202, which is also the behavior on base `cc25c441b`. The Money 404 in the first matrix attempt was fixture opt-in; after enabling Money for A and B and accepting 204, the focused matrix passed. - The main adversarial run supplied the release server path, which skips `run.sh`'s debug build. Nested Calendar/Mail/Money checks could not find the debug server, and the sync section lacked `calternald`. A focused `ROUND2_SECTIONS=cli,sync` run built the debug binaries: sync passed. The CLI probe remains red because API name validation rejects its control-character filenames before listing (only two safe names remain), and its retry assertion expects `agent` while the fixture user is `owner`. Existing CLI unit tests cover escaping; I left the adversarial expectations unchanged. - The appearance oversized-body probe returned a synthetic 502 from `editor-proxy` (`local adversarial server is unavailable`); the direct backend returned the required 413 and the health checks passed. This is a probe/proxy failure, not a product route 5xx. - Unresolved non-SLOW findings for owner review: Notes restart probe saw the edit missing once, failed to receive the second stale-epoch report within 20 seconds, saw the body change from 44 to 63 bytes after rebuild, and saw the legacy client sync into the new epoch. This may be data loss and blocks merge until resolved. The adversarial run also reported a Reminders completion not stored, a Notes submission login failure/broken pipe, missing PDF thumbnail, unexpected saved `system` appearance default, DAV alarm 204 vs expected 201, DAV cross-date move/delete 412 vs expected 204, and Calendar burst expected 120 photos but found 0. These were not changed because the job forbids changing existing expectations and limits behavior changes to merge resolution. SLOW latency/timeouts under concurrent load are recorded in the adversarial log; they are load findings. ### Performance The feature branches include hot-path profiles and recorded results. No new aggregate profile was run for the integrated snapshot; the local host was running concurrent builds and browser jobs. Stored numbers include: - Notes 10k import: issue baseline 1,069.7 s upload, p50 104.3 ms, p95 187.0 ms, CPU 1,547.6 s; recorded run 991.0 s, p50 97.98 ms, p95 221.26 ms, CPU 352.4 s. - Calendar snap: average p95 13.27 µs; worst p95 70.17 µs. The 49.9 ms Calendar grid frame reference is a different workload, not a direct comparison. - Task anchors: 96 tasks/request, average p50 1,151.66 ms and p95 3,896.05 ms; 24-request burst p95 15,748.3 ms. - Tag rename: 100 renames, p50 2,529.9 ms, p95 6,741.1 ms, mean RSS 667,215,650 bytes. Toast ring initial profile: p95 118.5 ms at phone and 126.8 ms at desktop; 50-toast burst p95 2,339.8 ms and 1,012 ms respectively. These measurements are from the recorded feature runs, not a fresh round-4 integrated comparison. Performance is not a merge gate. ### Decisions and remaining gaps No new product behavior decision was needed outside the approved branches. Integration choices are listed above. Do not merge this round yet: resolve the Notes restart/data-integrity finding and review the other non-SLOW adversarial findings, then repair the stale/unprovisioned E2E fixtures without changing their existing assertions. Screenshots are attached (Money captures were excluded): [Files selection](https://git.kayg.org/attachments/f11eab3b-b000-4ea0-a2f9-6b5ebd1a5942), [Files Recent](https://git.kayg.org/attachments/416a4cac-a46c-421e-9846-60ab5fd3c4e4), [feature E2Es](https://git.kayg.org/attachments/49e8db26-ce56-4b1a-9e0d-1517d6c6f8d0), [Calendar preview cards](https://git.kayg.org/attachments/600da8db-dbfb-40b2-8862-b28a0f1a8666), [Calendar week hover](https://git.kayg.org/attachments/5997af71-dad3-4681-91db-f423c5a7399b), [User-storage appearance/login](https://git.kayg.org/attachments/e7970c8f-30a6-420c-b23e-667c6ba57c00). Cleanup: `cargo clean` reported `Removed 33908 files, 19.8GiB total`; `apps/web/build` was removed. Worktree is clean. No push or deploy was made.
Author
Owner

Start integrity triage on job/merge-round-4 at a6fd7f7040. Compare cc25c441b production with this merged head. Focus on restart durability, room epochs, rebuild byte preservation, Reminder completion and Calendar photo readback, then the three E2E stops. No push or deploy.

Start integrity triage on job/merge-round-4 at a6fd7f7040972d476575c06a4c363e21070bb42d. Compare cc25c441b production with this merged head. Focus on restart durability, room epochs, rebuild byte preservation, Reminder completion and Calendar photo readback, then the three E2E stops. No push or deploy.
Author
Owner

Started merge-round-4a on branch job/merge-round-4a, based on origin/dev at cc25c441b7a974185622a1dee853cf38686d2b67. I’m checking the round-4 conflict resolutions, then I’ll merge the eight approved web branches in order and run the requested web gates and production e2e set. No Rust changes are intended.

Started merge-round-4a on branch `job/merge-round-4a`, based on origin/dev at `cc25c441b7a974185622a1dee853cf38686d2b67`. I’m checking the round-4 conflict resolutions, then I’ll merge the eight approved web branches in order and run the requested web gates and production e2e set. No Rust changes are intended.
Author
Owner

Started data-consistency follow-up for merge round 4. Branch: job/merge-round-4. Initial head: a6fd7f7040972d476575c06a4c363e21070bb42d. Comparison base: production cc25c441b. I will compare the six findings ten times at each revision, inspect the three E2E stops, and commit each confirmed fix with a deterministic regression test. No push or deploy.

Started data-consistency follow-up for merge round 4. Branch: `job/merge-round-4`. Initial head: `a6fd7f7040972d476575c06a4c363e21070bb42d`. Comparison base: production `cc25c441b`. I will compare the six findings ten times at each revision, inspect the three E2E stops, and commit each confirmed fix with a deterministic regression test. No push or deploy.
Author
Owner

Source findings for #427:

  • Search: commit 6e0d42fae adds Notes to PLUGIN_NAVIGATION. The existing group unit test already requires all eight rows. The seven-row E2E assertion omits Notes. Update it to include Notes and retain an exact title/order assertion.
  • Calendar: the Composer maps bare #work to area/work in Log mode (CONTEXT, DESIGN §31). Both revisions use this mapping. The edit dialog returns the stored Tag. Update the two edit assertions to #area/work; do not change stored Tags.
  • Files: the loadFilesVisibility().then(syncVisibility) callback reloads even when neither switch changed. The callback is byte-identical on production cc25c441b. Keep the duplicate-request assertion and remove only that unnecessary reload after confirming the real-server behavior.
  • Reminder: ReminderResource::calendar_data preserves dav_metadata.completed_at, and reminders.rs is unchanged between production and round 4. The probe writes 09:00 and expects 00:00. The focused readback will verify the supplied timestamp on both builds before updating the assertion.
Source findings for #427: - Search: commit `6e0d42fae` adds Notes to `PLUGIN_NAVIGATION`. The existing group unit test already requires all eight rows. The seven-row E2E assertion omits Notes. Update it to include Notes and retain an exact title/order assertion. - Calendar: the Composer maps bare `#work` to `area/work` in Log mode (CONTEXT, DESIGN §31). Both revisions use this mapping. The edit dialog returns the stored Tag. Update the two edit assertions to `#area/work`; do not change stored Tags. - Files: the `loadFilesVisibility().then(syncVisibility)` callback reloads even when neither switch changed. The callback is byte-identical on production `cc25c441b`. Keep the duplicate-request assertion and remove only that unnecessary reload after confirming the real-server behavior. - Reminder: `ReminderResource::calendar_data` preserves `dav_metadata.completed_at`, and `reminders.rs` is unchanged between production and round 4. The probe writes 09:00 and expects 00:00. The focused readback will verify the supplied timestamp on both builds before updating the assertion.
Author
Owner

Integration check found that the merged layout kept an unused selectSubview fragment from #571 after #526 removed the title sub-view menu; ModeHeader had no matching callback. The first bun run check reported MODE_SUBVIEWS missing at +layout.svelte:397 and an implicit any. I removed the stale fragment, updated the Top row comment to DESIGN §34, and reran the check: svelte-check found 0 errors and 0 warnings.

Integration check found that the merged layout kept an unused `selectSubview` fragment from #571 after #526 removed the title sub-view menu; `ModeHeader` had no matching callback. The first `bun run check` reported `MODE_SUBVIEWS` missing at `+layout.svelte:397` and an implicit `any`. I removed the stale fragment, updated the Top row comment to DESIGN §34, and reran the check: `svelte-check found 0 errors and 0 warnings`.
Author
Owner

The original Notes restart probe has a pre-existing fixed 4 s save assumption. restart.mjs, calternal-collab, the Notes provider, and DAV Reminder persistence are byte-identical between cc25c441b and a6fd7f704. The old report first read the 44-byte original body, then read 63 bytes after reconnect: the 19-byte difference is exactly the pending edit and separators. This is consistent with one delayed save and replay, not duplicate content. A valid cached snapshot must retain its epoch while its file etag still matches. The subsequent stale-epoch and legacy-refusal expectations require the preceding edit to have reached disk.

Written reason for the probe change: wait for the actual edit to appear exactly once on the REST read before the crash cut point. Keep the original stale-epoch, legacy-refusal and byte-equality expectations. If persistence times out, stop there and report that one failure, rather than infer three more integrity failures from an unestablished precondition. An in-process regression test will cover a crash before the write and idempotent pending-edit replay without a timing race. No product epoch behavior changes.

The original Notes restart probe has a pre-existing fixed 4 s save assumption. `restart.mjs`, `calternal-collab`, the Notes provider, and DAV Reminder persistence are byte-identical between cc25c441b and a6fd7f704. The old report first read the 44-byte original body, then read 63 bytes after reconnect: the 19-byte difference is exactly the pending edit and separators. This is consistent with one delayed save and replay, not duplicate content. A valid cached snapshot must retain its epoch while its file etag still matches. The subsequent stale-epoch and legacy-refusal expectations require the preceding edit to have reached disk. Written reason for the probe change: wait for the actual edit to appear exactly once on the REST read before the crash cut point. Keep the original stale-epoch, legacy-refusal and byte-equality expectations. If persistence times out, stop there and report that one failure, rather than infer three more integrity failures from an unestablished precondition. An in-process regression test will cover a crash before the write and idempotent pending-edit replay without a timing race. No product epoch behavior changes.
Author
Owner

The requested toast-ring e2e passes ring geometry and hover pause checks, then fails its F6 keyboard-focus pause assertion: computed animationPlayState is running at toast-ring.mjs:502. A focused production-browser probe showed the cause: the #527 keyboard-mode selector in packages/ui/src/tokens.css sets animation: none !important on every descendant, which cancels the #539 timer-ring animation before AppToaster can pause it with Sonner. I’m making the timer stroke an explicit exception to that global motion reset, so its existing expanded-state pause remains authoritative.

The requested toast-ring e2e passes ring geometry and hover pause checks, then fails its F6 keyboard-focus pause assertion: computed `animationPlayState` is `running` at `toast-ring.mjs:502`. A focused production-browser probe showed the cause: the #527 keyboard-mode selector in `packages/ui/src/tokens.css` sets `animation: none !important` on every descendant, which cancels the #539 timer-ring animation before AppToaster can pause it with Sonner. I’m making the timer stroke an explicit exception to that global motion reset, so its existing expanded-state pause remains authoritative.
Author
Owner

Comparison complete: production cc25c441b and merged a6fd7f704 each passed ten original Notes restart probes, ten Reminder completion write/read probes, and ten 120-photo bursts with eight workers. Every photo was accepted (POST 201, PATCH 204) and visible exactly once. The source diff for calternal-collab, the Notes provider, restart.mjs and DAV Reminder persistence is empty across those heads.

Four Notes follow-ups are filed separately: #597 missing edit, #598 stale report, #599 44→63 bytes, #600 legacy sync. These reports remain unreproduced; the confirmed pre-existing probe defect is its 4 s persistence assumption. The exact 19-byte delta is one pending edit and separators. The new in-process test proves retained-epoch/idempotent replay at a pre-save crash cut point; the corrected real-server probe observes a persisted edit before requiring a new epoch.

Reminder: #558 has the 09:00 request / 00:00 expectation defect and twenty successful timestamp-preserving reads. Photo burst: #565 already records rejected uploads on production; the original round-4 0/120 witness is missing, so no accepted-write-loss claim can be established. No introduced integrity failure is reproduced, so there is no failing case to bisect across the 16 merges. This does not rule out an interaction present only in the old full-suite fixture.

Files duplicate Inbox GET was reproduced with the production binary and fixed by ec000d777. Search's eighth row is the Notes Tab added by round 4; Calendar shows the canonical stored #area/work Tag. Written expectation reasons are above. Focused flows passed; full Calendar still stops at the pre-existing #569 composer snapshot. The additional Files hidden-files fixture uses the wrong Apple AAE filename; it remains unchanged, and a temporary correct-filename diagnostic passed (#420).

Final bounded ordinary-write round: consistency probe: 0 findings; restart probe: 0 findings. The full exploit/protocol-abuse campaign was not run in this session and remains a release-verification gap. Rust crate gates are running; web check and all 1011 tests passed. Files production screenshot evidence (390/820/1440, Light/Dark and additional widths) is attached: https://git.kayg.org/attachments/fd3cbd26-fb49-430d-a2f7-e1a5211a69f9 . Local benchmark numbers and the release/large-folder follow-up are on #563.

Comparison complete: production cc25c441b and merged a6fd7f704 each passed ten original Notes restart probes, ten Reminder completion write/read probes, and ten 120-photo bursts with eight workers. Every photo was accepted (POST 201, PATCH 204) and visible exactly once. The source diff for calternal-collab, the Notes provider, restart.mjs and DAV Reminder persistence is empty across those heads. Four Notes follow-ups are filed separately: #597 missing edit, #598 stale report, #599 44→63 bytes, #600 legacy sync. These reports remain unreproduced; the confirmed pre-existing probe defect is its 4 s persistence assumption. The exact 19-byte delta is one pending edit and separators. The new in-process test proves retained-epoch/idempotent replay at a pre-save crash cut point; the corrected real-server probe observes a persisted edit before requiring a new epoch. Reminder: #558 has the 09:00 request / 00:00 expectation defect and twenty successful timestamp-preserving reads. Photo burst: #565 already records rejected uploads on production; the original round-4 0/120 witness is missing, so no accepted-write-loss claim can be established. No introduced integrity failure is reproduced, so there is no failing case to bisect across the 16 merges. This does not rule out an interaction present only in the old full-suite fixture. Files duplicate Inbox GET was reproduced with the production binary and fixed by ec000d777. Search's eighth row is the Notes Tab added by round 4; Calendar shows the canonical stored #area/work Tag. Written expectation reasons are above. Focused flows passed; full Calendar still stops at the pre-existing #569 composer snapshot. The additional Files hidden-files fixture uses the wrong Apple AAE filename; it remains unchanged, and a temporary correct-filename diagnostic passed (#420). Final bounded ordinary-write round: `consistency probe: 0 findings`; `restart probe: 0 findings`. The full exploit/protocol-abuse campaign was not run in this session and remains a release-verification gap. Rust crate gates are running; web check and all 1011 tests passed. Files production screenshot evidence (390/820/1440, Light/Dark and additional widths) is attached: https://git.kayg.org/attachments/fd3cbd26-fb49-430d-a2f7-e1a5211a69f9 . Local benchmark numbers and the release/large-folder follow-up are on #563.
Author
Owner

Round 4 consistency follow-up — head f91f5484992a39d6e23664d73f55a2d556ee3b88

Built and committed the Files duplicate-listing fix, focused persistence comparison, corrected probe preconditions, and a deterministic in-process Notes crash regression. No push or deploy. The worktree is clean. Required origin/dev integration ran once: documentation-only head 4b849557fb584587e5a651eecc2a5dacf65781a5, merged in 208383af7. The conflict kept the latest owner Toast timings and the round-4 feature decisions.

Release status: no introduced data-integrity failure was reproduced. These comparisons give no evidence that round 4 makes production worse. This is not a full release clearance: the original four Notes incidents have no raw witness and remain unreproduced; the full exploit/protocol-abuse campaign was not run in this session. The final robustness check was a bounded ordinary-write/restart round, not the full suite.

Comparison and classification

The isolated production binary reported cc25c441b7a974185622a1dee853cf38686d2b67; the merged binary reported a6fd7f7040972d476575c06a4c363e21070bb42d. Each ran ten original restart probes and ten ordinary Reminder/120-photo checks. The focused API probes preserve the original completion PUT and photo upload flow but use an owner-only fixture, not the old full-suite accumulated state. The source diff is empty for calternal-collab, the Notes provider, restart.mjs and DAV Reminder persistence across these two heads.

Finding Evidence and classification Follow-up
Notes edit missing Original restart probe passed 10/10 on each build. Confirmed pre-existing fixed 4 s save assumption; original acknowledged-write incident remains unclassified. #597
Missing stale-epoch report A new epoch requires the prior write to have changed the file etag. A pre-save crash can correctly retain the epoch. Original incident not reproduced. #598
Body 44 → 63 bytes after rebuild The 19-byte delta is the pending edit plus separators, exactly once. This can be late save/replay; the original raw epoch/write witness is missing. Original incident not reproduced. #599
Legacy client syncing The old test did not establish that a new epoch existed after its failed save check. A matching cached lineage can continue. Original incident not reproduced. #600
Reminder completion Pre-existing expectation defect: PUT sends 09:00, assertion expected 00:00. Both builds preserve STATUS:COMPLETED and 09:00 in all ten reads each. #558, commented
Calendar 0/120 photos Both builds accepted every POST/PATCH and returned 120 unique photos in all ten bursts each. #565 independently records pre-existing 429/timeouts. Original 0/120 upload-status witness is missing, so accepted-write loss remains unproven. #565, commented

No introduced failing case exists to bisect across the 16 merge commits. This does not exclude an interaction confined to the old full-suite fixture. No product epoch or persistence contract was changed.

The new Rust test replaces volatile rooms in process before any save timer starts. It asserts the same cached epoch, unchanged disk body before replay, and the exact edited body after applying the same pending update twice. Existing post-write restart tests still require a new epoch. The real-server restart probe now observes the edit on disk before its crash, keeps the original stale/refusal/byte-equality assertions, and stops at a failed persistence precondition instead of reporting dependent noise.

E2E assertion triage

  • Files: the extra Inbox GET reproduces on the production SPA. Its visibility-load callback was unchanged across the comparison heads. ec000d777 skips a reload only when both visibility flags already match the loaded listing. The exact duplicate-request assertion was retained and passes.
  • Search: round-4 commit 6e0d42fae adds Notes to PLUGIN_NAVIGATION. Its unit test already expects eight rows. The E2E now includes Notes and still asserts exact titles/order and keyboard reachability. Written reason was posted before changing it.
  • Calendar: both revisions map bare #work in Log mode to stored #area/work (CONTEXT, DESIGN §31). Both edit paths now assert that canonical stored Tag. Written reason was posted before changing them.
  • Full Calendar proceeds past those assertions but stops at the pre-existing #569 nFrozen snapshot; that expectation is unchanged.
  • Additional Files visibility E2E stops at its unchanged wrong Apple AAE filename (hidden-proof.png.aae; contract is <stem>.aae). A temporary correct-filename diagnostic passed the full preference flow and was removed; #420 has the evidence. The committed fixture remains unchanged for owner review.

Files changed since the incoming merged head

CONTEXT.md
apps/web/e2e/calendar.mjs
apps/web/e2e/files.mjs
apps/web/e2e/search.mjs
apps/web/src/lib/files/FilesBrowser.svelte
bench/files-listing-427.py
crates/calternal-collab/src/session.rs
docs/DESIGN.md
tests/adversarial/attack.py
tests/adversarial/consistency.py
tests/adversarial/dav_probe_contracts.py
tests/adversarial/restart.mjs
tests/adversarial/run.sh
tests/adversarial/setup.mjs
tests/adversarial/test_dav_probe.py

CONTEXT.md and docs/DESIGN.md changes come from the required origin/dev integration. All other changes are atomic job commits. No dependencies or migration numbers changed. Doc comments were re-read before this report.

Gates

cargo fmt --check: exit 0, empty output. cargo clippy -p calternal-collab --all-targets -- -D warnings and cargo test -p calternal-collab: exit 0 (73 tests plus doc tests). Rust environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, worktree target/tmp; preset CARGO_TARGET_DIR retained. No Rust route/contract implementation changed, so calternal-server was not a touched-crate gate.

Verbatim Rust gate summary:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 16m 32s
test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.56s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.80s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.56s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 155.34s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.57s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.01s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.23s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.19s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.85s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.87s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 66.36s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

bun run check and bun run test: exit 0. Verbatim summary:

svelte-check found 0 errors and 0 warnings
 Test Files  148 passed (148)
      Tests  1011 passed (1011)
   Duration  165.00s (transform 52%, environment 19%, import 15%, tests 10%, setup 4%)

DAV probe unit tests: 15 passed. Focused production-SPA E2E output:

PASS mocked Index progress indicator, announcements and stable action bounds at phone, tablet and desktop widths in both themes
PASS complete Navigate rows and keyboard-accessible Files Show all
search count e2e: ok
CSP REPORTS search: 0 across 1 pages
calendar edit Tag e2e: both stored-Tag edit paths passed
FILES REQUEST COALESCING E2E PASSED
CSP REPORTS files: 0 across 17 pages

Final bounded robustness output, verbatim (fresh owner-only fixture, one 120-photo burst and one two-restart run):

consistency probe: 0 findings
restart probe: 0 findings

The full robustness suite has no new summary to quote. It was not rerun. Its other previous non-SLOW findings are outside this focused investigation.

Performance and visual evidence

New profile: bench/files-listing-427.py. Local debug run, shared-host load 21.77/22.01/20.38, 120 real uploaded photos: 50 serial reads p50 29.74 ms / p95 52.87 ms, CPU 1.0 s (59.38%), mean RSS 444593574 / peak 447209472 bytes. Burst 32 reads / eight workers: p50 71.46 ms / p95 111.63 ms, CPU 0.46 s (134.72%), mean RSS 447233536 / peak 447250432 bytes. Baseline files.entries: 1.8 / 3.1 ms on release perf-test at 369ab6a2f9. Environments are not comparable. #563 records the release/perf-lock and largest-folder follow-up; 120 entries do not fulfill the realistic 50k worst case.

Files screenshot archive: production build, 390/820/1440 px, Light/Dark, plus additional widths/zoom. Row-box geometry assertions passed; cap-height visual signoff remains with Claude (#538). Claude remains the visual reviewer; no claim of visual approval is made. Comparison and full gate logs contain no private fixture credentials.

Known gaps: original four Notes witnesses and original photo upload-status witness unavailable; full robustness campaign unrun; full Calendar and unchanged Files hidden-files E2E stop as described; largest realistic release performance run pending.

Decisions: no new product behavior outside DESIGN. Test choices: use a read-confirmed persistence cut point, preserve completion instants, include every enabled Navigate Tab, keep canonical stored Tags, and keep upload rejection separate from accepted-write loss. Do not change a valid epoch or weaken photo-count assertions to suppress a finding.

Cleanup: temporary production worktree, web build, snapshot binaries and private runtime fixtures removed. Screenshot and redacted evidence artifacts retained. cargo clean exited 0 with this output:

     Removed 13659 files, 9.4GiB total
**Round 4 consistency follow-up — head `f91f5484992a39d6e23664d73f55a2d556ee3b88`** Built and committed the Files duplicate-listing fix, focused persistence comparison, corrected probe preconditions, and a deterministic in-process Notes crash regression. No push or deploy. The worktree is clean. Required `origin/dev` integration ran once: documentation-only head `4b849557fb584587e5a651eecc2a5dacf65781a5`, merged in `208383af7`. The conflict kept the latest owner Toast timings and the round-4 feature decisions. **Release status:** no introduced data-integrity failure was reproduced. These comparisons give no evidence that round 4 makes production worse. This is not a full release clearance: the original four Notes incidents have no raw witness and remain unreproduced; the full exploit/protocol-abuse campaign was not run in this session. The final robustness check was a bounded ordinary-write/restart round, not the full suite. **Comparison and classification** The isolated production binary reported `cc25c441b7a974185622a1dee853cf38686d2b67`; the merged binary reported `a6fd7f7040972d476575c06a4c363e21070bb42d`. Each ran ten original restart probes and ten ordinary Reminder/120-photo checks. The focused API probes preserve the original completion PUT and photo upload flow but use an owner-only fixture, not the old full-suite accumulated state. The source diff is empty for calternal-collab, the Notes provider, restart.mjs and DAV Reminder persistence across these two heads. | Finding | Evidence and classification | Follow-up | |---|---|---| | Notes edit missing | Original restart probe passed 10/10 on each build. Confirmed pre-existing fixed 4 s save assumption; original acknowledged-write incident remains unclassified. | #597 | | Missing stale-epoch report | A new epoch requires the prior write to have changed the file etag. A pre-save crash can correctly retain the epoch. Original incident not reproduced. | #598 | | Body 44 → 63 bytes after rebuild | The 19-byte delta is the pending edit plus separators, exactly once. This can be late save/replay; the original raw epoch/write witness is missing. Original incident not reproduced. | #599 | | Legacy client syncing | The old test did not establish that a new epoch existed after its failed save check. A matching cached lineage can continue. Original incident not reproduced. | #600 | | Reminder completion | Pre-existing expectation defect: PUT sends 09:00, assertion expected 00:00. Both builds preserve STATUS:COMPLETED and 09:00 in all ten reads each. | #558, commented | | Calendar 0/120 photos | Both builds accepted every POST/PATCH and returned 120 unique photos in all ten bursts each. #565 independently records pre-existing 429/timeouts. Original 0/120 upload-status witness is missing, so accepted-write loss remains unproven. | #565, commented | No introduced failing case exists to bisect across the 16 merge commits. This does not exclude an interaction confined to the old full-suite fixture. No product epoch or persistence contract was changed. The new Rust test replaces volatile rooms in process before any save timer starts. It asserts the same cached epoch, unchanged disk body before replay, and the exact edited body after applying the same pending update twice. Existing post-write restart tests still require a new epoch. The real-server restart probe now observes the edit on disk before its crash, keeps the original stale/refusal/byte-equality assertions, and stops at a failed persistence precondition instead of reporting dependent noise. **E2E assertion triage** - Files: the extra Inbox GET reproduces on the production SPA. Its visibility-load callback was unchanged across the comparison heads. `ec000d777` skips a reload only when both visibility flags already match the loaded listing. The exact duplicate-request assertion was retained and passes. - Search: round-4 commit `6e0d42fae` adds Notes to PLUGIN_NAVIGATION. Its unit test already expects eight rows. The E2E now includes Notes and still asserts exact titles/order and keyboard reachability. Written reason was posted before changing it. - Calendar: both revisions map bare #work in Log mode to stored #area/work (CONTEXT, DESIGN §31). Both edit paths now assert that canonical stored Tag. Written reason was posted before changing them. - Full Calendar proceeds past those assertions but stops at the pre-existing #569 `nFrozen snapshot`; that expectation is unchanged. - Additional Files visibility E2E stops at its unchanged wrong Apple AAE filename (`hidden-proof.png.aae`; contract is `<stem>.aae`). A temporary correct-filename diagnostic passed the full preference flow and was removed; #420 has the evidence. The committed fixture remains unchanged for owner review. **Files changed since the incoming merged head** ```text CONTEXT.md apps/web/e2e/calendar.mjs apps/web/e2e/files.mjs apps/web/e2e/search.mjs apps/web/src/lib/files/FilesBrowser.svelte bench/files-listing-427.py crates/calternal-collab/src/session.rs docs/DESIGN.md tests/adversarial/attack.py tests/adversarial/consistency.py tests/adversarial/dav_probe_contracts.py tests/adversarial/restart.mjs tests/adversarial/run.sh tests/adversarial/setup.mjs tests/adversarial/test_dav_probe.py ``` CONTEXT.md and docs/DESIGN.md changes come from the required origin/dev integration. All other changes are atomic job commits. No dependencies or migration numbers changed. Doc comments were re-read before this report. **Gates** `cargo fmt --check`: exit 0, empty output. `cargo clippy -p calternal-collab --all-targets -- -D warnings` and `cargo test -p calternal-collab`: exit 0 (73 tests plus doc tests). Rust environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, worktree target/tmp; preset CARGO_TARGET_DIR retained. No Rust route/contract implementation changed, so calternal-server was not a touched-crate gate. Verbatim Rust gate summary: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 16m 32s test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.56s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.80s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.56s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 155.34s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.57s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.01s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.23s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.19s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.85s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.87s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 66.36s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `bun run check` and `bun run test`: exit 0. Verbatim summary: ```text svelte-check found 0 errors and 0 warnings Test Files 148 passed (148) Tests 1011 passed (1011) Duration 165.00s (transform 52%, environment 19%, import 15%, tests 10%, setup 4%) ``` DAV probe unit tests: 15 passed. Focused production-SPA E2E output: ```text PASS mocked Index progress indicator, announcements and stable action bounds at phone, tablet and desktop widths in both themes PASS complete Navigate rows and keyboard-accessible Files Show all search count e2e: ok CSP REPORTS search: 0 across 1 pages calendar edit Tag e2e: both stored-Tag edit paths passed FILES REQUEST COALESCING E2E PASSED CSP REPORTS files: 0 across 17 pages ``` Final bounded robustness output, verbatim (fresh owner-only fixture, one 120-photo burst and one two-restart run): ```text consistency probe: 0 findings restart probe: 0 findings ``` The full robustness suite has no new summary to quote. It was not rerun. Its other previous non-SLOW findings are outside this focused investigation. **Performance and visual evidence** New profile: bench/files-listing-427.py. Local debug run, shared-host load 21.77/22.01/20.38, 120 real uploaded photos: 50 serial reads p50 29.74 ms / p95 52.87 ms, CPU 1.0 s (59.38%), mean RSS 444593574 / peak 447209472 bytes. Burst 32 reads / eight workers: p50 71.46 ms / p95 111.63 ms, CPU 0.46 s (134.72%), mean RSS 447233536 / peak 447250432 bytes. Baseline files.entries: 1.8 / 3.1 ms on release perf-test at 369ab6a2f9fc673e3564b94857fbecfeb04df404. Environments are not comparable. #563 records the release/perf-lock and largest-folder follow-up; 120 entries do not fulfill the realistic 50k worst case. [Files screenshot archive](https://git.kayg.org/attachments/fd3cbd26-fb49-430d-a2f7-e1a5211a69f9): production build, 390/820/1440 px, Light/Dark, plus additional widths/zoom. Row-box geometry assertions passed; cap-height visual signoff remains with Claude (#538). Claude remains the visual reviewer; no claim of visual approval is made. [Comparison and full gate logs](https://git.kayg.org/attachments/73c9dbc2-08b0-4c02-9e94-668a4f5dc3fd) contain no private fixture credentials. **Known gaps:** original four Notes witnesses and original photo upload-status witness unavailable; full robustness campaign unrun; full Calendar and unchanged Files hidden-files E2E stop as described; largest realistic release performance run pending. **Decisions:** no new product behavior outside DESIGN. Test choices: use a read-confirmed persistence cut point, preserve completion instants, include every enabled Navigate Tab, keep canonical stored Tags, and keep upload rejection separate from accepted-write loss. Do not change a valid epoch or weaken photo-count assertions to suppress a finding. **Cleanup:** temporary production worktree, web build, snapshot binaries and private runtime fixtures removed. Screenshot and redacted evidence artifacts retained. `cargo clean` exited 0 with this output: ```text Removed 13659 files, 9.4GiB total ```
Author
Owner

Starting merge round 6 on job/merge-round-6, based on origin/dev at 3f258302a0f2d6418ff60c9ce22cbb33e008ca99. I have read CLAUDE.md, CONTEXT.md, the relevant Calendar, Mail, Appearance, Chrome/motion and Notes sections of docs/DESIGN.md, and issue #427. I will merge the eight approved branches in the requested order, apply the two small UI fixes, then run the requested gates and one time-boxed robustness round.

Starting merge round 6 on `job/merge-round-6`, based on `origin/dev` at `3f258302a0f2d6418ff60c9ce22cbb33e008ca99`. I have read `CLAUDE.md`, `CONTEXT.md`, the relevant Calendar, Mail, Appearance, Chrome/motion and Notes sections of `docs/DESIGN.md`, and issue #427. I will merge the eight approved branches in the requested order, apply the two small UI fixes, then run the requested gates and one time-boxed robustness round.
Author
Owner

Merge findings and resolutions so far:

  • #613 and #616 changed the same Mail Settings controls. The merge keeps #613's server-reported progress and retry handling, #616's busy guards and stable dedupe toast identity, and the shared API client's empty 202 handling.
  • #626's migration 0009 changes Mail membership uniqueness from (folder_id, generation, message_id) to (folder_id, generation, uid). The old ON CONFLICT(folder_id, generation, message_id) DO NOTHING insert clause is removed because the new schema permits several UIDs to reference one message.
  • #549 was based on an older Calendar/Notes tree and produced 17 content conflicts. I kept its bounded Journal snapshots, complete Calendar grid snapshots, warm Tab redirect handling and benchmark path alongside current #609, #623 and #588 behavior. Notes migrations 21–23 remain ordered before migration 24.
  • The old inline Log editor in the #549 branch was superseded by the current shared Composer flow (DESIGN §38). Calendar preview refreshes replace the preview; the Composer owns the draft and keeps it across that refresh.
Merge findings and resolutions so far: - #613 and #616 changed the same Mail Settings controls. The merge keeps #613's server-reported progress and retry handling, #616's busy guards and stable dedupe toast identity, and the shared API client's empty 202 handling. - #626's migration 0009 changes Mail membership uniqueness from `(folder_id, generation, message_id)` to `(folder_id, generation, uid)`. The old `ON CONFLICT(folder_id, generation, message_id) DO NOTHING` insert clause is removed because the new schema permits several UIDs to reference one message. - #549 was based on an older Calendar/Notes tree and produced 17 content conflicts. I kept its bounded Journal snapshots, complete Calendar grid snapshots, warm Tab redirect handling and benchmark path alongside current #609, #623 and #588 behavior. Notes migrations 21–23 remain ordered before migration 24. - The old inline Log editor in the #549 branch was superseded by the current shared Composer flow (DESIGN §38). Calendar preview refreshes replace the preview; the Composer owns the draft and keeps it across that refresh.
Author
Owner

Gate finding: bun run --cwd apps/web test failed 3 of 1,053 tests (150 files passed). Failures: the Calendar week short title now returns Sep while my added assertion expected Sep 2026; a warm Calendar snapshot remains stale after the unmounted-view Files notification instead of being removed; and the #549 preview-editor test cannot find its Edit log entry control. I am resolving the title assertion and reconciling cache/editor behavior with the current shared Composer and retained-snapshot design before rerunning the web gate.

Gate finding: `bun run --cwd apps/web test` failed 3 of 1,053 tests (150 files passed). Failures: the Calendar week short title now returns `Sep` while my added assertion expected `Sep 2026`; a warm Calendar snapshot remains stale after the unmounted-view Files notification instead of being removed; and the #549 preview-editor test cannot find its `Edit log entry` control. I am resolving the title assertion and reconciling cache/editor behavior with the current shared Composer and retained-snapshot design before rerunning the web gate.
Author
Owner

Gate finding: cargo test -p calternal-plugin-notes failed 1 of 167 tests. tests::daily_and_composer_preserve_unrelated_bytes receives 404 from GET /journal/2026-09-24 after the Composer successfully appends to a Daily note whose legacy Log line needs a block ID. The new #549 snapshot reader does not repair or serve that committed source in this case. I am tracing the pending-ID/snapshot path; all other Notes tests passed (166 passed, 0 ignored).

Gate finding: `cargo test -p calternal-plugin-notes` failed 1 of 167 tests. `tests::daily_and_composer_preserve_unrelated_bytes` receives 404 from `GET /journal/2026-09-24` after the Composer successfully appends to a Daily note whose legacy Log line needs a block ID. The new #549 snapshot reader does not repair or serve that committed source in this case. I am tracing the pending-ID/snapshot path; all other Notes tests passed (166 passed, 0 ignored).
Author
Owner

Finding update: the focused test passes when run alone. The full Notes suite uses one shared test User ID, and under parallel test load another Notes writer can hold the User mutex while the Journal snapshot fallback tries its nonblocking legacy-ID repair; that read then returns 404. I kept the #549 nonblocking snapshot contract and the byte-preserving Log write contract, because repairing the legacy line before the Log write returns changes bytes earlier than the existing test and design allow. I will run the Notes test gate serially and record this transient missing-snapshot race as a known gap for follow-up.

Finding update: the focused test passes when run alone. The full Notes suite uses one shared test User ID, and under parallel test load another Notes writer can hold the User mutex while the Journal snapshot fallback tries its nonblocking legacy-ID repair; that read then returns 404. I kept the #549 nonblocking snapshot contract and the byte-preserving Log write contract, because repairing the legacy line before the Log write returns changes bytes earlier than the existing test and design allow. I will run the Notes test gate serially and record this transient missing-snapshot race as a known gap for follow-up.
Author
Owner

Finding during merge-round-6 e2e: toaststack-616.mjs could not click Sync now because the shared Mail fixture had no successful-sync timestamp, so #613 correctly kept the button busy as a first sync. After representing the fixture accounts as previously synced and having the toast probe return a completed status after its gated queue request (there is no IMAP worker in this probe), the unchanged five-click dedupe assertions passed and 12 production screenshots were captured. Commit: 83431474c992.

Finding during merge-round-6 e2e: `toaststack-616.mjs` could not click Sync now because the shared Mail fixture had no successful-sync timestamp, so #613 correctly kept the button busy as a first sync. After representing the fixture accounts as previously synced and having the toast probe return a completed status after its gated queue request (there is no IMAP worker in this probe), the unchanged five-click dedupe assertions passed and 12 production screenshots were captured. Commit: `83431474c992`.
Author
Owner

Finding during merge-round-6 e2e: the Week Today check initially timed out at 17:32 local because its now line was 6.5 hours below the scroller top, beyond the test's existing 1–5 hour window. The app focused the correct date and kept the correct Week URL. Playwright 1.63 had reset the attempted fake clock after the full-page navigation. The e2e now pins its test clock to local noon and installs it on the loaded page before each Today action; the assertion is unchanged. Both Today actions passed, then the script saved all 12 Week/Day screenshots at 390, 820, and 1440 px in light and dark. Commit: 9cb68197460c.

Finding during merge-round-6 e2e: the Week Today check initially timed out at 17:32 local because its now line was 6.5 hours below the scroller top, beyond the test's existing 1–5 hour window. The app focused the correct date and kept the correct Week URL. Playwright 1.63 had reset the attempted fake clock after the full-page navigation. The e2e now pins its test clock to local noon and installs it on the loaded page before each Today action; the assertion is unchanged. Both Today actions passed, then the script saved all 12 Week/Day screenshots at 390, 820, and 1440 px in light and dark. Commit: `9cb68197460c`.
Author
Owner

Finding and fix: the production glass audit reached the desktop context submenu at 1440 px and found that the expanded “View as” trigger lost its hover highlight when the pointer entered its child surface. The recursive Menu explicitly suppressed the active state for an open submenu trigger. I removed that suppression and documented the pointer-state invariant. The audit expectation remains unchanged; I am rebuilding and rerunning the full audit now.

Finding and fix: the production glass audit reached the desktop context submenu at 1440 px and found that the expanded “View as” trigger lost its hover highlight when the pointer entered its child surface. The recursive Menu explicitly suppressed the active state for an open submenu trigger. I removed that suppression and documented the pointer-state invariant. The audit expectation remains unchanged; I am rebuilding and rerunning the full audit now.
Author
Owner

Finding from the real production glass audit: the Settings inner-card assertion still calculated light alpha from the pre-#588 36% overlay. The page rendered 92%, which is the new 78% light overlay plus the unchanged 14-point inner-card step. I updated this expectation because #588 explicitly changes the light glass token. Dark alpha and the shared step are unchanged; rerunning the audit now.

Finding from the real production glass audit: the Settings inner-card assertion still calculated light alpha from the pre-#588 36% overlay. The page rendered 92%, which is the new 78% light overlay plus the unchanged 14-point inner-card step. I updated this expectation because #588 explicitly changes the light glass token. Dark alpha and the shared step are unchanged; rerunning the audit now.
Author
Owner

Production Chromium evidence from test:e2e:glass-audit: after opening /notes, the audit timed out waiting 30 s for the real Notes actions button. NotesExplorer is rendered only when AppSidebar receives mode === 'calendar', but modeForPath('/notes') now returns notes and the mode registry has a Notes mode. This leaves Notes mode without its Notes tree or actions. I changed the sidebar condition to match Notes mode, with DESIGN §§28 N1 and 34 and job #549 recorded by the module comment; the production audit will verify the fix.

Production Chromium evidence from `test:e2e:glass-audit`: after opening `/notes`, the audit timed out waiting 30 s for the real `Notes actions` button. `NotesExplorer` is rendered only when `AppSidebar` receives `mode === 'calendar'`, but `modeForPath('/notes')` now returns `notes` and the mode registry has a Notes mode. This leaves Notes mode without its Notes tree or actions. I changed the sidebar condition to match Notes mode, with DESIGN §§28 N1 and 34 and job #549 recorded by the module comment; the production audit will verify the fix.
Author
Owner

Production Chromium evidence from test:e2e:glass-audit: the dark 390 px pass reached the Catppuccin row, which has a submenu, then timed out waiting for a separate flyout. Menu.svelte intentionally drills into a child list on narrow layouts; it does not open a flyout on hover. I updated the glass audit to tap submenu rows and return through the back row at 390 px, while retaining hover/flyout checks at desktop widths (issue #436, DESIGN §34).

Production Chromium evidence from `test:e2e:glass-audit`: the dark 390 px pass reached the Catppuccin row, which has a submenu, then timed out waiting for a separate flyout. `Menu.svelte` intentionally drills into a child list on narrow layouts; it does not open a flyout on hover. I updated the glass audit to tap submenu rows and return through the back row at 390 px, while retaining hover/flyout checks at desktop widths (issue #436, DESIGN §34).
Author
Owner

The follow-up production audit reached the Catppuccin submenu at 390 px after switching the test to the documented tap-to-drill interaction. The blur check then sampled its old location: the probe was at y=363 while the live menu had re-placed to y=629, so the measured variance ratio was 1.00 outside the glass surface. The audit now repositions its stripe from the live surface bounds before each pixel check (issue #436, DESIGN §34).

The follow-up production audit reached the Catppuccin submenu at 390 px after switching the test to the documented tap-to-drill interaction. The blur check then sampled its old location: the probe was at y=363 while the live menu had re-placed to y=629, so the measured variance ratio was 1.00 outside the glass surface. The audit now repositions its stripe from the live surface bounds before each pixel check (issue #436, DESIGN §34).
Author
Owner

The focused 390 px dark audit opened and measured the real Catppuccin submenu, then failed while returning to the root menu. Menu.svelte labels its drill-back row Back to <current submenu>; the audit had hard-coded Back to Theme. I changed it to use the active family label, so the sweep can continue through every narrow-layout submenu (issue #436, DESIGN §34).

The focused 390 px dark audit opened and measured the real Catppuccin submenu, then failed while returning to the root menu. `Menu.svelte` labels its drill-back row `Back to <current submenu>`; the audit had hard-coded `Back to Theme`. I changed it to use the active family label, so the sweep can continue through every narrow-layout submenu (issue #436, DESIGN §34).
Author
Owner

Production Chromium evidence: the dark 1440 px Theme variant menu opened and accepted its selection, but the audit timed out waiting for raw localStorage to change. Appearance preferences use the per-User storage namespace, the same production store used by Photos. I am updating both the read and wait in selectThemeSubmenuByMouse to use the audit's __userStorageTest seam (issue #436, DESIGN §34).

Production Chromium evidence: the dark 1440 px Theme variant menu opened and accepted its selection, but the audit timed out waiting for raw `localStorage` to change. Appearance preferences use the per-User storage namespace, the same production store used by Photos. I am updating both the read and wait in `selectThemeSubmenuByMouse` to use the audit's `__userStorageTest` seam (issue #436, DESIGN §34).
Author
Owner

The focused dark 1440 px production audit passed Theme, Files, Photos, Notes and Editor menus, then failed while checking the Calendar preview Tooltip. The test moved the pointer to page coordinate (5,5) to dismiss the Tooltip; that also left the anchored preview and removed its trigger before the keyboard check. I will dismiss it over a non-action area inside the live preview so the Tooltip closes while the preview remains mounted (issue #436, DESIGN §34).

The focused dark 1440 px production audit passed Theme, Files, Photos, Notes and Editor menus, then failed while checking the Calendar preview Tooltip. The test moved the pointer to page coordinate (5,5) to dismiss the Tooltip; that also left the anchored preview and removed its trigger before the keyboard check. I will dismiss it over a non-action area inside the live preview so the Tooltip closes while the preview remains mounted (issue #436, DESIGN §34).
Author
Owner

The targeted Tooltip rerun showed that hovering the preview eyebrow did not dismiss the visible Tooltip, even though the trigger stayed mounted. The shared layer dismisses on focusout and shows again on reader keyboard navigation. I am using Shift+Tab to dismiss the pointer Tooltip, then Tab to check the keyboard Tooltip, while keeping the pointer over the anchored preview (issue #436, DESIGN §34).

The targeted Tooltip rerun showed that hovering the preview eyebrow did not dismiss the visible Tooltip, even though the trigger stayed mounted. The shared layer dismisses on focusout and shows again on reader keyboard navigation. I am using Shift+Tab to dismiss the pointer Tooltip, then Tab to check the keyboard Tooltip, while keeping the pointer over the anchored preview (issue #436, DESIGN §34).
Author
Owner

The focused 390 px surface audit passes with the fixture in its initial Files view. In the full run, the earlier desktop menu audit changed the saved Files view to Grid; the selection-tray audit then timed out on a .fc-item text match. The existing context-menu audit documents that Files clips visible names but preserves the full filename in the option's accessible name. I am switching the selection-tray audit to that same role/name locator (issue #436, DESIGN §34).

The focused 390 px surface audit passes with the fixture in its initial Files view. In the full run, the earlier desktop menu audit changed the saved Files view to Grid; the selection-tray audit then timed out on a `.fc-item` text match. The existing context-menu audit documents that Files clips visible names but preserves the full filename in the option's accessible name. I am switching the selection-tray audit to that same role/name locator (issue #436, DESIGN §34).
Author
Owner

The full glass audit reached Chromium light / week background / 1440 px, then timed out opening the Calendar preview. Playwright reported the Event center was intercepted by an attachment thumbnail (.pile-slot); the Calendar still contained the provider-backed Event. I am updating the audit to find a real unobscured point inside the Event before moving the mouse, so the preview check exercises the production hover behavior without an overlay intercepting the test coordinate.

The full glass audit reached Chromium light / week background / 1440 px, then timed out opening the Calendar preview. Playwright reported the Event center was intercepted by an attachment thumbnail (`.pile-slot`); the Calendar still contained the provider-backed Event. I am updating the audit to find a real unobscured point inside the Event before moving the mouse, so the preview check exercises the production hover behavior without an overlay intercepting the test coordinate.
Author
Owner

The Chromium visual and interaction matrix completed. WebKit then timed out waiting for the first Calendar route to hydrate its Light theme: the test context used the local HTTP origin, where WebKit does not expose the Secure __Host-calternal_user_hint cookie needed by the per-User Theme store. I am moving WebKit screenshots to the existing same-origin HTTPS test front in e2e/harness.mjs, so the production app receives the real secure session and user hint.

The Chromium visual and interaction matrix completed. WebKit then timed out waiting for the first Calendar route to hydrate its Light theme: the test context used the local HTTP origin, where WebKit does not expose the Secure `__Host-calternal_user_hint` cookie needed by the per-User Theme store. I am moving WebKit screenshots to the existing same-origin HTTPS test front in `e2e/harness.mjs`, so the production app receives the real secure session and user hint.
Author
Owner

The HTTPS run authenticated correctly and its real Calendar range returned “Attack on Titan,” but the Event was absent from the WebKit grid. The first fixture context set the host timezone; the second browser context did not. This left WebKit's virtualized TimeGrid centered at 18:00 UTC while the Event was at 20:00 Europe/Berlin. I am giving both browser contexts the same timezone so the actual rendered Calendar view and fixture use the same local hour.

The HTTPS run authenticated correctly and its real Calendar range returned “Attack on Titan,” but the Event was absent from the WebKit grid. The first fixture context set the host timezone; the second browser context did not. This left WebKit's virtualized TimeGrid centered at 18:00 UTC while the Event was at 20:00 Europe/Berlin. I am giving both browser contexts the same timezone so the actual rendered Calendar view and fixture use the same local hour.
Author
Owner

Finding: Linux WebKit's Calendar TimeGrid emitted an initial scroll event while its 2,001-day track still had zero scroll range. then replaced with 2024-01-02 and then 2021-04-07. After the fix, the route and visible columns remain on the requested 2026 week, and the real Attack on Titan Event renders. Fix: defer the initial scroll write until the next frame, ignore scroll/settle events until then, and keep the grid hidden until positioned. Added a deep-link assertion to the production glass audit. Commit: 597f208cc.

Finding: Linux WebKit's Calendar TimeGrid emitted an initial scroll event while its 2,001-day track still had zero scroll range. then replaced with 2024-01-02 and then 2021-04-07. After the fix, the route and visible columns remain on the requested 2026 week, and the real Attack on Titan Event renders. Fix: defer the initial scroll write until the next frame, ignore scroll/settle events until then, and keep the grid hidden until positioned. Added a deep-link assertion to the production glass audit. Commit: 597f208cc.
Author
Owner

Correction to the finding: Linux WebKit's Calendar TimeGrid emitted an initial scroll event while its 2,001-day track still had zero scroll range. The Calendar replaced the requested week of 2026-09-28 with 2024-01-02 and then 2021-04-07. After the fix, the route and visible columns remain on the requested 2026 week, and the real Attack on Titan Event renders. The fix defers the initial scroll write until the next frame, ignores scroll and settle events until then, and keeps the grid hidden until positioned. The production glass audit now asserts that the deep link remains unchanged. Commit: 597f208cc.

Correction to the finding: Linux WebKit's Calendar TimeGrid emitted an initial scroll event while its 2,001-day track still had zero scroll range. The Calendar replaced the requested week of 2026-09-28 with 2024-01-02 and then 2021-04-07. After the fix, the route and visible columns remain on the requested 2026 week, and the real Attack on Titan Event renders. The fix defers the initial scroll write until the next frame, ignores scroll and settle events until then, and keeps the grid hidden until positioned. The production glass audit now asserts that the deep link remains unchanged. Commit: 597f208cc.
Author
Owner

Finding: the real HTTPS front used by the glass audit forwarded API streams but dropped Notes collaboration WebSocket upgrades. On the production Note route, the API created the Note but the editor remained at Connecting and its contenteditable surface did not mount. I added a streamed TCP tunnel for upgrade handshakes and frames. Evidence: the focused Chromium Notes and Editor menu audit now passes, including opening a real Note and applying Turn into and Move actions.

Finding: the real HTTPS front used by the glass audit forwarded API streams but dropped Notes collaboration WebSocket upgrades. On the production Note route, the API created the Note but the editor remained at Connecting and its contenteditable surface did not mount. I added a streamed TCP tunnel for upgrade handshakes and frames. Evidence: the focused Chromium Notes and Editor menu audit now passes, including opening a real Note and applying Turn into and Move actions.
Author
Owner

The redundant Playwright API route caused a late ECONNRESET during the first full WebKit glass run. The HTTPS fixture already sends the real Secure session cookie, so I removed that route and let same-origin requests go directly through the HTTPS front. Verification: full Chromium glass audit passed; full WebKit glass audit passed across all three widths, light/dark themes, theme/photo backgrounds, menu states, and interaction checks. Linux WebKit still reports its expected backdrop-pixel and Block action limitations; Chromium covers the rendered pixel and real Editor actions. Commit: 828df0a32.

The redundant Playwright API route caused a late ECONNRESET during the first full WebKit glass run. The HTTPS fixture already sends the real Secure session cookie, so I removed that route and let same-origin requests go directly through the HTTPS front. Verification: full Chromium glass audit passed; full WebKit glass audit passed across all three widths, light/dark themes, theme/photo backgrounds, menu states, and interaction checks. Linux WebKit still reports its expected backdrop-pixel and Block action limitations; Chromium covers the rendered pixel and real Editor actions. Commit: 828df0a32.
Author
Owner

Merge round 6 final report

Merged approved branches in the requested order: job/motion-611, job/weekstate-609, job/toaststack-616, job/mailsync-613, job/maildup-626, job/ghosttask-623, job/lightglass-588, and job/tabswitch-549. Also merged the current origin/dev once before final gates. No push or deploy was made.

Head: 3286cad7208562f3c357fb59204854b0dbab6f84.

Built and integration fixes

  • Keyboard actions animate with pointer actions, subject to reduced-motion preferences. The week state uses one visible-range store. Toasts deduplicate and expose stack text. Mail sync has the empty 202, progress, and connect-toast states. Mail migration 0009 supports duplicate UID memberships; removed the now-obsolete ON CONFLICT (..., message_id) DO NOTHING hotfix clause. Deleted Tasks no longer project into Calendar. Light glass and contrast checks are integrated. Journal day snapshots and cached Calendar rendering are integrated with Notes migration 0024.
  • Week titles use Intl month short formatting (Sep). Removed the Appearance sentence “A 91% paper scrim keeps text readable on this picture.”
  • Fixed WebKit TimeGrid startup: wait one animation frame for virtual layout before setting the initial scroll offset, and ignore scroll settling before initialization. The glass audit now checks that the requested week stays selected.
  • Fixed the glass audit platform shortcut detection to use the platform fields the app uses. The HTTPS e2e front now tunnels WebSocket upgrades for Notes collaboration. Removed an API route.fetch cookie workaround after it caused a late ECONNRESET; the HTTPS origin already supplies its Secure session cookie.
  • Calendar e2e waits for the Journal server acknowledgement before checking persisted state, so an optimistic block is not mistaken for a committed write.
  • Checked migration numbers against origin/dev: Mail 0009 and Notes 0024 were free.

Changed files

  • CLAUDE.md
  • apps/web/e2e/animation-trace.mjs
  • apps/web/e2e/calendar.mjs
  • apps/web/e2e/files-paste.mjs
  • apps/web/e2e/glass-audit.mjs
  • apps/web/e2e/harness.mjs
  • apps/web/e2e/kbd-motion-527.mjs
  • apps/web/e2e/mail-sync-613.mjs
  • apps/web/e2e/settings-shortcut.mjs
  • apps/web/e2e/task-trash-623.mjs
  • apps/web/e2e/theme-variants-506.mjs
  • apps/web/e2e/toaststack-616.mjs
  • apps/web/e2e/weekstate-609.mjs
  • apps/web/package.json
  • apps/web/src/app.d.ts
  • apps/web/src/lib/a11y/inputModality.ts
  • apps/web/src/lib/actions/pillFeedback.test.ts
  • apps/web/src/lib/actions/pillFeedback.ts
  • apps/web/src/lib/calendar/ItemPreview.svelte.test.ts
  • apps/web/src/lib/calendar/agenda.svelte.test.ts
  • apps/web/src/lib/calendar/data.test.ts
  • apps/web/src/lib/calendar/data.ts
  • apps/web/src/lib/calendar/journal.test.ts
  • apps/web/src/lib/calendar/journal.ts
  • apps/web/src/lib/calendar/model.test.ts
  • apps/web/src/lib/calendar/sidebarState.svelte.ts
  • apps/web/src/lib/calendar/window.test.ts
  • apps/web/src/lib/capsule-motion.test.ts
  • apps/web/src/lib/components/AppToaster.svelte
  • apps/web/src/lib/components/SidebarLinks.svelte
  • apps/web/src/lib/components/ToastBody.svelte
  • apps/web/src/lib/components/analytics/BklitTooltipMaterial.test.ts
  • apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/files/FilesSidebar.svelte
  • apps/web/src/lib/mail/MailSidebar.svelte
  • apps/web/src/lib/mail/MailSidebar.svelte.test.ts
  • apps/web/src/lib/mail/syncStatus.test.ts
  • apps/web/src/lib/mail/syncStatus.ts
  • apps/web/src/lib/navigation/modePreload.ts
  • apps/web/src/lib/notifications/InboxPanel.svelte
  • apps/web/src/lib/stores/toasts.svelte.test.ts
  • apps/web/src/lib/stores/toasts.svelte.ts
  • apps/web/src/lib/themes.test.ts
  • apps/web/src/lib/time.test.ts
  • apps/web/src/lib/tray.svelte.test.ts
  • apps/web/src/lib/ui/uiScale.svelte.test.ts
  • apps/web/src/lib/ui/uiScale.svelte.ts
  • apps/web/src/routes/+layout.svelte
  • apps/web/src/routes/calendar/[view]/[date]/+page.svelte
  • apps/web/src/routes/settings/[...path]/+page.svelte
  • apps/web/src/routes/settings/appearance/BackgroundGroup.svelte
  • apps/web/src/routes/settings/mail/MailSection.svelte
  • apps/web/src/routes/settings/mail/MailSection.svelte.test.ts
  • bench/calendar-weekstate-609.mjs
  • bench/hdd-emu.sh
  • bench/kbd-motion-527.mjs
  • bench/mail-sync.py
  • bench/run.sh
  • bench/tab-switch-seed.py
  • bench/tab-switch-trace.py
  • bench/tab-switch.mjs
  • bench/tab-switch.test.mjs
  • bench/task-trash-623.py
  • bench/toast-ring-539.mjs
  • crates/calternal-plugin/src/lib.rs
  • crates/calternal-plugin/src/timing.rs
  • crates/calternal-server/Cargo.toml
  • crates/plugins/calendar/src/lib.rs
  • crates/plugins/calendar/src/view.rs
  • crates/plugins/files/src/lib.rs
  • crates/plugins/files/src/listing.rs
  • crates/plugins/mail/Cargo.toml
  • crates/plugins/mail/migrations/0009_duplicate_uid_memberships.sql
  • crates/plugins/mail/src/cache.rs
  • crates/plugins/mail/src/cache/store.rs
  • crates/plugins/mail/src/imap.rs
  • crates/plugins/mail/src/routes.rs
  • crates/plugins/mail/src/sync.rs
  • crates/plugins/notes/migrations/0024_journal_day_snapshots.sql
  • crates/plugins/notes/src/lib.rs
  • crates/plugins/notes/src/store.rs
  • crates/plugins/notes/src/tasks_api.rs
  • crates/plugins/notes/src/tasks_dav.rs
  • crates/plugins/notes/src/tasks_store.rs
  • docs/DESIGN.md
  • docs/perf/2026-10-01-maildup-626.md
  • docs/perf/2026-10-01-tabswitch-549.md
  • docs/perf/baseline.json
  • docs/perf/runs/tab-switch-2026-10-01-549-after-smoke.json
  • docs/perf/runs/tab-switch-2026-10-01-549-partial.json
  • docs/perf/runs/tab-switch-2026-10-01-549-round3-hdd.json
  • docs/perf/runs/tab-switch-2026-10-01-549-round3-scale-warm.json
  • docs/perf/runs/tab-switch-2026-10-01-549-round4-corrected-smoke.json
  • docs/perf/runs/tab-switch-2026-10-01-549-round4-final-hdd.json
  • docs/perf/task-trash-623.md
  • packages/api-client/src/index.test.ts
  • packages/api-client/src/index.ts
  • packages/ui/src/components/OverlaySurface.svelte
  • packages/ui/src/components/SegmentedControl.svelte
  • packages/ui/src/components/TabBar.svelte
  • packages/ui/src/components/calendar/AgendaList.svelte
  • packages/ui/src/components/calendar/ItemPreview.svelte
  • packages/ui/src/components/calendar/MiniMonth.svelte
  • packages/ui/src/components/calendar/TimeGrid.svelte
  • packages/ui/src/components/calendar/model.ts
  • packages/ui/src/components/calendar/window.ts
  • packages/ui/src/components/menu/Menu.svelte
  • packages/ui/src/date.ts
  • packages/ui/src/index.ts
  • packages/ui/src/motion.ts
  • packages/ui/src/time.ts
  • packages/ui/src/tokens.css
  • tests/adversarial/calendar_event_tags.mjs
  • tests/adversarial/mail-sync.md
  • tests/adversarial/mail_screenshot_fixture.py
  • tests/adversarial/mail_sync_provider.py
  • tests/adversarial/task-trash-623.mjs

Gates and evidence

Output excerpts below are verbatim. cargo fmt --all -- --check exited 0 with no output. All listed clippy commands exited 0:

cargo clippy -p calternal-plugin --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.45s
cargo clippy -p calternal-server --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 47s
cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 39s
cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 30s
cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.15s
cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 22.73s

Per-crate Rust test outputs:

calternal-plugin: test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.44s
calternal-plugin-calendar: test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.35s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
calternal-plugin-files: test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 130.80s
calternal-plugin-mail: test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.89s
calternal-plugin-notes: test result: ok. 167 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 101.48s
Apple replay: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.48s
calternal-server: test result: FAILED. 106 passed; 1 failed; 3 ignored; 0 measured; 0 filtered out; finished in 53.09s
error: test failed, to rerun pass `-p calternal-server --bin calternal-server`

The server failure was wire::tests::live_apps_run_in_separate_processes: nested test full_app_setup_session_config_and_backup timed out at crates/calternal-server/src/wire.rs:7533 with called Result::unwrap() on an Err value: Elapsed(()) while waiting for the dedup scrub. No test expectation was changed.

Web gates:

svelte-check found 0 errors and 0 warnings
Tests  1053 passed (1053)

The production build completed (✓ built in 39.88s). Toast, week-state, TLS Dovecot mail-sync, and task-trash e2e runs passed. Their logs report, respectively: PASS: five Sync now clicks queued once, one non-empty toast rendered, and 12 production screenshots saved; calendar Week state e2e: focus, URL, data, both Today actions, cross-month title, and screenshot matrix passed; PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark with CSP REPORTS mail-sync-613: 0 across 1 pages; and all six task-trash combinations at 390/820/1440 px in Light and Dark passed.

The glass audit produced 166 screenshots per engine, with Chromium and WebKit surface checks passing in segmented runs. The final combined audit rerun did not finish: it timed out in setTheme while switching theme. Linux WebKit cannot rasterize backdrop-filter pixels; Chromium covered those pixel checks and Notes Editor block actions.

Calendar e2e is not green. The latest run ended with:

TimeoutError: textContent: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('.block.moving').locator('.drag-label-start')
error: script "test:e2e:calendar" exited with code 1

The two-User/admin matrices and the one robustness-suite run remain undone. The four-hour stop point was reached, so I stopped without rerunning gates.

Performance

The perf VM lock was occupied, so measurements were local. Profiles and results are in docs/perf/ and bench/. Week-state measurements: 2k updates p50 2.2 µs / p95 5.54 µs; 10k worst-case updates p50 0.93 µs / p95 1.76 µs. Toast dedupe at 390 px: p50 18.5 ms / p95 54.2 ms; 50-toast burst 65.3 ms p50/p95. Mail local sync averaged 2.92 s over 2k messages, with page p50 27.87 ms / p95 51.06 ms. Task-trash debug restore on 1k Task Notes exceeded 120 s and is marked SLOW; there is no valid p50/p95 result. There is no matching baseline for these paths to calculate a regression threshold.

Decisions where DESIGN was silent

  • Use locale-aware Intl short month formatting for the week title.
  • Wait one frame before setting virtual Calendar initial scroll because WebKit reported a false initial offset before width/layout existed.
  • In the HTTPS browser fixture, proxy WebSocket upgrades and let the secure origin provide the session cookie.
  • Make Calendar persistence assertions wait for the server acknowledgement while preserving the optimistic UI behavior.

Screenshots (ignored worktree artifacts, not committed): Chromium glass matrix, WebKit glass matrix.

Cleanup: cargo clean reported Removed 21186 files, 12.9GiB total; removed apps/web/.svelte-kit and apps/web/build.

## Merge round 6 final report Merged approved branches in the requested order: `job/motion-611`, `job/weekstate-609`, `job/toaststack-616`, `job/mailsync-613`, `job/maildup-626`, `job/ghosttask-623`, `job/lightglass-588`, and `job/tabswitch-549`. Also merged the current `origin/dev` once before final gates. No push or deploy was made. Head: `3286cad7208562f3c357fb59204854b0dbab6f84`. ### Built and integration fixes - Keyboard actions animate with pointer actions, subject to reduced-motion preferences. The week state uses one visible-range store. Toasts deduplicate and expose stack text. Mail sync has the empty `202`, progress, and connect-toast states. Mail migration `0009` supports duplicate UID memberships; removed the now-obsolete `ON CONFLICT (..., message_id) DO NOTHING` hotfix clause. Deleted Tasks no longer project into Calendar. Light glass and contrast checks are integrated. Journal day snapshots and cached Calendar rendering are integrated with Notes migration `0024`. - Week titles use `Intl` month `short` formatting (`Sep`). Removed the Appearance sentence “A 91% paper scrim keeps text readable on this picture.” - Fixed WebKit TimeGrid startup: wait one animation frame for virtual layout before setting the initial scroll offset, and ignore scroll settling before initialization. The glass audit now checks that the requested week stays selected. - Fixed the glass audit platform shortcut detection to use the platform fields the app uses. The HTTPS e2e front now tunnels WebSocket upgrades for Notes collaboration. Removed an API `route.fetch` cookie workaround after it caused a late `ECONNRESET`; the HTTPS origin already supplies its Secure session cookie. - Calendar e2e waits for the Journal server acknowledgement before checking persisted state, so an optimistic block is not mistaken for a committed write. - Checked migration numbers against `origin/dev`: Mail `0009` and Notes `0024` were free. ### Changed files - `CLAUDE.md` - `apps/web/e2e/animation-trace.mjs` - `apps/web/e2e/calendar.mjs` - `apps/web/e2e/files-paste.mjs` - `apps/web/e2e/glass-audit.mjs` - `apps/web/e2e/harness.mjs` - `apps/web/e2e/kbd-motion-527.mjs` - `apps/web/e2e/mail-sync-613.mjs` - `apps/web/e2e/settings-shortcut.mjs` - `apps/web/e2e/task-trash-623.mjs` - `apps/web/e2e/theme-variants-506.mjs` - `apps/web/e2e/toaststack-616.mjs` - `apps/web/e2e/weekstate-609.mjs` - `apps/web/package.json` - `apps/web/src/app.d.ts` - `apps/web/src/lib/a11y/inputModality.ts` - `apps/web/src/lib/actions/pillFeedback.test.ts` - `apps/web/src/lib/actions/pillFeedback.ts` - `apps/web/src/lib/calendar/ItemPreview.svelte.test.ts` - `apps/web/src/lib/calendar/agenda.svelte.test.ts` - `apps/web/src/lib/calendar/data.test.ts` - `apps/web/src/lib/calendar/data.ts` - `apps/web/src/lib/calendar/journal.test.ts` - `apps/web/src/lib/calendar/journal.ts` - `apps/web/src/lib/calendar/model.test.ts` - `apps/web/src/lib/calendar/sidebarState.svelte.ts` - `apps/web/src/lib/calendar/window.test.ts` - `apps/web/src/lib/capsule-motion.test.ts` - `apps/web/src/lib/components/AppToaster.svelte` - `apps/web/src/lib/components/SidebarLinks.svelte` - `apps/web/src/lib/components/ToastBody.svelte` - `apps/web/src/lib/components/analytics/BklitTooltipMaterial.test.ts` - `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/files/FilesSidebar.svelte` - `apps/web/src/lib/mail/MailSidebar.svelte` - `apps/web/src/lib/mail/MailSidebar.svelte.test.ts` - `apps/web/src/lib/mail/syncStatus.test.ts` - `apps/web/src/lib/mail/syncStatus.ts` - `apps/web/src/lib/navigation/modePreload.ts` - `apps/web/src/lib/notifications/InboxPanel.svelte` - `apps/web/src/lib/stores/toasts.svelte.test.ts` - `apps/web/src/lib/stores/toasts.svelte.ts` - `apps/web/src/lib/themes.test.ts` - `apps/web/src/lib/time.test.ts` - `apps/web/src/lib/tray.svelte.test.ts` - `apps/web/src/lib/ui/uiScale.svelte.test.ts` - `apps/web/src/lib/ui/uiScale.svelte.ts` - `apps/web/src/routes/+layout.svelte` - `apps/web/src/routes/calendar/[view]/[date]/+page.svelte` - `apps/web/src/routes/settings/[...path]/+page.svelte` - `apps/web/src/routes/settings/appearance/BackgroundGroup.svelte` - `apps/web/src/routes/settings/mail/MailSection.svelte` - `apps/web/src/routes/settings/mail/MailSection.svelte.test.ts` - `bench/calendar-weekstate-609.mjs` - `bench/hdd-emu.sh` - `bench/kbd-motion-527.mjs` - `bench/mail-sync.py` - `bench/run.sh` - `bench/tab-switch-seed.py` - `bench/tab-switch-trace.py` - `bench/tab-switch.mjs` - `bench/tab-switch.test.mjs` - `bench/task-trash-623.py` - `bench/toast-ring-539.mjs` - `crates/calternal-plugin/src/lib.rs` - `crates/calternal-plugin/src/timing.rs` - `crates/calternal-server/Cargo.toml` - `crates/plugins/calendar/src/lib.rs` - `crates/plugins/calendar/src/view.rs` - `crates/plugins/files/src/lib.rs` - `crates/plugins/files/src/listing.rs` - `crates/plugins/mail/Cargo.toml` - `crates/plugins/mail/migrations/0009_duplicate_uid_memberships.sql` - `crates/plugins/mail/src/cache.rs` - `crates/plugins/mail/src/cache/store.rs` - `crates/plugins/mail/src/imap.rs` - `crates/plugins/mail/src/routes.rs` - `crates/plugins/mail/src/sync.rs` - `crates/plugins/notes/migrations/0024_journal_day_snapshots.sql` - `crates/plugins/notes/src/lib.rs` - `crates/plugins/notes/src/store.rs` - `crates/plugins/notes/src/tasks_api.rs` - `crates/plugins/notes/src/tasks_dav.rs` - `crates/plugins/notes/src/tasks_store.rs` - `docs/DESIGN.md` - `docs/perf/2026-10-01-maildup-626.md` - `docs/perf/2026-10-01-tabswitch-549.md` - `docs/perf/baseline.json` - `docs/perf/runs/tab-switch-2026-10-01-549-after-smoke.json` - `docs/perf/runs/tab-switch-2026-10-01-549-partial.json` - `docs/perf/runs/tab-switch-2026-10-01-549-round3-hdd.json` - `docs/perf/runs/tab-switch-2026-10-01-549-round3-scale-warm.json` - `docs/perf/runs/tab-switch-2026-10-01-549-round4-corrected-smoke.json` - `docs/perf/runs/tab-switch-2026-10-01-549-round4-final-hdd.json` - `docs/perf/task-trash-623.md` - `packages/api-client/src/index.test.ts` - `packages/api-client/src/index.ts` - `packages/ui/src/components/OverlaySurface.svelte` - `packages/ui/src/components/SegmentedControl.svelte` - `packages/ui/src/components/TabBar.svelte` - `packages/ui/src/components/calendar/AgendaList.svelte` - `packages/ui/src/components/calendar/ItemPreview.svelte` - `packages/ui/src/components/calendar/MiniMonth.svelte` - `packages/ui/src/components/calendar/TimeGrid.svelte` - `packages/ui/src/components/calendar/model.ts` - `packages/ui/src/components/calendar/window.ts` - `packages/ui/src/components/menu/Menu.svelte` - `packages/ui/src/date.ts` - `packages/ui/src/index.ts` - `packages/ui/src/motion.ts` - `packages/ui/src/time.ts` - `packages/ui/src/tokens.css` - `tests/adversarial/calendar_event_tags.mjs` - `tests/adversarial/mail-sync.md` - `tests/adversarial/mail_screenshot_fixture.py` - `tests/adversarial/mail_sync_provider.py` - `tests/adversarial/task-trash-623.mjs` ### Gates and evidence Output excerpts below are verbatim. `cargo fmt --all -- --check` exited 0 with no output. All listed clippy commands exited 0: ```text cargo clippy -p calternal-plugin --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.45s cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 47s cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 39s cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 30s cargo clippy -p calternal-plugin-mail --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.15s cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 22.73s ``` Per-crate Rust test outputs: ```text calternal-plugin: test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.44s calternal-plugin-calendar: test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.35s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s calternal-plugin-files: test result: ok. 146 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 130.80s calternal-plugin-mail: test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.89s calternal-plugin-notes: test result: ok. 167 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 101.48s Apple replay: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.48s calternal-server: test result: FAILED. 106 passed; 1 failed; 3 ignored; 0 measured; 0 filtered out; finished in 53.09s error: test failed, to rerun pass `-p calternal-server --bin calternal-server` ``` The server failure was `wire::tests::live_apps_run_in_separate_processes`: nested test `full_app_setup_session_config_and_backup` timed out at `crates/calternal-server/src/wire.rs:7533` with `called Result::unwrap() on an Err value: Elapsed(())` while waiting for the dedup scrub. No test expectation was changed. Web gates: ```text svelte-check found 0 errors and 0 warnings Tests 1053 passed (1053) ``` The production build completed (`✓ built in 39.88s`). Toast, week-state, TLS Dovecot mail-sync, and task-trash e2e runs passed. Their logs report, respectively: `PASS: five Sync now clicks queued once, one non-empty toast rendered, and 12 production screenshots saved`; `calendar Week state e2e: focus, URL, data, both Today actions, cross-month title, and screenshot matrix passed`; `PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark` with `CSP REPORTS mail-sync-613: 0 across 1 pages`; and all six task-trash combinations at 390/820/1440 px in Light and Dark passed. The glass audit produced 166 screenshots per engine, with Chromium and WebKit surface checks passing in segmented runs. The final combined audit rerun did not finish: it timed out in `setTheme` while switching theme. Linux WebKit cannot rasterize backdrop-filter pixels; Chromium covered those pixel checks and Notes Editor block actions. Calendar e2e is not green. The latest run ended with: ```text TimeoutError: textContent: Timeout 30000ms exceeded. Call log: - waiting for locator('.block.moving').locator('.drag-label-start') error: script "test:e2e:calendar" exited with code 1 ``` The two-User/admin matrices and the one robustness-suite run remain undone. The four-hour stop point was reached, so I stopped without rerunning gates. ### Performance The perf VM lock was occupied, so measurements were local. Profiles and results are in `docs/perf/` and `bench/`. Week-state measurements: 2k updates p50 2.2 µs / p95 5.54 µs; 10k worst-case updates p50 0.93 µs / p95 1.76 µs. Toast dedupe at 390 px: p50 18.5 ms / p95 54.2 ms; 50-toast burst 65.3 ms p50/p95. Mail local sync averaged 2.92 s over 2k messages, with page p50 27.87 ms / p95 51.06 ms. Task-trash debug restore on 1k Task Notes exceeded 120 s and is marked SLOW; there is no valid p50/p95 result. There is no matching baseline for these paths to calculate a regression threshold. ### Decisions where DESIGN was silent - Use locale-aware `Intl` short month formatting for the week title. - Wait one frame before setting virtual Calendar initial scroll because WebKit reported a false initial offset before width/layout existed. - In the HTTPS browser fixture, proxy WebSocket upgrades and let the secure origin provide the session cookie. - Make Calendar persistence assertions wait for the server acknowledgement while preserving the optimistic UI behavior. Screenshots (ignored worktree artifacts, not committed): [Chromium glass matrix](https://git.kayg.org/attachments/2805595b-0b09-49a6-8902-2ae019c312be), [WebKit glass matrix](https://git.kayg.org/attachments/08d2d06f-18f8-4bdf-a0a9-ab82fe83f62a). Cleanup: `cargo clean` reported `Removed 21186 files, 12.9GiB total`; removed `apps/web/.svelte-kit` and `apps/web/build`.
Author
Owner

Starting round 2 on job/merge-round-6 at 3286cad7208562f3c357fb59204854b0dbab6f84. Branch base (merge-base with current origin/dev) is 3f258302a0f2d6418ff60c9ce22cbb33e008ca99.

I will reproduce the isolated server test and Calendar resize e2e, run the missing isolation/adversarial and glass audit gates, then merge origin/dev once at the end only if its ref has moved.

Starting round 2 on `job/merge-round-6` at `3286cad7208562f3c357fb59204854b0dbab6f84`. Branch base (merge-base with current origin/dev) is `3f258302a0f2d6418ff60c9ce22cbb33e008ca99`. I will reproduce the isolated server test and Calendar resize e2e, run the missing isolation/adversarial and glass audit gates, then merge `origin/dev` once at the end only if its ref has moved.
Author
Owner

Server test comparison: wire::tests::live_apps_run_in_separate_processes passed 5/5 on 3286cad7208562f3c357fb59204854b0dbab6f84 (test durations: 17.34s, 17.79s, 10.53s, 14.56s, 24.98s) and 1/1 on origin/dev at 687ff703136e71e89f8dfba139e93cd0788b25c1 (7.85s). crates/calternal-server/src/wire.rs is identical at both revisions. The scrub poll already checks for phase == "complete" every 50ms, but only allows 10 seconds; I will make that condition wait load-tolerant while keeping a finite deadline.

Server test comparison: `wire::tests::live_apps_run_in_separate_processes` passed 5/5 on `3286cad7208562f3c357fb59204854b0dbab6f84` (test durations: 17.34s, 17.79s, 10.53s, 14.56s, 24.98s) and 1/1 on `origin/dev` at `687ff703136e71e89f8dfba139e93cd0788b25c1` (7.85s). `crates/calternal-server/src/wire.rs` is identical at both revisions. The scrub poll already checks for `phase == "complete"` every 50ms, but only allows 10 seconds; I will make that condition wait load-tolerant while keeping a finite deadline.
Author
Owner

Calendar e2e evidence: the bottom-edge resize passed in the full production run (the moving Journal block showed 09:00 and 11:00, and the saved end was 11:00). The run then failed during inline edit: the PATCH response changed the title but moved the entry from 2026-09-30 to 2026-10-01; the e2e queried the original day and did not find it. The Composer already documents that a bare time parse resolves to today, so I am tracing the editor's date resolution and will preserve the source day unless the text contains an explicit date token.

Calendar e2e evidence: the bottom-edge resize passed in the full production run (the moving Journal block showed `09:00` and `11:00`, and the saved end was `11:00`). The run then failed during inline edit: the PATCH response changed the title but moved the entry from `2026-09-30` to `2026-10-01`; the e2e queried the original day and did not find it. The Composer already documents that a bare time parse resolves to today, so I am tracing the editor's date resolution and will preserve the source day unless the text contains an explicit date token.
Author
Owner

Calendar date-edit fix: the production Calendar e2e now passes the bottom-edge resize checks and the edit of a Journal entry on 2026-09-30; the edit keeps that source date when the text has no date token. The previous run showed the parser's current-day value moving the entry to 2026-10-01. The full e2e then advanced to the next check and timed out waiting for the Open note link after “Turn into note”; I am inspecting the rendered action and accessible name.

Calendar date-edit fix: the production Calendar e2e now passes the bottom-edge resize checks and the edit of a Journal entry on `2026-09-30`; the edit keeps that source date when the text has no date token. The previous run showed the parser's current-day value moving the entry to `2026-10-01`. The full e2e then advanced to the next check and timed out waiting for the `Open note` link after “Turn into note”; I am inspecting the rendered action and accessible name.
Author
Owner

Calendar E2E investigation finding: the Composer's “Open note” toast action renders as a button through the shared ToastBody component. The old test queried it as a link and timed out after the note was created. Updated the locator to assert the visible button label; no user-visible behavior changed. Commit: 7f91461b41.

Calendar E2E investigation finding: the Composer's “Open note” toast action renders as a button through the shared ToastBody component. The old test queried it as a link and timed out after the note was created. Updated the locator to assert the visible button label; no user-visible behavior changed. Commit: 7f91461b417a293edeed7f20870af686192bb4a9.
Author
Owner

Calendar fixture finding: the Files replacement hit the server's atomic BLAKE3 precondition (412 upload destination changed). A temporary trace at the filesystem precondition confirmed that the bytes changed during finalization; the server correctly rejected the stale replacement. I changed this fixture setup to append through the Daily Note body API with its required If-Match ETag, retrying only on 412 and preserving the latest body. The repair-hint assertion remains unchanged. This keeps the test on the single-writer Notes path and removes an unrelated Files-versus-Journal race (#427, DESIGN §§2, 38).

Calendar fixture finding: the Files replacement hit the server's atomic BLAKE3 precondition (412 `upload destination changed`). A temporary trace at the filesystem precondition confirmed that the bytes changed during finalization; the server correctly rejected the stale replacement. I changed this fixture setup to append through the Daily Note body API with its required If-Match ETag, retrying only on 412 and preserving the latest body. The repair-hint assertion remains unchanged. This keeps the test on the single-writer Notes path and removes an unrelated Files-versus-Journal race (#427, DESIGN §§2, 38).
Author
Owner

Calendar repair follow-up: the repair endpoint returned 200 and rewrote the Daily note to - 19:00 dinner with Sam, while /api/v1/notes/journal/{date} kept the previous snapshot because the newly valid line has no stable block ID yet. This matches the existing #549 rule that Journal snapshots stay on the last complete revision until ID reconciliation. I added an explicit /notes/reconcile step after repair in the E2E before asserting the committed Journal view; the repair response and resulting entry assertions remain in place.

Calendar repair follow-up: the repair endpoint returned 200 and rewrote the Daily note to `- 19:00 dinner with Sam`, while `/api/v1/notes/journal/{date}` kept the previous snapshot because the newly valid line has no stable block ID yet. This matches the existing #549 rule that Journal snapshots stay on the last complete revision until ID reconciliation. I added an explicit `/notes/reconcile` step after repair in the E2E before asserting the committed Journal view; the repair response and resulting entry assertions remain in place.
Author
Owner

Calendar E2E follow-up: the midnight-range assertion later queried .block.actual globally. TimeGrid mounts both the source-day segment and its next-day continuation, so strict mode found two identical Journal titles after the resize checks had passed. Scoped the locator to the source date column, which is the segment owning the stable Log identity used by the move assertion.

Calendar E2E follow-up: the midnight-range assertion later queried `.block.actual` globally. TimeGrid mounts both the source-day segment and its next-day continuation, so strict mode found two identical Journal titles after the resize checks had passed. Scoped the locator to the source date column, which is the segment owning the stable Log identity used by the move assertion.
Author
Owner

Calendar E2E copy finding: after the midnight selector fix, the suite reached the future Composer guard and timed out on the old phrase “A log entry is for now or earlier”. The production Composer uses the CONTEXT glossary term “Journal” in its validation message. Updated the test to match the rendered Journal copy.

Calendar E2E copy finding: after the midnight selector fix, the suite reached the future Composer guard and timed out on the old phrase “A log entry is for now or earlier”. The production Composer uses the CONTEXT glossary term “Journal” in its validation message. Updated the test to match the rendered Journal copy.
Author
Owner

Calendar E2E fixture finding: the Agenda deck test uploaded its Daily note under Notes/Journal, but the canonical Daily note folder has moved to Notes (as documented by calendar-perf.mjs). Calendar therefore did not show the fixture line. Updated the test upload to the canonical folder; its attachment and Agenda assertions are unchanged.

Calendar E2E fixture finding: the Agenda deck test uploaded its Daily note under `Notes/Journal`, but the canonical Daily note folder has moved to `Notes` (as documented by `calendar-perf.mjs`). Calendar therefore did not show the fixture line. Updated the test upload to the canonical folder; its attachment and Agenda assertions are unchanged.
Author
Owner

Calendar E2E Agenda finding: after correcting the Daily note folder, the fixture was still absent because Agenda initially loads only two days while this fixture is four days back. The UI has a “Show earlier days” control. The test now activates that control before checking the older fixture row, so it exercises the real paging behavior.

Calendar E2E Agenda finding: after correcting the Daily note folder, the fixture was still absent because Agenda initially loads only two days while this fixture is four days back. The UI has a “Show earlier days” control. The test now activates that control before checking the older fixture row, so it exercises the real paging behavior.
Author
Owner

Round-6 Calendar e2e findings: GridColumn still renders .drag-label-start/.drag-label-end. The bottom resize commits and its live labels pass after the test waits for the single settled source-day block; the top resize also passes with that state wait and source-day scope. The original label timeout was stale rendered geometry, not a removed selector. The Agenda test also expected Enter to expand the attachment deck, but AgendaList now routes Enter to its documented Journal edit action (39c5b07); the test now follows that behavior and selects the line to expand the deck. The full run next exposed a stale column-geometry assertion after ArrowRight: the route changed while the visible header was still settling. I changed it to wait for the user-visible date header; the next full run will verify that and continue the suite.

Round-6 Calendar e2e findings: GridColumn still renders .drag-label-start/.drag-label-end. The bottom resize commits and its live labels pass after the test waits for the single settled source-day block; the top resize also passes with that state wait and source-day scope. The original label timeout was stale rendered geometry, not a removed selector. The Agenda test also expected Enter to expand the attachment deck, but AgendaList now routes Enter to its documented Journal edit action (39c5b07); the test now follows that behavior and selects the line to expand the deck. The full run next exposed a stale column-geometry assertion after ArrowRight: the route changed while the visible header was still settling. I changed it to wait for the user-visible date header; the next full run will verify that and continue the suite.
Author
Owner

Additional Calendar e2e finding: the photo preview flow used click-to-open, but merged #589 changed desktop activity previews to open on hover. The full run reached the photo pile, focused it, and then timed out with no popover. The test now hovers the photo pile and asserts the visible preview, matching the current interaction. The preceding date-window check also confirmed the route/title date but the old virtual-column coordinate helper returned null, so reload checks now assert the visible Calendar title.

Additional Calendar e2e finding: the photo preview flow used click-to-open, but merged #589 changed desktop activity previews to open on hover. The full run reached the photo pile, focused it, and then timed out with no popover. The test now hovers the photo pile and asserts the visible preview, matching the current interaction. The preceding date-window check also confirmed the route/title date but the old virtual-column coordinate helper returned null, so reload checks now assert the visible Calendar title.
Author
Owner

Correction to the photo finding: the clicked pile selects the Calendar activity; Cmd+I opens its anchored Inspector under DESIGN §34. A hover only showed its warm Tooltip, and no preview. The test now selects the pile and uses Cmd+I before asserting the saved photo. The previous comment that this flow should use hover was inaccurate; the hover-preview change affects other item types, while this pile uses the selection/Inspector action.

Correction to the photo finding: the clicked pile selects the Calendar activity; Cmd+I opens its anchored Inspector under DESIGN §34. A hover only showed its warm Tooltip, and no preview. The test now selects the pile and uses Cmd+I before asserting the saved photo. The previous comment that this flow should use hover was inaccurate; the hover-preview change affects other item types, while this pile uses the selection/Inspector action.
Author
Owner

The photo Inspector opens after the selection + Cmd+I path. Its accessible UI has both a filename link and an “Open photo” link, so the old substring locator matched two visible actions and failed strict mode. The e2e now asserts the exact filename link.

The photo Inspector opens after the selection + Cmd+I path. Its accessible UI has both a filename link and an “Open photo” link, so the old substring locator matched two visible actions and failed strict mode. The e2e now asserts the exact filename link.
Author
Owner

The Journal attachment preview showed the recognized ticket.jpg as an accessible photo link (“Open photo ticket.jpg, 01:25”), not an “Open ticket.jpg” button. The old test selector was stale; it now asserts the photo link the User sees.

The Journal attachment preview showed the recognized ticket.jpg as an accessible photo link (“Open photo ticket.jpg, 01:25”), not an “Open ticket.jpg” button. The old test selector was stale; it now asserts the photo link the User sees.
Author
Owner

Correction for the attached JPG: the server range API intentionally presents linked images as “Photo · ” (with ticket.jpg in the card Tooltip). The prior test expectation “Open ticket.jpg” did not match this accessible display name. It now asserts the photo card label and its full-name Tooltip.

Correction for the attached JPG: the server range API intentionally presents linked images as “Photo · <capture time>” (with ticket.jpg in the card Tooltip). The prior test expectation “Open ticket.jpg” did not match this accessible display name. It now asserts the photo card label and its full-name Tooltip.
Author
Owner

Calendar follow-up finding: the Agenda fixture is valid (both Notes Journal and Calendar range return the Sept 28 entry and its three attachments). The stress E2E then fails at Week/1440: the lane cap counts space used by the 34 px +N pill as a content lane, while GridColumn also reserves 34 px on every visible block. The existing 50-overlap stress assertion reports three block slivers. I’m changing the cap so an overflow cluster gives one lane to +N, and will rerun the full E2E.

Calendar follow-up finding: the Agenda fixture is valid (both Notes Journal and Calendar range return the Sept 28 entry and its three attachments). The stress E2E then fails at Week/1440: the lane cap counts space used by the 34 px +N pill as a content lane, while GridColumn also reserves 34 px on every visible block. The existing 50-overlap stress assertion reports three block slivers. I’m changing the cap so an overflow cluster gives one lane to +N, and will rerun the full E2E.
Author
Owner

Calendar E2E finding: the overflow-lane change now passes the 50-entry checks at Day/1440, Week/1440 and Week/390. The later resized-grid assertion is stale: parseFloat() on the --gutter custom property yields NaN (bodyWidth: null), while TimeGrid.measure uses a 56 px fallback; the actual seven-column track is 686.98 px. I am aligning the test gutter fallback with the production sizing rule and rerunning the full flow.

Calendar E2E finding: the overflow-lane change now passes the 50-entry checks at Day/1440, Week/1440 and Week/390. The later resized-grid assertion is stale: `parseFloat()` on the `--gutter` custom property yields NaN (`bodyWidth: null`), while TimeGrid.measure uses a 56 px fallback; the actual seven-column track is 686.98 px. I am aligning the test gutter fallback with the production sizing rule and rerunning the full flow.
Author
Owner

Calendar E2E finding: after using TimeGrid.measure’s 56 px gutter fallback, the resized-grid check passes. The Agenda next fails because both the selected inline deck and the desktop hover preview expose an Open ticket.pdf button. The test already identifies the inline deck as cards; I am scoping the remaining visibility check to that list.

Calendar E2E finding: after using TimeGrid.measure’s 56 px gutter fallback, the resized-grid check passes. The Agenda next fails because both the selected inline deck and the desktop hover preview expose an `Open ticket.pdf` button. The test already identifies the inline deck as `cards`; I am scoping the remaining visibility check to that list.
Author
Owner

Calendar E2E finding: the keyboard check reaches Control+i after Enter opens the Composer and Escape closes it, but that sequence leaves no selected item. The app shortcut correctly opens Day info when there is no selected item; DESIGN §39 says Control+i opens the preview for a selected item. I am adding the supported Space selection before that shortcut. The Agenda fixture is present in the API, but one run stayed at its initial two-day state after the pager action; I am refreshing the app after external fixture writes and explicitly revealing the older day.

Calendar E2E finding: the keyboard check reaches Control+i after Enter opens the Composer and Escape closes it, but that sequence leaves no selected item. The app shortcut correctly opens Day info when there is no selected item; DESIGN §39 says Control+i opens the preview for a selected item. I am adding the supported Space selection before that shortcut. The Agenda fixture is present in the API, but one run stayed at its initial two-day state after the pager action; I am refreshing the app after external fixture writes and explicitly revealing the older day.
Author
Owner

Calendar E2E finding: the final phone long-press test targets 08:30 on yesterday, which the same E2E now fills with Wrote the calendar test (08:30–10:15). The grid correctly starts no creation ghost over that Journal block. I am moving the touch proof to the empty 11:30 slot, before the 13:00 entry.

Calendar E2E finding: the final phone long-press test targets 08:30 on yesterday, which the same E2E now fills with `Wrote the calendar test` (08:30–10:15). The grid correctly starts no creation ghost over that Journal block. I am moving the touch proof to the empty 11:30 slot, before the 13:00 entry.
Author
Owner

Calendar E2E finding: the phone pointer now starts in the empty 11:30 slot, but the live label is 11:30 AM – 11:45 AM because this phone BrowserContext omitted a locale and uses Playwright’s en-US default. The other phone touch context pins en-GB. I am setting the same locale on the full-suite phone context so the existing 24-hour label expectation matches the test setup.

Calendar E2E finding: the phone pointer now starts in the empty 11:30 slot, but the live label is `11:30 AM – 11:45 AM` because this phone BrowserContext omitted a locale and uses Playwright’s en-US default. The other phone touch context pins en-GB. I am setting the same locale on the full-suite phone context so the existing 24-hour label expectation matches the test setup.
Author
Owner

Calendar E2E finding: after the app reload and one earlier-days page, the Sept 28 section appears, but its Show Monday items button detaches during Playwright click because the Agenda IntersectionObserver hydrates that day as it enters the viewport. I am scrolling the section into view and waiting for its visible Journal row, matching the virtualization behavior.

Calendar E2E finding: after the app reload and one earlier-days page, the Sept 28 section appears, but its `Show Monday items` button detaches during Playwright click because the Agenda IntersectionObserver hydrates that day as it enters the viewport. I am scrolling the section into view and waiting for its visible Journal row, matching the virtualization behavior.
Author
Owner

Calendar E2E finding: the deep link opens the correct attached Journal entry at /d/<date>#^<id>, as shown in the production screenshot. The preview exposes aria-label="Journal" in ItemPreview.svelte, while runLogAttachmentProof still waits for a dialog named Log entry. I am updating this stale accessible-name locator to Journal.

Calendar E2E finding: the deep link opens the correct attached Journal entry at `/d/<date>#^<id>`, as shown in the production screenshot. The preview exposes `aria-label="Journal"` in ItemPreview.svelte, while runLogAttachmentProof still waits for a dialog named `Log entry`. I am updating this stale accessible-name locator to `Journal`.
Author
Owner

Round 6 report — stopped after the owner’s ~4-hour limit.

Head: c4a61e8cf0. I merged the one new origin/dev commit (687ff7031, Mail sync) once; the merge was clean. No push or deploy.

Built and committed:

  • Server dedup-scrub test now waits on the condition with a 60-second bounded deadline. The focused test passed five times on this branch and once on origin/dev (7.85s on dev; branch runs: 17.34s, 17.79s, 10.53s, 14.56s, 24.98s).
  • Calendar Journal edit preserves its day.
  • Calendar overflow lanes reserve space for the +N pill, avoiding unreadable sliver blocks.
  • Calendar E2E selectors and waits now follow current Journal, Agenda, Inspector, responsive and touch interactions.

Files changed by this branch: crates/calternal-server/src/wire.rs; apps/web/src/routes/calendar/[view]/[date]/+page.svelte; apps/web/e2e/calendar.mjs; packages/ui/src/components/calendar/GridColumn.svelte; packages/ui/src/components/calendar/TimeGrid.svelte. The merged origin commit also changes crates/plugins/mail/src/cache/store.rs and crates/plugins/mail/src/sync.rs.

Gate output captured before the stop:

cargo fmt --check (before the origin/dev merge): exit 0, no stdout.

bun run check:

svelte-check found 0 errors and 0 warnings

bun run test:

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  02:38:02
   Duration  112.48s (transform 48%, environment 20%, import 16%, tests 11%, setup 4%)

Environment  |component| jsdom was created 48 times · 117.22s total, 29% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)

git diff --check: exit 0, no stdout. cargo clean:

Removed 7673 files, 6.2GiB total

apps/web/build was removed.

Known gaps: the full Calendar E2E did not reach calendar e2e: all flows passed; it reached the Journal attachment preview flow and then exited. The exact failure text was not retained when the long-running tool output was compacted. The current capture is apps/web/artifacts/calendar-427/failure.png and is not committed or attached. The final phone/tablet/desktop light/dark screenshot matrix is incomplete. The two-User plus admin isolation matrix, adversarial suite, combined glass audit, and post-merge Rust clippy/test gates were not run. cargo fmt --check also predates the origin/dev merge. I stopped further work at the owner’s ~4-hour limit as instructed.

Decisions not covered by DESIGN: the +N pill takes width from content lanes; when no lane meets the minimum readable width, the grid shows the pill without block slivers. The Agenda E2E reloads after external fixture writes and scrolls the target section into view so the app’s cache and virtualized list settle. The phone fixture uses en-GB and an empty 11:30 slot to avoid the existing 08:30–10:15 entry.

Round 6 report — stopped after the owner’s ~4-hour limit. Head: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. I merged the one new `origin/dev` commit (687ff7031, Mail sync) once; the merge was clean. No push or deploy. Built and committed: - Server dedup-scrub test now waits on the condition with a 60-second bounded deadline. The focused test passed five times on this branch and once on `origin/dev` (7.85s on dev; branch runs: 17.34s, 17.79s, 10.53s, 14.56s, 24.98s). - Calendar Journal edit preserves its day. - Calendar overflow lanes reserve space for the `+N` pill, avoiding unreadable sliver blocks. - Calendar E2E selectors and waits now follow current Journal, Agenda, Inspector, responsive and touch interactions. Files changed by this branch: `crates/calternal-server/src/wire.rs`; `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`; `apps/web/e2e/calendar.mjs`; `packages/ui/src/components/calendar/GridColumn.svelte`; `packages/ui/src/components/calendar/TimeGrid.svelte`. The merged origin commit also changes `crates/plugins/mail/src/cache/store.rs` and `crates/plugins/mail/src/sync.rs`. Gate output captured before the stop: `cargo fmt --check` (before the origin/dev merge): exit 0, no stdout. `bun run check`: ``` svelte-check found 0 errors and 0 warnings ``` `bun run test`: ``` Test Files 153 passed (153) Tests 1053 passed (1053) Start at 02:38:02 Duration 112.48s (transform 48%, environment 20%, import 16%, tests 11%, setup 4%) Environment |component| jsdom was created 48 times · 117.22s total, 29% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) ``` `git diff --check`: exit 0, no stdout. `cargo clean`: ``` Removed 7673 files, 6.2GiB total ``` `apps/web/build` was removed. Known gaps: the full Calendar E2E did not reach `calendar e2e: all flows passed`; it reached the Journal attachment preview flow and then exited. The exact failure text was not retained when the long-running tool output was compacted. The current capture is `apps/web/artifacts/calendar-427/failure.png` and is not committed or attached. The final phone/tablet/desktop light/dark screenshot matrix is incomplete. The two-User plus admin isolation matrix, adversarial suite, combined glass audit, and post-merge Rust `clippy`/`test` gates were not run. `cargo fmt --check` also predates the origin/dev merge. I stopped further work at the owner’s ~4-hour limit as instructed. Decisions not covered by DESIGN: the `+N` pill takes width from content lanes; when no lane meets the minimum readable width, the grid shows the pill without block slivers. The Agenda E2E reloads after external fixture writes and scrolls the target section into view so the app’s cache and virtualized list settle. The phone fixture uses `en-GB` and an empty 11:30 slot to avoid the existing 08:30–10:15 entry.
Author
Owner

Starting merge round 7a on job/merge-round-7a, based at origin/dev SHA c4a61e8cf090170f35b1bed3350d9de20c83ecd5. I have read CLAUDE.md, docs/DESIGN.md and CONTEXT.md. I will merge the requested branches in order, run each group’s crate gates, and commit one checkpoint per group. No push, deploy or merge into dev.

Starting merge round 7a on `job/merge-round-7a`, based at `origin/dev` SHA `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. I have read CLAUDE.md, docs/DESIGN.md and CONTEXT.md. I will merge the requested branches in order, run each group’s crate gates, and commit one checkpoint per group. No push, deploy or merge into dev.
Author
Owner

Group 1 finding and resolution: the Notes plugin branches both used migration 0024. I kept dev's 0024_journal_day_snapshots, renamed DAV resource projections to 0025, and renamed the authoritative Reminder wire epoch to 0026. The Notes Bridge Daily note identity migration adds only a missing calternal-id: with_daily_note_identity verifies the parsed frontmatter mapping differs only by that key and returns an existing valid UUID unchanged. Its daily_identity_migration_is_lossless_and_idempotent test uses a moved Daily note with CRLF, a YAML comment and custom Tag metadata; stripping the new ID restores the original bytes, and the second pass is identical.

Group 1 finding and resolution: the Notes plugin branches both used migration 0024. I kept dev's `0024_journal_day_snapshots`, renamed DAV resource projections to 0025, and renamed the authoritative Reminder wire epoch to 0026. The Notes Bridge Daily note identity migration adds only a missing `calternal-id`: `with_daily_note_identity` verifies the parsed frontmatter mapping differs only by that key and returns an existing valid UUID unchanged. Its `daily_identity_migration_is_lossless_and_idempotent` test uses a moved Daily note with CRLF, a YAML comment and custom Tag metadata; stripping the new ID restores the original bytes, and the second pass is identical.
Author
Owner

Group 1 gate finding: cargo test -p calternal-plugin-notes reported the known #653 test tests::daily_and_composer_preserve_unrelated_bytes as 404 instead of 200. Per the job instructions, I reran it alone; cargo test -p calternal-plugin-notes tests::daily_and_composer_preserve_unrelated_bytes -- --exact passed (1 passed; 0 failed). The other 181 Notes plugin tests passed in the full run. I kept its assertion unchanged.

Group 1 gate finding: `cargo test -p calternal-plugin-notes` reported the known #653 test `tests::daily_and_composer_preserve_unrelated_bytes` as 404 instead of 200. Per the job instructions, I reran it alone; `cargo test -p calternal-plugin-notes tests::daily_and_composer_preserve_unrelated_bytes -- --exact` passed (`1 passed; 0 failed`). The other 181 Notes plugin tests passed in the full run. I kept its assertion unchanged.
Author
Owner

Group 1 checkpoint: 967ef3f89.

Merged dupwrite-634, reminders-643, notesbridge-644, and davscale-573 in order. The Journal, DAV and reminder Notes migrations are 0024, 0025 and 0026. Notes Daily ID migration was checked: it adds only the missing ID frontmatter, leaves unrelated bytes intact, and is idempotent on a real-shaped Daily note. Added a migration regression for a fresh database and a copied version-24 database; both apply versions 25 and 26 and the DAV projection table is present.

The stale path: Daily Note selector now returns 409 before UUID lookup so collab does not return 404 after UUID migration. Its regression test passed.

Gates:

  • cargo fmt --check: exit 0, no output.
  • cargo clippy -p calternal-collab --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 2m 11s.
  • cargo test -p calternal-collab: test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (all integration test binaries also passed).
  • cargo clippy -p calternal-server --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 57.83s.
  • cargo test -p calternal-server: test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 14.43s.
  • cargo clippy -p calternal-dav --all-targets -- -D warnings: passed; cargo test -p calternal-dav: 46 unit and 37 replay tests passed.
  • cargo clippy -p calternal-imap --all-targets -- -D warnings: passed; all IMAP tests passed (58 total).
  • cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 1m 46s.
  • cargo test -p calternal-plugin-notes: test result: FAILED. 182 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 116.88s; known #653 test daily_and_composer_preserve_unrelated_bytes failed with 404 vs 200. Per instruction, reran alone: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s.
  • cargo test -p calternal-plugin-notes notes_migrations_apply_fresh_and_from_a_dev_schema_copy: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out; finished in 0.41s.

No test expectation was changed. The #653 full-suite failure is load-sensitive and was reported here for follow-up.

Group 1 checkpoint: `967ef3f89`. Merged dupwrite-634, reminders-643, notesbridge-644, and davscale-573 in order. The Journal, DAV and reminder Notes migrations are 0024, 0025 and 0026. Notes Daily ID migration was checked: it adds only the missing ID frontmatter, leaves unrelated bytes intact, and is idempotent on a real-shaped Daily note. Added a migration regression for a fresh database and a copied version-24 database; both apply versions 25 and 26 and the DAV projection table is present. The stale `path:` Daily Note selector now returns 409 before UUID lookup so collab does not return 404 after UUID migration. Its regression test passed. Gates: - `cargo fmt --check`: exit 0, no output. - `cargo clippy -p calternal-collab --all-targets -- -D warnings`: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 11s`. - `cargo test -p calternal-collab`: `test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out` (all integration test binaries also passed). - `cargo clippy -p calternal-server --all-targets -- -D warnings`: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 57.83s`. - `cargo test -p calternal-server`: `test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 14.43s`. - `cargo clippy -p calternal-dav --all-targets -- -D warnings`: passed; `cargo test -p calternal-dav`: 46 unit and 37 replay tests passed. - `cargo clippy -p calternal-imap --all-targets -- -D warnings`: passed; all IMAP tests passed (58 total). - `cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings`: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 46s`. - `cargo test -p calternal-plugin-notes`: `test result: FAILED. 182 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 116.88s`; known #653 test `daily_and_composer_preserve_unrelated_bytes` failed with 404 vs 200. Per instruction, reran alone: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s`. - `cargo test -p calternal-plugin-notes notes_migrations_apply_fresh_and_from_a_dev_schema_copy`: `test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out; finished in 0.41s`. No test expectation was changed. The #653 full-suite failure is load-sensitive and was reported here for follow-up.
Author
Owner

Group 2 progress: merged job/files-631, job/fix-493, job/fix-510, and job/docs-thumb-547 in order. Files migrations are now 0016–0020; 0020_coalesce_folder_reconcile.sql follows the already-used 0019 migration. The upgrade test’s max-version expectation is now 20 because this job explicitly adds #493’s migration.

The #547 merge exposed two cache-integrity issues during integration: the FS API argument order differed from #510’s call sites, and an existing empty legacy .failed marker prevented the new writer from creating a versioned marker. Unified cache reads/publishes on (hash, size, ThumbnailKind) and made stale marker upgrades atomic. Added stale_unversioned_failure_marker_allows_decoder_retry; cargo test -p calternal-fs passed (54 unit tests, 42 storage tests, doc tests 0). Files clippy passed; the full Files suite had 152 pass, 1 ignored, and only the old expected migration value 19 failed; the targeted upgrade test passed after changing it to 20.

Checkpoint SHA so far: 66399e3d4 (docs-thumb merge; not the Group 2 checkpoint).

Group 2 progress: merged `job/files-631`, `job/fix-493`, `job/fix-510`, and `job/docs-thumb-547` in order. Files migrations are now 0016–0020; `0020_coalesce_folder_reconcile.sql` follows the already-used 0019 migration. The upgrade test’s max-version expectation is now 20 because this job explicitly adds #493’s migration. The #547 merge exposed two cache-integrity issues during integration: the FS API argument order differed from #510’s call sites, and an existing empty legacy `.failed` marker prevented the new writer from creating a versioned marker. Unified cache reads/publishes on `(hash, size, ThumbnailKind)` and made stale marker upgrades atomic. Added `stale_unversioned_failure_marker_allows_decoder_retry`; `cargo test -p calternal-fs` passed (54 unit tests, 42 storage tests, doc tests 0). Files clippy passed; the full Files suite had 152 pass, 1 ignored, and only the old expected migration value 19 failed; the targeted upgrade test passed after changing it to 20. Checkpoint SHA so far: `66399e3d4` (docs-thumb merge; not the Group 2 checkpoint).
Author
Owner

Group 2 progress: merged job/perf-496 at 46d67a770. The conflict resolution keeps both per-user Search isolation and the bounded SQLite reconciliation design. Bounded scan pages now skip paired Sidecars while preserving orphan behavior, and staging rebuilds use a separate manifest without updating private readers. Search validation passed: cargo clippy -p calternal-search --all-targets -- -D warnings succeeded; cargo test -p calternal-search passed 73 tests with 3 ignored. The only integration cleanup was removing unused no-budget constructors after SearchIndex writers became explicitly memory-bounded.

Group 2 progress: merged `job/perf-496` at `46d67a770`. The conflict resolution keeps both per-user Search isolation and the bounded SQLite reconciliation design. Bounded scan pages now skip paired Sidecars while preserving orphan behavior, and staging rebuilds use a separate manifest without updating private readers. Search validation passed: `cargo clippy -p calternal-search --all-targets -- -D warnings` succeeded; `cargo test -p calternal-search` passed 73 tests with 3 ignored. The only integration cleanup was removing unused no-budget constructors after SearchIndex writers became explicitly memory-bounded.
Author
Owner

Group 2 progress: merged job/restart-505 at 0a3d5cc67. Search startup now keeps #496's configured writer budget while acquiring the #505 cross-process startup gate, retrying only Tantivy LockBusy, and removing a stale lock only after the OS lock is free. Server shutdown drains queued changes and waits for the actor to drop the writer. The same-Home two-server regression passed. Search gates passed: cargo clippy -p calternal-search --all-targets -- -D warnings succeeded; cargo test -p calternal-search passed 75 tests with 3 ignored. The server test build also exposed a #547 Calendar MIME type mismatch in ThumbnailKind::for_indexed_file; corrected the call to pass Some(mime), and the restart test then passed (1 passed, 110 filtered out).

Group 2 progress: merged `job/restart-505` at `0a3d5cc67`. Search startup now keeps #496's configured writer budget while acquiring the #505 cross-process startup gate, retrying only Tantivy `LockBusy`, and removing a stale lock only after the OS lock is free. Server shutdown drains queued changes and waits for the actor to drop the writer. The same-Home two-server regression passed. Search gates passed: `cargo clippy -p calternal-search --all-targets -- -D warnings` succeeded; `cargo test -p calternal-search` passed 75 tests with 3 ignored. The server test build also exposed a #547 Calendar MIME type mismatch in `ThumbnailKind::for_indexed_file`; corrected the call to pass `Some(mime)`, and the restart test then passed (1 passed, 110 filtered out).
Author
Owner

Group 2 complete. Checkpoint: ea09d8c8b (checkpoint: 7a group 2). The ordered merges include Files/Search/thumb work, bounded Search reconciliation, restart-safe writer ownership, the #513 reconcile barrier, and #520's chaos probe. Rust gates passed for all changed Group 2 crates: calternal-db (27 passed, 1 ignored), calternal-embed (36 passed, 4 ignored), calternal-fs (59 unit + 42 storage passed), calternal-plugin-files (155 passed, 1 ignored), calternal-plugin-calendar (87 passed), calternal-plugin-video (11 passed), calternal-search (75 passed, 3 ignored), calternal-server (108 passed, 3 ignored), and calternal-tags (12 passed). Every listed crate's clippy gate also passed. cargo fmt --all -- --check passed. One compile defect found during integration in Calendar's thumbnail kind call was corrected and covered by the Calendar gates.

Group 2 complete. Checkpoint: `ea09d8c8b` (`checkpoint: 7a group 2`). The ordered merges include Files/Search/thumb work, bounded Search reconciliation, restart-safe writer ownership, the #513 reconcile barrier, and #520's chaos probe. Rust gates passed for all changed Group 2 crates: `calternal-db` (27 passed, 1 ignored), `calternal-embed` (36 passed, 4 ignored), `calternal-fs` (59 unit + 42 storage passed), `calternal-plugin-files` (155 passed, 1 ignored), `calternal-plugin-calendar` (87 passed), `calternal-plugin-video` (11 passed), `calternal-search` (75 passed, 3 ignored), `calternal-server` (108 passed, 3 ignored), and `calternal-tags` (12 passed). Every listed crate's clippy gate also passed. `cargo fmt --all -- --check` passed. One compile defect found during integration in Calendar's thumbnail kind call was corrected and covered by the Calendar gates.
Author
Owner

Notes crate gate finding (known #653 load-sensitive test): full cargo test -p calternal-plugin-notes had 182 passed and one failure in tests::daily_and_composer_preserve_unrelated_bytes (assertion observed 404, expected 200); 183 tests total. The required isolated rerun passed: 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out. The initial short filter selected zero tests, then I reran the fully qualified test name. No expectation was changed.

Notes crate gate finding (known #653 load-sensitive test): full `cargo test -p calternal-plugin-notes` had 182 passed and one failure in `tests::daily_and_composer_preserve_unrelated_bytes` (assertion observed 404, expected 200); 183 tests total. The required isolated rerun passed: `1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out`. The initial short filter selected zero tests, then I reran the fully qualified test name. No expectation was changed.
Author
Owner

Files e2e finding: the real production UI renders a filename fixture ( spaced out name .txt) with computed white-space: nowrap in its one-line FileName mode. The existing assertion in apps/web/e2e/files.mjs requires pre, and the run failed at that assertion after the desktop keyboard, pin, and breadcrumb flows passed. This collapses visible repeated spaces. I am keeping the assertion and changing the one-line FileName rule to preserve whitespace.

Files e2e finding: the real production UI renders a filename fixture (` spaced out name .txt`) with computed `white-space: nowrap` in its one-line FileName mode. The existing assertion in `apps/web/e2e/files.mjs` requires `pre`, and the run failed at that assertion after the desktop keyboard, pin, and breadcrumb flows passed. This collapses visible repeated spaces. I am keeping the assertion and changing the one-line FileName rule to preserve whitespace.
Author
Owner

Second Files e2e finding: after a successful Ctrl+V copy into Archive, the Files row is visible and /api/v1/files/stat?path=Archive%2Fcopy-action.txt returns HTTP 200, but the active element is <body>. Pressing Linux Alt+ArrowUp then leaves the route at /files?path=Archive; the listbox owns that shortcut. In the isolated real-server reproduction, the listbox was focused immediately before Ctrl+V and focus was lost by the time paste completed. The earlier Alt+ArrowUp from Inbox to Home passed. I am fixing the paste refresh so it keeps the list mounted and keyboard focus usable.

Second Files e2e finding: after a successful Ctrl+V copy into `Archive`, the Files row is visible and `/api/v1/files/stat?path=Archive%2Fcopy-action.txt` returns HTTP 200, but the active element is `<body>`. Pressing Linux `Alt+ArrowUp` then leaves the route at `/files?path=Archive`; the listbox owns that shortcut. In the isolated real-server reproduction, the listbox was focused immediately before Ctrl+V and focus was lost by the time paste completed. The earlier `Alt+ArrowUp` from Inbox to Home passed. I am fixing the paste refresh so it keeps the list mounted and keyboard focus usable.
Author
Owner

Finding from the production Files e2e rerun: the suite reaches checkListingGetCoalescing and passes the keyboard transfer checks, then waits for gamma.md in Recent after the main flow renamed Inbox/gamma.md to Inbox/delta.md.

Evidence: real-server API reproduction returned before=["Inbox/gamma.md"], rename status 204, renamed stat status 200, and then after=["Inbox/delta.md"] from /api/v1/files/recent?limit=200. I left the existing e2e assertion unchanged because it conflicts with the live indexed path. The broader run therefore stops before its Trash restore and tus upload checks; I am running those checks separately against the production build.

Finding from the production Files e2e rerun: the suite reaches `checkListingGetCoalescing` and passes the keyboard transfer checks, then waits for `gamma.md` in Recent after the main flow renamed `Inbox/gamma.md` to `Inbox/delta.md`. Evidence: real-server API reproduction returned `before=["Inbox/gamma.md"]`, rename status `204`, renamed stat status `200`, and then `after=["Inbox/delta.md"]` from `/api/v1/files/recent?limit=200`. I left the existing e2e assertion unchanged because it conflicts with the live indexed path. The broader run therefore stops before its Trash restore and tus upload checks; I am running those checks separately against the production build.
Author
Owner

#427 merge-round 7a report

Branch: job/merge-round-7a
Head: 8c42d902ce619780ccb23084b8167c8cfa1f4370
Checkpoints: Group 1 967ef3f89; Group 2 ea09d8c8b; Group 3 8c42d902c.

Merged the authorized Groups 1–3 in the requested order, including the #634 duplicate-write and collab fixes; reminder, Notes bridge and DAV changes; Files/Search/thumbnails; WebDAV, WebCal, app-password cache review, integrations plus review fixtures, MCP events, agent docs, and Appearance migration. The deliberately excluded branches were not merged. Migration numbers were checked against origin/dev before final gates: DAV 0025, reminders 0026, Files 0019–0020, Search 0004, core integrations 0007–0012, Calendar 0005; Mail 0009 remains the version already on dev. The #407 review now compares its #626 SQL fixture to shipped Mail 0009 and applies the real migration set, avoiding a duplicate registration.

Two follow-up fixes are included:

  • packages/ui/src/components/files/FileName.svelte uses white-space: pre for one-line filenames. The existing real filename fixture showed that nowrap collapsed repeated spaces.
  • apps/web/src/lib/files/FilesBrowser.svelte refreshes clipboard writes without a loading-state remount and restores focus after an empty-folder paste mounts a new collection. A real-server reproduction confirmed that the next Alt+ArrowUp then navigates to the parent.

Gate output / results

cargo fmt --all -- --check: exit 0 (no output)

Per-crate cargo clippy -p <crate> --all-targets -- -D warnings and cargo test -p <crate> passed for every changed Rust crate: calternal-server, calternal-api, calternal-auth, calternal-collab, calternal-dav, calternal-db, calternal-embed, calternal-fs, calternal-imap, calternal-plugin, calternal-plugin-calendar, calternal-plugin-files, calternal-plugin-mail, calternal-plugin-money, calternal-plugin-notes, calternal-plugin-notifications, calternal-plugin-video, calternal-search, and calternal-tags. The Notes full run had the known load-sensitive daily_and_composer_preserve_unrelated_bytes failure (404 expected 200); the requested isolated rerun passed: 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out. No test expectation was changed.

svelte-check found 0 errors and 0 warnings
 Test Files  154 passed (154)
      Tests  1073 passed (1073)
✓ built in 38.57s
  Wrote site to "build"
  ✔ done

Additional production evidence:

notes external-write #634: MCP twice + API + editor + WebDAV + editor passed
PASS production Trash restore
PASS production 5 MiB tus upload and indexed file size
FILES SCREENSHOTS PASSED
CSP REPORTS files: 0 across 18 pages
Removed 30043 files, 27.7GiB total

Files screenshots are in the ignored local directory artifacts/files-round7 (208 images covering Files, Shared, Recent and Trash at phone, tablet and desktop widths in light and dark). The fj issue comment interface has no attachment option, so they are not attached to this issue.

Known gaps at the 5-hour limit: Connected Accounts, Mail, and MCP Events end-to-end probes remain unrun. I did not run the two-User/admin matrix, the one robustness suite, or the merged bench profiles. The full apps/web/e2e/files.mjs run passed keyboard transfer and the coalescing check, then stopped at its Recent assertion: it renamed Inbox/gamma.md to Inbox/delta.md earlier but later still waits for gamma.md. A real-server API check returned before=["Inbox/gamma.md"], rename 204, stat 200, and after=["Inbox/delta.md"]. I kept that existing assertion unchanged. The isolated production restore and 5 MiB tus checks passed. I also did not separately apply the migration set to a copied dev-schema database; the per-crate migration tests passed.

Decisions not settled by DESIGN: preserve all spaces in one-line filenames to match the full stored/displayed name; restore keyboard focus to the mounted Files collection after paste into an empty folder; use the already-shipped Mail 0009 migration in review tests rather than registering another version. No further product behavior or test expectation was changed.

#427 merge-round 7a report **Branch:** `job/merge-round-7a` **Head:** `8c42d902ce619780ccb23084b8167c8cfa1f4370` **Checkpoints:** Group 1 `967ef3f89`; Group 2 `ea09d8c8b`; Group 3 `8c42d902c`. Merged the authorized Groups 1–3 in the requested order, including the #634 duplicate-write and collab fixes; reminder, Notes bridge and DAV changes; Files/Search/thumbnails; WebDAV, WebCal, app-password cache review, integrations plus review fixtures, MCP events, agent docs, and Appearance migration. The deliberately excluded branches were not merged. Migration numbers were checked against `origin/dev` before final gates: DAV 0025, reminders 0026, Files 0019–0020, Search 0004, core integrations 0007–0012, Calendar 0005; Mail 0009 remains the version already on dev. The #407 review now compares its #626 SQL fixture to shipped Mail 0009 and applies the real migration set, avoiding a duplicate registration. Two follow-up fixes are included: - `packages/ui/src/components/files/FileName.svelte` uses `white-space: pre` for one-line filenames. The existing real filename fixture showed that `nowrap` collapsed repeated spaces. - `apps/web/src/lib/files/FilesBrowser.svelte` refreshes clipboard writes without a loading-state remount and restores focus after an empty-folder paste mounts a new collection. A real-server reproduction confirmed that the next `Alt+ArrowUp` then navigates to the parent. **Gate output / results** ```text cargo fmt --all -- --check: exit 0 (no output) ``` Per-crate `cargo clippy -p <crate> --all-targets -- -D warnings` and `cargo test -p <crate>` passed for every changed Rust crate: `calternal-server`, `calternal-api`, `calternal-auth`, `calternal-collab`, `calternal-dav`, `calternal-db`, `calternal-embed`, `calternal-fs`, `calternal-imap`, `calternal-plugin`, `calternal-plugin-calendar`, `calternal-plugin-files`, `calternal-plugin-mail`, `calternal-plugin-money`, `calternal-plugin-notes`, `calternal-plugin-notifications`, `calternal-plugin-video`, `calternal-search`, and `calternal-tags`. The Notes full run had the known load-sensitive `daily_and_composer_preserve_unrelated_bytes` failure (404 expected 200); the requested isolated rerun passed: `1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out`. No test expectation was changed. ```text svelte-check found 0 errors and 0 warnings Test Files 154 passed (154) Tests 1073 passed (1073) ✓ built in 38.57s Wrote site to "build" ✔ done ``` Additional production evidence: ```text notes external-write #634: MCP twice + API + editor + WebDAV + editor passed PASS production Trash restore PASS production 5 MiB tus upload and indexed file size FILES SCREENSHOTS PASSED CSP REPORTS files: 0 across 18 pages Removed 30043 files, 27.7GiB total ``` Files screenshots are in the ignored local directory `artifacts/files-round7` (208 images covering Files, Shared, Recent and Trash at phone, tablet and desktop widths in light and dark). The `fj issue comment` interface has no attachment option, so they are not attached to this issue. **Known gaps at the 5-hour limit:** Connected Accounts, Mail, and MCP Events end-to-end probes remain unrun. I did not run the two-User/admin matrix, the one robustness suite, or the merged bench profiles. The full `apps/web/e2e/files.mjs` run passed keyboard transfer and the coalescing check, then stopped at its Recent assertion: it renamed `Inbox/gamma.md` to `Inbox/delta.md` earlier but later still waits for `gamma.md`. A real-server API check returned `before=["Inbox/gamma.md"]`, rename `204`, stat `200`, and `after=["Inbox/delta.md"]`. I kept that existing assertion unchanged. The isolated production restore and 5 MiB tus checks passed. I also did not separately apply the migration set to a copied dev-schema database; the per-crate migration tests passed. **Decisions not settled by DESIGN:** preserve all spaces in one-line filenames to match the full stored/displayed name; restore keyboard focus to the mounted Files collection after paste into an empty folder; use the already-shipped Mail 0009 migration in review tests rather than registering another version. No further product behavior or test expectation was changed.
Author
Owner

Round 2 starting on job/merge-round-7a at 8c42d902ce; branch base is origin/dev c4a61e8cf0. I am running the requested upgrade, adversarial and e2e checks, then will fix the stale Files Recent assertion and report gate output. No push, deploy or merge.

Round 2 starting on job/merge-round-7a at 8c42d902ce619780ccb23084b8167c8cfa1f4370; branch base is origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. I am running the requested upgrade, adversarial and e2e checks, then will fix the stale Files Recent assertion and report gate output. No push, deploy or merge.
Author
Owner

Finding: the Files flow renames gamma.md to delta.md, then opens Recent where only delta.md is the live renamed item. The old assertion typed 'g' and expected gamma.md to receive focus; it could not match the row the User sees after the rename. I changed this check to type 'd' and assert focus on delta.md. This keeps the existing interaction check and corrects its fixture expectation.

Finding: the Files flow renames gamma.md to delta.md, then opens Recent where only delta.md is the live renamed item. The old assertion typed 'g' and expected gamma.md to receive focus; it could not match the row the User sees after the rename. I changed this check to type 'd' and assert focus on delta.md. This keeps the existing interaction check and corrects its fixture expectation.
Author
Owner

Resuming round 2 after the build-host resize. Branch: job/merge-round-7a; base/production head: origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5; current head: 8c42d902ce619780ccb23084b8167c8cfa1f4370. The existing uncommitted change updates the Files Recent e2e to follow delta.md after rename. I am reviewing the saved gate notes and continuing the required upgrade, matrix, robustness and e2e checks without merging new branches.

Resuming round 2 after the build-host resize. Branch: `job/merge-round-7a`; base/production head: `origin/dev` `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`; current head: `8c42d902ce619780ccb23084b8167c8cfa1f4370`. The existing uncommitted change updates the Files Recent e2e to follow `delta.md` after rename. I am reviewing the saved gate notes and continuing the required upgrade, matrix, robustness and e2e checks without merging new branches.
Author
Owner

Finding from the Files e2e after the stale Recent name was corrected: the production Recent view contains delta.md, but typing only d does not focus it. Recent applies type-to-select from the first matching name, and the fixture also contains other d… files. The assertion therefore did not identify the renamed row. I will type the unique prefix delta and keep the keyboard-focus assertion on the row the User sees.

Finding from the Files e2e after the stale Recent name was corrected: the production Recent view contains `delta.md`, but typing only `d` does not focus it. Recent applies type-to-select from the first matching name, and the fixture also contains other `d…` files. The assertion therefore did not identify the renamed row. I will type the unique prefix `delta` and keep the keyboard-focus assertion on the row the User sees.
Author
Owner

Round 2 finding, 2026-10-02: apps/web/e2e/files.mjs reached the keyboard paste flow. The copy API returned 200 for Archive/copy-action.txt and the row became visible. The following Alt+ArrowUp did not restore the Inbox row within 30 seconds. The same step passed in the earlier run, so I am doing one focused reproduction to distinguish a repeatable focus/navigation issue from load sensitivity. The corrected Recent assertion also passed before this later failure.

Round 2 finding, 2026-10-02: `apps/web/e2e/files.mjs` reached the keyboard paste flow. The copy API returned 200 for `Archive/copy-action.txt` and the row became visible. The following `Alt+ArrowUp` did not restore the Inbox row within 30 seconds. The same step passed in the earlier run, so I am doing one focused reproduction to distinguish a repeatable focus/navigation issue from load sensitivity. The corrected Recent assertion also passed before this later failure.
Author
Owner

Round 2 build finding: the origin/dev and head binaries use the job's configured Cargo target. After saving the c4a61e8cf binary, the first head --features mail-test-provider build reused the old calternal-imap artifact and failed in crates/plugins/notes/src/imap.rs with cannot find function apple_markdown in module calternal_imap::projection (the head API). I preserved the baseline binary, ran cargo clean on this job target, and started a clean head rebuild. This is a cross-revision build artifact issue; I made no Rust source change.

Round 2 build finding: the origin/dev and head binaries use the job's configured Cargo target. After saving the c4a61e8cf binary, the first head `--features mail-test-provider` build reused the old `calternal-imap` artifact and failed in `crates/plugins/notes/src/imap.rs` with `cannot find function apple_markdown in module calternal_imap::projection` (the head API). I preserved the baseline binary, ran `cargo clean` on this job target, and started a clean head rebuild. This is a cross-revision build artifact issue; I made no Rust source change.
Author
Owner

Round 2 real-data finding: the Dovecot fixture has 2,000 provider UIDs, including two UIDs with one RFC Message-ID. The old server stores 1,999 mail_messages rows and 2,000 mail_memberships rows; both duplicate UIDs are memberships of the same cached message, matching the #626 per-UID model. I corrected the one-off probe to expect 1,999 content rows and 2,000 UID memberships, and it will compare both counts/checksums across the upgrade.

Round 2 real-data finding: the Dovecot fixture has 2,000 provider UIDs, including two UIDs with one RFC Message-ID. The old server stores 1,999 `mail_messages` rows and 2,000 `mail_memberships` rows; both duplicate UIDs are memberships of the same cached message, matching the #626 per-UID model. I corrected the one-off probe to expect 1,999 content rows and 2,000 UID memberships, and it will compare both counts/checksums across the upgrade.
Author
Owner

Started rev-mcp-api review.

  • Branch: job/rev-mcp-api
  • Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev)
  • Scope: action registry #484; HTTP API, MCP, WebMCP, CLI; generated contracts; agent docs on job/agentdocs-630.
  • Product code will not change. Findings will go in review-findings.md. Each confirmed new finding gets a separate issue after a duplicate search.
  • Issue #427 currently describes Log attachments. The explicit job prompt assigns this issue as the progress record for this review.
  • No builds, dependencies or measured hot paths are added. Full Rust and web builds are excluded by the review brief.
Started rev-mcp-api review. - Branch: `job/rev-mcp-api` - Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`) - Scope: action registry #484; HTTP API, MCP, WebMCP, CLI; generated contracts; agent docs on `job/agentdocs-630`. - Product code will not change. Findings will go in `review-findings.md`. Each confirmed new finding gets a separate issue after a duplicate search. - Issue #427 currently describes Log attachments. The explicit job prompt assigns this issue as the progress record for this review. - No builds, dependencies or measured hot paths are added. Full Rust and web builds are excluded by the review brief.
Author
Owner

Independent data-integrity review started for round 7b.

Branch: job/rev-7b-data
Base SHA: c4a61e8cf090170f35b1bed3350d9de20c83ecd5

I will review the 25 named branch heads, with priority on Undo receipts, live-room write ordering, read-your-writes, Calendar mutations, voice memo files and Tasks. I will record evidence in review-findings.md and check for duplicate issues before I file each confirmed finding. This job makes no product changes.

Decisions: use local job branch heads where present and record the full SHA for each; use origin heads only when a local branch is absent.

Independent data-integrity review started for round 7b. Branch: `job/rev-7b-data` Base SHA: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` I will review the 25 named branch heads, with priority on Undo receipts, live-room write ordering, read-your-writes, Calendar mutations, voice memo files and Tasks. I will record evidence in `review-findings.md` and check for duplicate issues before I file each confirmed finding. This job makes no product changes. Decisions: use local job branch heads where present and record the full SHA for each; use origin heads only when a local branch is absent.
Author
Owner

Starting the rev-consistency static review on job/rev-consistency. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). I read CLAUDE.md, CONTEXT.md and DESIGN.md. Scope follows the job prompt: shared primitives/helpers, token overrides, glass/focus styles and plain-language UI copy. Findings will be recorded in review-findings.md and grouped into deduplicated issues. No product edits or full builds. The existing body of #427 describes attachment bugs; this job uses #427 as its explicitly assigned review tracker.

Starting the rev-consistency static review on `job/rev-consistency`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). I read CLAUDE.md, CONTEXT.md and DESIGN.md. Scope follows the job prompt: shared primitives/helpers, token overrides, glass/focus styles and plain-language UI copy. Findings will be recorded in `review-findings.md` and grouped into deduplicated issues. No product edits or full builds. The existing body of #427 describes attachment bugs; this job uses #427 as its explicitly assigned review tracker.
Author
Owner

rev-a11y accessibility audit started.

Branch: job/rev-a11y. Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev).

Scope: whole web app source audit against WCAG 2.2 AA and DESIGN §§34, 35, 57. No product edits, build, push or deployment. The job assigns #427, although that issue currently describes Log attachment behavior; this audit follows the job prompt and reports here. Findings will go in review-findings.md and separate component-family issues after duplicate searches.

rev-a11y accessibility audit started. Branch: `job/rev-a11y`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (origin/dev). Scope: whole web app source audit against WCAG 2.2 AA and DESIGN §§34, 35, 57. No product edits, build, push or deployment. The job assigns #427, although that issue currently describes Log attachment behavior; this audit follows the job prompt and reports here. Findings will go in review-findings.md and separate component-family issues after duplicate searches.
Author
Owner

Started the source-only design-gap review requested by the rev-design-gaps job.

  • Branch: job/rev-design-gaps
  • Base: origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5
  • Read CLAUDE.md, CONTEXT.md and DESIGN.md. Later owner decisions supersede older rules. OPEN items are excluded.
  • Scope: web app and shared UI source. Record evidence in review-findings.md. Search for duplicates before each new issue. No product edits, full builds, pushes or deployments.
  • Issue #427 has a Log attachment bug as its current body. The explicit job assigns the general design review and its final report to #427; this review follows that scope. It does not claim to fix the original bug.
Started the source-only design-gap review requested by the rev-design-gaps job. - Branch: `job/rev-design-gaps` - Base: `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` - Read CLAUDE.md, CONTEXT.md and DESIGN.md. Later owner decisions supersede older rules. OPEN items are excluded. - Scope: web app and shared UI source. Record evidence in `review-findings.md`. Search for duplicates before each new issue. No product edits, full builds, pushes or deployments. - Issue #427 has a Log attachment bug as its current body. The explicit job assigns the general design review and its final report to #427; this review follows that scope. It does not claim to fix the original bug.
Author
Owner

Started the round 7b defensive security review on job/rev-7b-security. Base SHA: c4a61e8cf090170f35b1bed3350d9de20c83ecd5. The review covers the 25 queued branches in the job brief. I will commit review-findings.md, search for duplicate issues before filing each finding, and leave product files unchanged. The current date is 2026-10-02.

Started the round 7b defensive security review on `job/rev-7b-security`. Base SHA: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. The review covers the 25 queued branches in the job brief. I will commit `review-findings.md`, search for duplicate issues before filing each finding, and leave product files unchanged. The current date is 2026-10-02.
Author
Owner

Confirmed P1 privacy leak in job/maillayouts: sender image permission is revoked on the server, but only the selected message cache entry is invalidated. A second warmed message retains permission and can load sender images. Merge blocker filed as #736. The local inert check used the actual branch cache and confirmed stale permission; no network request was sent. Evidence and regression test idea are in that issue and review-findings.md.

Confirmed P1 privacy leak in `job/maillayouts`: sender image permission is revoked on the server, but only the selected message cache entry is invalidated. A second warmed message retains permission and can load sender images. Merge blocker filed as [#736](https://git.kayg.org/kayg/calternal/issues/736). The local inert check used the actual branch cache and confirmed stale permission; no network request was sent. Evidence and regression test idea are in that issue and `review-findings.md`.
Author
Owner

Review checkpoint at eea364d9b (base c4a61e8cf). Product code is unchanged.

Confirmed source findings:

  • #752: Daily note GET access is classified as read in wire.rs:2375, but the missing-Note branch writes Home content in crates/plugins/notes/src/lib.rs:3871. The agent-doc read example reaches that route.
  • #754: Composer and Task parse handlers are pure previews (composer_api.rs:219, tasks_api.rs:91), but scripts/action_registry.py:146 and wire.rs:2375 require mutation access. Generated CLI/WebMCP ask for change confirmation.
  • #760: generated downloads stop at 1 MiB, but attachment, Version and ZIP routes have no range or continuation input. Mail accepts attachments up to 25 MiB (mail/src/routes.rs:45).

Duplicate checks found existing owners: #667 for missing client-operation idempotency receipts; #688 for unbounded Money transaction lists. These will be recorded without new duplicate issues.

The checked-in TypeScript client matches an offline regeneration with the pinned openapi-typescript 7.13.0. No claim of source-to-OpenAPI regeneration: the server was not built.

Checks so far:

Action registry: 333 operations, 315 generated tools
Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps
...........
----------------------------------------------------------------------
Ran 11 tests in 0.333s

OK
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Review checkpoint at `eea364d9b` (base `c4a61e8cf`). Product code is unchanged. Confirmed source findings: - #752: Daily note GET access is classified as read in `wire.rs:2375`, but the missing-Note branch writes Home content in `crates/plugins/notes/src/lib.rs:3871`. The agent-doc read example reaches that route. - #754: Composer and Task parse handlers are pure previews (`composer_api.rs:219`, `tasks_api.rs:91`), but `scripts/action_registry.py:146` and `wire.rs:2375` require mutation access. Generated CLI/WebMCP ask for change confirmation. - #760: generated downloads stop at 1 MiB, but attachment, Version and ZIP routes have no range or continuation input. Mail accepts attachments up to 25 MiB (`mail/src/routes.rs:45`). Duplicate checks found existing owners: #667 for missing client-operation idempotency receipts; #688 for unbounded Money transaction lists. These will be recorded without new duplicate issues. The checked-in TypeScript client matches an offline regeneration with the pinned `openapi-typescript 7.13.0`. No claim of source-to-OpenAPI regeneration: the server was not built. Checks so far: ```text Action registry: 333 operations, 315 generated tools Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps ........... ---------------------------------------------------------------------- Ran 11 tests in 0.333s OK .... ---------------------------------------------------------------------- Ran 4 tests in 0.002s OK ```
Author
Owner

Round 7b review findings:

  • BLOCKER: #731, saved Task view PUT can erase another client's acknowledged filter edit. Evidence: task_views.rs:180,194,202 at f620390c724ee08540d38b0ba69c3d12225fc1e4. The route replaces a complete client view with no client revision. The file CAS checks the server's current read. Static proof.
  • BLOCKER: #777, Calendar Undo can erase a later Journal edit. Evidence: Calendar +page.svelte:851, journal.ts:30,58,67, edits.ts:353 at 5f7fdb96706aca0c457a6b7851f548f7f618ac85. The old inverse uses an ETag refreshed by a later read. This is inherited in the reviewed head.
  • Calendar edit retries retain a rejected ETag. Two retries of the real branch Journal adapter send the same R0 after a fixture update to R1. Both get 412. Non-blocking; issue filing in progress.

The two Calendar checks import the reviewed adapter and stub only transport. They do not use a live server or change User files. Full output is in review-findings.md.

No product changes. Review continues with Note caches, voice memo projections, editor decorations and migration interactions.

Round 7b review findings: - BLOCKER: #731, saved Task view PUT can erase another client's acknowledged filter edit. Evidence: `task_views.rs:180,194,202` at `f620390c724ee08540d38b0ba69c3d12225fc1e4`. The route replaces a complete client view with no client revision. The file CAS checks the server's current read. Static proof. - BLOCKER: #777, Calendar Undo can erase a later Journal edit. Evidence: Calendar `+page.svelte:851`, `journal.ts:30,58,67`, `edits.ts:353` at `5f7fdb96706aca0c457a6b7851f548f7f618ac85`. The old inverse uses an ETag refreshed by a later read. This is inherited in the reviewed head. - Calendar edit retries retain a rejected ETag. Two retries of the real branch Journal adapter send the same R0 after a fixture update to R1. Both get 412. Non-blocking; issue filing in progress. The two Calendar checks import the reviewed adapter and stub only transport. They do not use a live server or change User files. Full output is in `review-findings.md`. No product changes. Review continues with Note caches, voice memo projections, editor decorations and migration interactions.
Author
Owner

Confirmed static findings at review base c4a61e8cf090170f35b1bed3350d9de20c83ecd5, branch job/rev-consistency:

  • #727: three byte-size formatters. Mail divides by 1024 and does not use the number locale; Files uses decimal units. Evidence: MailView.svelte:148,650, files/model.ts:329-342, AdminSection.svelte:61-70.
  • #729: the motion guard passes helper-call timings it does not scan. Evidence: DraftStack.svelte:60-62, AttachmentDeck.svelte:107,303-304, flip.ts:374-380. Node probe returns [] for the literal fly helper; the CSS control is detected.
  • #730: browser download anchor/object-URL cleanup is copied in Recent, Public links and recovery-file saving despite files/transfer.ts:199-218 owning the action.
  • #799: ProgressiveBlur declares identical filter steps per edge and tokens.css declares another unused selector family. Keep one filter recipe; keep the distinct edge masks.

Duplicate evidence was added to #658 (focus token contracts/local rings), #73 (menu copy feedback) and #308 (local hidden-label recipes). Further material/token/copy findings will go to their existing owners. Product source is unchanged. The first findings are committed in b18d62f24; the remaining notes are being recorded now.

Confirmed static findings at review base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`, branch `job/rev-consistency`: - #727: three byte-size formatters. Mail divides by 1024 and does not use the number locale; Files uses decimal units. Evidence: `MailView.svelte:148,650`, `files/model.ts:329-342`, `AdminSection.svelte:61-70`. - #729: the motion guard passes helper-call timings it does not scan. Evidence: `DraftStack.svelte:60-62`, `AttachmentDeck.svelte:107,303-304`, `flip.ts:374-380`. Node probe returns `[]` for the literal `fly` helper; the CSS control is detected. - #730: browser download anchor/object-URL cleanup is copied in Recent, Public links and recovery-file saving despite `files/transfer.ts:199-218` owning the action. - #799: ProgressiveBlur declares identical filter steps per edge and tokens.css declares another unused selector family. Keep one filter recipe; keep the distinct edge masks. Duplicate evidence was added to #658 (focus token contracts/local rings), #73 (menu copy feedback) and #308 (local hidden-label recipes). Further material/token/copy findings will go to their existing owners. Product source is unchanged. The first findings are committed in `b18d62f24`; the remaining notes are being recorded now.
Author
Owner

The real-data upgrade probe exposed a local verification limit: on the old binary, uploads of the repository's valid 2×2 PNG and a Markdown file both produced .failed thumbnail markers containing decoder-failed-v1; their files.thumbnail jobs completed without last_error, and the Files API reported has_thumbnail: false. The staged media runtime has local-nproc-limit=3486, while the shared account had over 4,800 threads during the run. I am recording the Files identities and continuing the migration/data-integrity checks; thumbnail rendering remains unverified under this host load. No product behavior change is inferred from this evidence.

The real-data upgrade probe exposed a local verification limit: on the old binary, uploads of the repository's valid 2×2 PNG and a Markdown file both produced `.failed` thumbnail markers containing `decoder-failed-v1`; their `files.thumbnail` jobs completed without `last_error`, and the Files API reported `has_thumbnail: false`. The staged media runtime has `local-nproc-limit=3486`, while the shared account had over 4,800 threads during the run. I am recording the Files identities and continuing the migration/data-integrity checks; thumbnail rendering remains unverified under this host load. No product behavior change is inferred from this evidence.
Author
Owner

Accessibility source audit progress on branch job/rev-a11y; first report commit 4d9126541123987e5d0fc40a2a2ac3dc0885a2b3, initial base c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

New component-family issues after duplicate searches:

  • #740: hidden focus candidates and phone Settings initial focus.
  • #741: PDF preview exposes canvases without document text.
  • #742: search scope/subfolder controls have no keyboard route.
  • #744: warm tooltip closes before the pointer can enter its text.
  • #745: interactive TagPill touch targets remain below the project floor.

A small browser probe bundles the actual focusTrap.ts action, with no product build or product change. It reproduces both #740 failures: hidden explicit initial focus leaves focus outside, and a hidden final candidate lets Tab escape. This is behavior evidence, not visual review. Full production screenshots and assistive-technology runs are not part of this read-mostly job.

The existing #658 owns focus-ring styling. The report will add duplicate evidence there instead of filing another ring-style issue. Review continues through Composer, Photos and the remaining route families.

Accessibility source audit progress on branch `job/rev-a11y`; first report commit `4d9126541123987e5d0fc40a2a2ac3dc0885a2b3`, initial base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. New component-family issues after duplicate searches: - #740: hidden focus candidates and phone Settings initial focus. - #741: PDF preview exposes canvases without document text. - #742: search scope/subfolder controls have no keyboard route. - #744: warm tooltip closes before the pointer can enter its text. - #745: interactive TagPill touch targets remain below the project floor. A small browser probe bundles the actual `focusTrap.ts` action, with no product build or product change. It reproduces both #740 failures: hidden explicit initial focus leaves focus outside, and a hidden final candidate lets Tab escape. This is behavior evidence, not visual review. Full production screenshots and assistive-technology runs are not part of this read-mostly job. The existing #658 owns focus-ring styling. The report will add duplicate evidence there instead of filing another ring-style issue. Review continues through Composer, Photos and the remaining route families.
Author
Owner

Confirmed a second static merge blocker: #816, new media parser in job/voicefiles-620 has no nesting bound. iso_box_tracks recurses at crates/calternal-media/src/lib.rs:367; Files and Search call it during indexing of User-controlled files. Input byte and per-call loop limits do not bound stack use. No crash payload was executed. The issue records repair and regression coverage. Review continues with the remaining cross-plugin changes.

Confirmed a second static merge blocker: [#816](https://git.kayg.org/kayg/calternal/issues/816), new media parser in `job/voicefiles-620` has no nesting bound. `iso_box_tracks` recurses at `crates/calternal-media/src/lib.rs:367`; Files and Search call it during indexing of User-controlled files. Input byte and per-call loop limits do not bound stack use. No crash payload was executed. The issue records repair and regression coverage. Review continues with the remaining cross-plugin changes.
Author
Owner

Finished the rev-consistency static review.

  • Branch: job/rev-consistency.
  • Base and fetched origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
  • Head: a5252c5c63e5824709a75c90e3b553ed7f5ae59c.
  • Built: review-findings.md, with 11 grouped findings, source evidence, shared owners, expected results and test ideas. This is a review artifact; no product code changed.
  • Files: review-findings.md only, 337 lines. Four atomic documentation commits. No push, deploy or new merge commit.
  • New issues: #727 (byte-size labels), #729 (motion guard coverage), #730 (browser saves), #799 (progressive filter recipe).
  • Existing owners received current evidence: #658, #73, #308, #588, #307, #546 and #726. #412/#502 remain the specific App Password/Location copy owners.
  • Reuse checks: clipboard writes, toast state and Quick Look media have shared owners. JobRow wraps SettingsRow. Different row names alone were not treated as duplication.

Final verification

git fetch origin && git merge origin/dev completed once before final checks.
The fetched revision is still c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
Merge output, verbatim:

Already up to date.

git diff --check and git diff --check origin/dev...HEAD exited 0 with no
output. The branch changes only this report. No product source, dependency,
test expectation, migration or build artifact was committed.

node apps/web/scripts/check-type-tokens.mjs exited 0. Output, verbatim:

Text sizes and UI shape values use shared role tokens.

node apps/web/scripts/check-motion-tokens.mjs exited 0. Output, verbatim:

UI transitions and animation options use shared motion tokens or documented exceptions.

The helper-call probe in F2 shows a coverage gap despite this passing guard.

node apps/web/scripts/check-user-storage.mjs exited 1 before scanning source.
This worktree does not have its TypeScript dependency installed. Output,
verbatim:

node:internal/modules/package_json_reader:314
  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from /home/kayg/Developer/calternal-wt/rev-consistency/apps/web/scripts/check-user-storage.mjs
    at Object.getPackageJSONURL (node:internal/modules/package_json_reader:314:9)
    at packageResolve (node:internal/modules/esm/resolve:772:81)
    at moduleResolve (node:internal/modules/esm/resolve:859:18)
    at defaultResolve (node:internal/modules/esm/resolve:989:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:747:20)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:724:38)
    at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:320:38)
    at ModuleJob._link (node:internal/modules/esm/module_job:182:49) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v22.23.3

The job asks for a read-mostly review without full builds. No Rust crate or web
product file changed, so Cargo format/clippy/test and web check/test were not
run. No route or background job changed, so no performance profile or live
adversarial run was required. No dependency version was selected or changed.

Known gaps

  • Static evidence does not prove rendered contrast, target sizes, input
    behaviour or performance. Each issue gives a test idea for its fix.
  • The browser-storage guard needs the installed TypeScript dependency.
  • Product fixes are outside this job. All reported defects remain with their
    linked implementation issues.

UX gaps closed

None. This job records and routes evidence; it does not change the UI.

UX gaps left

The reported focus, material, scaling, copy-feedback and plain-language gaps
remain. Runtime checks for keyboard, touch, screen readers, Undo and live
updates belong to the implementation jobs. Their screenshot sets must cover
390, 820 and 1440 px, light/dark, with macOS emulation.

Cleanup

Ran cargo clean with CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and worktree TMPDIR. Kept the preset job target directory. Output, verbatim:

     Removed 1 file, 356B total
No build output at apps/web/build
No build output at apps/web/.svelte-kit/output

Decisions

Followed the job prompt's review scope despite #427's attachment-report body. Used static checks, as this job explicitly avoids full builds. Added overlapping evidence to existing issues. Filed the script guard gap as a follow-up to closed #477. Applied the newer 2026-10-02 silent-privacy instruction to the opt-in conflict in #726. No product design decision or implementation was made.

The worktree is clean. This review does not close #427 or any finding issue.

Finished the rev-consistency static review. - Branch: `job/rev-consistency`. - Base and fetched `origin/dev`: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. - Head: `a5252c5c63e5824709a75c90e3b553ed7f5ae59c`. - Built: `review-findings.md`, with 11 grouped findings, source evidence, shared owners, expected results and test ideas. This is a review artifact; no product code changed. - Files: `review-findings.md` only, 337 lines. Four atomic documentation commits. No push, deploy or new merge commit. - New issues: [#727](https://git.kayg.org/kayg/calternal/issues/727) (byte-size labels), [#729](https://git.kayg.org/kayg/calternal/issues/729) (motion guard coverage), [#730](https://git.kayg.org/kayg/calternal/issues/730) (browser saves), [#799](https://git.kayg.org/kayg/calternal/issues/799) (progressive filter recipe). - Existing owners received current evidence: #658, #73, #308, #588, #307, #546 and #726. #412/#502 remain the specific App Password/Location copy owners. - Reuse checks: clipboard writes, toast state and Quick Look media have shared owners. JobRow wraps SettingsRow. Different row names alone were not treated as duplication. ## Final verification `git fetch origin && git merge origin/dev` completed once before final checks. The fetched revision is still `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Merge output, verbatim: ```text Already up to date. ``` `git diff --check` and `git diff --check origin/dev...HEAD` exited 0 with no output. The branch changes only this report. No product source, dependency, test expectation, migration or build artifact was committed. `node apps/web/scripts/check-type-tokens.mjs` exited 0. Output, verbatim: ```text Text sizes and UI shape values use shared role tokens. ``` `node apps/web/scripts/check-motion-tokens.mjs` exited 0. Output, verbatim: ```text UI transitions and animation options use shared motion tokens or documented exceptions. ``` The helper-call probe in F2 shows a coverage gap despite this passing guard. `node apps/web/scripts/check-user-storage.mjs` exited 1 before scanning source. This worktree does not have its TypeScript dependency installed. Output, verbatim: ```text node:internal/modules/package_json_reader:314 throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null); ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from /home/kayg/Developer/calternal-wt/rev-consistency/apps/web/scripts/check-user-storage.mjs at Object.getPackageJSONURL (node:internal/modules/package_json_reader:314:9) at packageResolve (node:internal/modules/esm/resolve:772:81) at moduleResolve (node:internal/modules/esm/resolve:859:18) at defaultResolve (node:internal/modules/esm/resolve:989:11) at #cachedDefaultResolve (node:internal/modules/esm/loader:747:20) at ModuleLoader.resolve (node:internal/modules/esm/loader:724:38) at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:320:38) at ModuleJob._link (node:internal/modules/esm/module_job:182:49) { code: 'ERR_MODULE_NOT_FOUND' } Node.js v22.23.3 ``` The job asks for a read-mostly review without full builds. No Rust crate or web product file changed, so Cargo format/clippy/test and web check/test were not run. No route or background job changed, so no performance profile or live adversarial run was required. No dependency version was selected or changed. ## Known gaps - Static evidence does not prove rendered contrast, target sizes, input behaviour or performance. Each issue gives a test idea for its fix. - The browser-storage guard needs the installed TypeScript dependency. - Product fixes are outside this job. All reported defects remain with their linked implementation issues. ## UX gaps closed None. This job records and routes evidence; it does not change the UI. ## UX gaps left The reported focus, material, scaling, copy-feedback and plain-language gaps remain. Runtime checks for keyboard, touch, screen readers, Undo and live updates belong to the implementation jobs. Their screenshot sets must cover 390, 820 and 1440 px, light/dark, with macOS emulation. ## Cleanup Ran `cargo clean` with `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=4` and worktree `TMPDIR`. Kept the preset job target directory. Output, verbatim: ```text Removed 1 file, 356B total No build output at apps/web/build No build output at apps/web/.svelte-kit/output ``` ## Decisions Followed the job prompt's review scope despite #427's attachment-report body. Used static checks, as this job explicitly avoids full builds. Added overlapping evidence to existing issues. Filed the script guard gap as a follow-up to closed #477. Applied the newer 2026-10-02 silent-privacy instruction to the opt-in conflict in #726. No product design decision or implementation was made. The worktree is clean. This review does not close #427 or any finding issue.
Author
Owner

Source review findings for #427, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. No product changes.

  • #758: Saved searches: Trash and Unpin have no Undo action
  • #768: Reminders: Remove, Done and Snooze have no Undo action
  • #770: Files: restoring a Version has no Undo action
  • #771: Mail: read-state changes leave sidebar unread counts stale
  • #772: Calendar settings: failed format saves leave unsaved choices selected
  • #773: Search deep links: same-route query changes do not restore the palette
  • #775: Photos: changing a burst key photo has no Undo action
  • #776: Notifications: Delete has no Undo action
  • #827: Calendar: default Event calendar has no synced Settings choice
  • #828: Location: deleting a Saved place has no Undo action

Each new issue has its DESIGN reference, file:line evidence, observed and expected behavior, duplicate check and test idea. The committed review-findings.md also maps 18 observations to existing tickets. Place-reminder UI work remains under #393; Task IDs and saved-search rename/pin are already implemented.

Source review findings for #427, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. No product changes. - #758: Saved searches: Trash and Unpin have no Undo action - #768: Reminders: Remove, Done and Snooze have no Undo action - #770: Files: restoring a Version has no Undo action - #771: Mail: read-state changes leave sidebar unread counts stale - #772: Calendar settings: failed format saves leave unsaved choices selected - #773: Search deep links: same-route query changes do not restore the palette - #775: Photos: changing a burst key photo has no Undo action - #776: Notifications: Delete has no Undo action - #827: Calendar: default Event calendar has no synced Settings choice - #828: Location: deleting a Saved place has no Undo action Each new issue has its DESIGN reference, file:line evidence, observed and expected behavior, duplicate check and test idea. The committed `review-findings.md` also maps 18 observations to existing tickets. Place-reminder UI work remains under #393; Task IDs and saved-search rename/pin are already implemented.
Author
Owner

Round 7b review update. Two local fixture checks confirmed stale cache faults, both non-blocking because no server write loss was shown:

  • #826: a read issued before a save can replace the acknowledged month report in memory and browser storage. The mutation does not fence pending reads.
  • #829: an online warm Note open skips its authoritative read and keeps a deleted cached body editable after the live room refuses it.

Evidence and exact test output are in review-findings.md at commit 68f834ba8 plus the next atomic review commit. No product files changed. The fixture tests run reviewed store/function code with mocked collaborators; they do not claim a live-server reproduction.

Round 7b review update. Two local fixture checks confirmed stale cache faults, both non-blocking because no server write loss was shown: - #826: a read issued before a save can replace the acknowledged month report in memory and browser storage. The mutation does not fence pending reads. - #829: an online warm Note open skips its authoritative read and keeps a deleted cached body editable after the live room refuses it. Evidence and exact test output are in review-findings.md at commit 68f834ba8 plus the next atomic review commit. No product files changed. The fixture tests run reviewed store/function code with mocked collaborators; they do not claim a live-server reproduction.
Author
Owner

Finished rev-mcp-api review.

Branch: job/rev-mcp-api
Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev)
Head: 4a82c6414156f4362e0571afea542a8d0971b786
Changed file: review-findings.md only. Four atomic review commits. Worktree clean.

Review delivered: 333 API operation bindings, shared adapter metadata and behavior review, and the separate agent-doc branch review. Eight new issues: #752, #754, #760, #814, #815, #817, #818 and #821. Existing owners retained for two findings: #667 and #688.

High-priority findings: the missing Daily note read branch writes content under read classification (#752); the CLI can replay a non-idempotent create after an ambiguous gateway response (#814). These are source findings, not claims of live reproduction.

Final gate output and decisions follow verbatim in the committed report. No Rust or web product code changed. Clippy, Rust tests, full web checks and full builds were excluded by this read-mostly job. One fetch and merge attempt before final checks returned Already up to date. cargo clean completed; web build output was absent. No push, deployment or integration merge.


MCP, API, CLI and WebMCP review

Issue: #427. Review scope: #484 and DESIGN §§14, 18, 33, 34, 41 and 48.
Review base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev).
Branch: job/rev-mcp-api.
Agent docs source: origin/job/agentdocs-630 at
183ac356359f1f84afad30932f02e07323ddb7ed.

Review result: eight new issues and two confirmed findings assigned to existing
issues. High-priority findings concern read-only content writes (#752) and
unsafe CLI replay (#814). The other issues concern parser access (#754), large
downloads (#760), required headers (#815), legacy contracts (#817), input
errors (#818) and help (#821). No product code changed.

This is a source review. It changes no product code. It does not prove live
transport or authorization behavior. Findings below separate source evidence
from results observed on a running server.

Initial checks

The registry has 333 HTTP operations across 270 paths. It has 315 generated
tools. The route scope counts are 250 data, 44 account and 39 admin.
The parity inventory has no missing generated adapter. This result does not
prove that the adapters can complete each operation.

Commands and output:

$ python3 scripts/action_registry.py --check
Action registry: 333 operations, 315 generated tools
$ python3 scripts/parity_matrix.py --check
Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps

Confirmed source findings

The review covers route and schema parity, tool names and input groups,
pagination, response bounds, retry safety, actionable errors and agent docs.
Each confirmed finding includes file and line evidence, the expected
behavior, a regression test idea and its issue or existing duplicate.

Decisions

  • Issue #427 has a Log attachment report. Use it for review progress because
    the job prompt assigns it. Follow the explicit API review scope.
  • Review existing references without changing product code or dependencies.
  • Use offline contract checks. Do not run full Rust or web builds for this
    read-mostly review, as required by the job brief.

F01 — Daily note reads must preserve read-only App Password access

Priority: High. Issue: #752.

  • crates/calternal-server/src/wire.rs:2320 derives App Password access from is_read_request; :2375 treats GET as read.
  • crates/plugins/notes/src/lib.rs:3867 enters the missing Daily note branch and :3871 writes a new Note, indexes it and updates adjacent Daily note navigation.
  • scripts/action_registry.py:146 marks the GET action read-only. crates/calternal-server/src/mcp.rs:797 describes calternal_today as a read.
  • On origin/job/agentdocs-630 at 183ac356359f1f84afad30932f02e07323ddb7ed, crates/calternal-server/src/agent_docs/skill_intro.md:29 recommends a read-only MCP App Password and :59 uses calternal_today as the first read.

Source review shows that a route classified as a read contains a content-creation branch. Its authorization and its tool hints do not describe that branch. No live authorization test was run.

Expected: Keep read-only requests free of content writes. Missing Daily notes need a read result or an explicit write action that checks write access on the server. Preserve the ordinary Daily note flow for a User who can write. Update the tool hints and agent examples from the same action intent.

Test idea: Add a route-level regression for a missing Daily note under a read-only principal. Assert unchanged Home content and adjacent Daily notes. Check the API, MCP and generated adapters against the same contract. Keep existing status expectations.

Duplicate check: Searched all issue states for daily, read-only, and MCP; read #661. #661 concerns a viewing-triggered rewrite of an existing Note in the editor, not the missing-Daily-note creation branch.

F02 — Pure Composer and Task parser tools incorrectly require write access

Priority: Medium. Issue: #754.

  • crates/plugins/notes/src/composer_api.rs:1 documents a pure preview function with no reads or writes; :219 returns project(...).
  • crates/plugins/notes/src/tasks_api.rs:91 parses Task text and returns a preview without a content write.
  • scripts/action_registry.py:146 classifies every POST except ZIP download as a mutation. Both notes_composer_parse and parse have read_only: false and destructive: true in contracts/actions.json.
  • crates/calternal-server/src/wire.rs:2375 has only the ZIP read exception. crates/calternal-cli/src/remote_commands.rs:294 requires --confirm; apps/web/src/lib/webmcp/generated.ts:88 confirms every action not marked read-only.

A read-only API or MCP App Password cannot use the parsers. CLI and WebMCP request change confirmation for pure previews. The inventory gate still passes.

Expected: Declare read intent for the pure parser actions once and use it in route access and adapter hints. Keep authentication, body validation and CPU bounds. CLI and WebMCP must allow the preview without change confirmation.

Test idea: Add metadata assertions and read-only route tests for both parser actions. Add adapter tests that the pure previews do not ask for confirmation and do not write Home content.

Duplicate check: Searched all issue states for notes_composer_parse, parser, read-only and parity. No specific parser-access issue found.

F03 — Generated download tools cannot read valid attachments, Versions or ZIPs above 1 MiB

Priority: Medium. Issue: #760.

  • crates/calternal-server/src/mcp.rs:311, crates/calternal-cli/src/remote_commands.rs:343 and packages/api-client/src/index.ts:456 stop generated responses above 1 MiB.
  • contracts/actions.json declares no Range or continuation input for mail_download_attachment, download_version or download_zip; scripts/action_registry.py:49 supplies Range only to other download actions.
  • crates/plugins/mail/src/routes.rs:45 permits attachments up to 25 MiB; :1439 reads a whole attachment and accepts no Range header.
  • crates/plugins/files/src/lib.rs:3620 streams a complete Version without a Range input. crates/plugins/files/src/archive.rs:1 streams a complete ZIP.

These HTTP operations and the UI accept valid results larger than the adapter cap. The generated CLI, MCP and WebMCP tools fail on those results. The error recommends a smaller page or range that these operations do not accept. CLI Mail export is a separate path; it does not make the MCP or WebMCP tools complete.

Expected: Keep bounded tool outputs and provide a declared continuation or range path for these downloads, or an explicit supported transfer result. Map it to the existing server operation. Do not raise the cap without a memory bound.

Test idea: Use a synthetic 2 MiB attachment, saved Version and ZIP. Verify complete transfer with bounded chunks through each generated adapter. Assert that small transfers still retain their current output shape.

Duplicate check: Searched all issue states for mail_download_attachment, attachment, pagination, and 1 MiB. #484 and #472 mention the operation inventory; no specific large-download parity issue found.

F04 — CLI retries non-idempotent writes after ambiguous gateway responses

Priority: High. Issue: #814.

  • crates/calternal-sync/src/remote.rs:39 checks safe methods only for transport errors; :45 retries 429, 502, 503 and 504 for every clonable request.
  • crates/calternal-cli/src/remote_commands.rs:311 uses that policy for every generated action; :390 also applies it to ergonomic commands. crates/calternal-cli/src/main.rs:47 defaults to three retries.
  • crates/plugins/notes/src/lib.rs:3410 creates a fresh Note identity on each request; :4349 allocates fresh Log block IDs for each batch. The contracts for these actions have no request-operation identity.

A gateway response can arrive after an upstream write committed. The CLI can then replay the same create and create a second item. Source evidence confirms the unconditional replay path; no live duplicate was produced in this review.

Expected: Do not automatically replay non-idempotent writes after an ambiguous result. Use a server-recognized operation identity and receipt when #667 supplies them. Until then, retain bounded retries for safe reads and provide an actionable unknown-write-result error for ambiguous writes.

Test idea: Use a test-only fault proxy that forwards one ordinary create, records its successful response, and returns a transient gateway status to the CLI. Assert one created item. Keep safe-read retry coverage and existing status expectations.

Duplicate check: Searched all issue states for send_with_retry, retry and idempotency; read closed #350 and #460, and open #667. #667 owns the server receipt contract. This issue owns the concrete current CLI replay policy and depends on #667 for receipt-based retries.

F05 — OpenAPI omits required revision and upload headers that generated tools require

Priority: Medium. Issue: #815.

  • crates/plugins/notes/src/reminders_api.rs:165 and :251 declare only path inputs, but :185 and :267 call match_etag with the request headers.
  • crates/plugins/files/src/public.rs:1446 omits the If-Match parameter from the public-edit contract, while :1467 requires it.
  • scripts/action_registry.py:41 adds required If-Match for these three operations. Its tus supplements at :29 also add required upload protocol headers that OpenAPI omits.
  • contracts/openapi.json lacks those header parameters. packages/api-client/src/generated.ts is byte-identical to a regeneration, so it faithfully repeats the omissions. contracts/actions.json declares the extra required headers.

An HTTP client or agent using the published OpenAPI schema can send a schema-valid request that the server rejects for a missing required header. The CLI, MCP and WebMCP schemas have a different required-input contract. Fresh generated files do not prevent this semantic drift.

Expected: Declare existing required protocol and revision headers in the owning route annotations. Generate OpenAPI, actions and the client from that declaration. Remove each corresponding supplement once the primary contract contains it. Preserve revision conflict checks.

Test idea: Assert that these operations expose required If-Match in both OpenAPI and the action schema. Check each tus operation for its required headers. Send requests formed from the documented schemas to the same route and keep stale-revision tests.

Duplicate check: Searched all issue states for notes_create_block_reminder and action-overrides; read #484. Its general registry scope and documented supplements do not provide a specific issue for these missing primary-contract requirements.

F06 — Legacy MCP and WebMCP tools have incompatible contracts and lose pagination

Priority: Medium. Issue: #817.

  • crates/calternal-server/src/mcp.rs:363 accepts q for calternal_search; apps/web/src/lib/webmcp/tools.ts:151 accepts query for the same name.
  • crates/calternal-server/src/mcp.rs:428 accepts a Note id for calternal_open; apps/web/src/lib/webmcp/tools.ts:156 instead accepts href and navigates the page.
  • crates/calternal-server/src/mcp.rs:436 accepts Home path and visibility flags for calternal_list_files; apps/web/src/lib/webmcp/tools.ts:166 accepts folderId.
  • apps/web/src/lib/webmcp/tools.ts:412 returns only the first 100 File rows and drops next_cursor, total and raw item IDs. The legacy MCP Files input has no cursor or limit. Mail reader inputs at mcp.rs:404 also omit the page cursor supported by the API.
  • mcp.rs:797 reads the Daily note for calternal_today; tools.ts:402 returns Journal, Events and Tasks from several routes. Generated registry tools already have common contracts but the legacy names remain registered.

An agent cannot reuse a legacy call across surfaces. Legacy file and Mail list tools cannot reach later pages. The WebMCP File result also prevents an agent from obtaining the folderId needed by its own list tool without parsing a link or changing tools.

Expected: Keep compatibility through explicit wrappers, but choose one documented shared contract for each common data tool. Give page tools cursor and limit inputs and preserve stable IDs and continuation output. Give browser navigation a distinct name from reading content. Direct legacy descriptions to the common generated action when a compatibility shape must stay.

Test idea: Compare schemas and results for tools sharing a name. Use a folder and Mail thread with more than one page. Traverse every page once, preserve IDs, and verify the documented compatibility path.

Duplicate check: Searched all issue states for calternal_list_files, parity, pagination and MCP; no specific legacy-contract drift issue found.

F07 — Generated tool input errors do not identify the field to fix

Priority: Low. Issue: #818.

  • crates/calternal-api/src/actions.rs:234 reports only Unexpected tool input field; :243 reports only Missing required tool input field; :267 reports only Invalid tool parameter type.
  • apps/web/src/lib/webmcp/generated.ts:20 and :21 return the same unnamed-field errors; :39 omits the field and expected type.
  • Generated operations have nested path, query, headers and body groups. For example, update_body requires path.id, headers.If-Match and body. The current error does not say which group or field failed.

An agent must guess which value to repair or reread the whole schema after a normal argument error. All generated adapters repeat this limitation.

Expected: Report the action ID, field path and expected shape, such as headers.If-Match is required. Report unknown field names without echoing supplied values. Keep credential and Home content values out of error text.

Test idea: Check missing nested headers, unknown fields and wrong primitive types through Rust and browser validators. Assert precise field paths and expected types. Include a sensitive test value and assert that the value is absent from the error.

Duplicate check: Searched all issue states for the quoted exact phrase Missing required tool input field; no issue found.

F08 — Generated tool names and help omit the object and outcome for basic actions

Priority: Low. Issue: #821.

  • scripts/action_registry.py:153 builds names from raw operation IDs, and :154 falls back to an operation ID with spaces when there is no summary.
  • contracts/actions.json has 23 eligible actions with one-word help. Examples: calternal_api_update for public-link settings, calternal_api_properties for Task edits, and calternal_api_head and calternal_api_terminate for tus uploads.
  • crates/calternal-server/src/mcp.rs:338 builds discovery from those fields. apps/web/src/lib/webmcp/generated.ts:82 uses the same help for title and description.
  • contracts/action-overrides.json:5 already shows the reuse path: create is named calternal_api_create_note with Create a Note help. The other ambiguous entries have no corresponding override.

Discovery gives an agent no object type or outcome for basic operations. Generic fields such as id or path cannot supply all of the missing context. The tools also mix bare verbs and plugin-qualified names.

Expected: Use the existing name/help override mechanism or route summaries to state an object and outcome. Prefer consistent plugin-qualified names for new tools; keep documented compatibility aliases for existing names. Include required revision, continuation and important result identities in useful help.

Test idea: Add a metadata review check for the identified ambiguous operations. Assert that help states the object and action and that a name change preserves compatibility. Keep descriptions concise.

Duplicate check: Searched all issue states for action-overrides, calternal_list_files and parity; no specific generated-help issue found.

Existing issues, kept without duplicates

D01 — Writes have no common idempotency identity or receipt

Existing owner: #667.

The registry has no declared idempotency header. Note create accepts a title,
body, folder and tags, then allocates a new identity
(crates/plugins/notes/src/lib.rs:3410). The Log batch accepts entries and
allocates fresh block IDs (:4349). There is no common receipt lookup in the
333-operation contract. Some operations are already idempotent by their own
identity, such as Calendar “Log this”; this does not establish a common write
contract. #667 explicitly owns client-operation identities, repeated-ID results
and durable receipts. #814 separately records unsafe CLI replay before those
receipts exist.

Test idea: send the same ordinary create intent twice, including concurrent
calls and a lost acknowledgement. It must have one result. A changed payload
under that identity must fail. Use the same route on all adapters.

D02 — Collection results lack page and byte bounds

Existing confirmed owner: #688.

crates/plugins/money/src/routes.rs:897 accepts only account and month
filters. :916 returns every matching transaction. The action schema has no
cursor or limit and the response has no continuation. All generated tools
stop at 1 MiB, so a large matching set has no complete adapter result.
#688 has this exact route and the expected bounded-page contract.

The route census also found collection responses without page inputs in
Calendar, Files, Photos, Mail, Notes, Search, Settings and Admin. The existing
list-bound campaigns are #678, #680, #682, #685, #703, #694, #691 and #697.
A collection schema alone does not prove an unbounded implementation: some
arrays have fixed domain limits. Do not file each schema array as a new bug.

Test idea: use a large matching set with variable row sizes. Traverse bounded
pages with stable identities and verify no missing or repeated unchanged row.
Compare the HTTP and generated tool contracts.

Agent docs review

Read agent_docs.rs, agent_docs/groups.rs, agent_docs/skill_intro.md, the
server build script and DESIGN §58 on the separate agent-doc branch. Its
public routes are /llms.txt, /skills/calternal/SKILL.md,
/.well-known/agent-skills/index.json, /.well-known/mcp-server-card and
/api/openapi.json.

  • The CLI commands, token environment variables, App Password preset names
    and document.modelContext entry point match the reviewed source.
  • Skill group counts derive from non-admin MCP registry entries. The guide
    correctly says that live tools/list is authoritative.
  • The Server Card takes protocol versions from MCP_PROTOCOLS. Its Instance
    switch defaults match the Instance policy defaults.
  • The skill index hashes the rendered Skill bytes. The public documents
    query Instance flags and do not query User flags. Cache headers use
    public, max-age=60, must-revalidate; the security middleware preserves an
    explicitly set cache header.
  • The first-read example reaches the Daily note write branch in #752. This
    is the concrete mismatch between the guide's read intent and route behavior.
  • The guide is on a separate branch. Its routes are not claimed to exist on
    the reviewed origin/dev base. Public-link resolution and cache behavior
    were inspected in source; no public endpoint was called.

Coverage and limits

The census checks all 333 method/path bindings and each action's scopes,
input groups, transfer encoding, help and successful-response media types.
The registry and parity tests check generated freshness and dispatch hooks.
The TypeScript client matches an offline regeneration with the pinned
openapi-typescript 7.13.0. This proves contract-to-client freshness. It does
not prove that the server emits the same OpenAPI bytes: no server build ran.

Deep source reads cover the common Rust/browser mapping, CLI retry policy,
MCP discovery and dispatch, legacy tools, Note and Log writes, parser previews,
revision guards, tus inputs, binary downloads and Money lists. UI data calls
were compared with the registry inventory. Dynamic menus, editor WebSockets
and provider-backed behavior still need live semantic coverage. Existing
docs/action-registry.md also reports incomplete full-smoke coverage.

No feature or hot path changed. No performance profile or benchmark run is
required for this review. No UI changed, so screenshots and UX interaction
checks do not apply. No live adversarial or authorization claim is made.

Final verification

Ran git fetch origin && git merge origin/dev once before final checks.
The fetched origin/dev remained at
c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Output:

Already up to date.

git diff --check and cargo fmt --check both exited 0 with no output.
Every Cargo command used CARGO_PROFILE_DEV_DEBUG=line-tables-only,
CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and the worktree's target/tmp.
The preset target directory was not changed. No Rust crate or web product
file changed, so clippy, Rust tests, full web check and full web tests were
not run. The review brief excludes full builds.

cmp packages/api-client/src/generated.ts artifacts/rev-mcp-api/generated.ts
exited 0. The generator used its existing pinned package without installing
a dependency. Check output:

Generated TypeScript client matches contracts/openapi.json

PYTHONDONTWRITEBYTECODE=1 python3 scripts/action_registry.py --check exited 0. Output, verbatim:

Action registry: 333 operations, 315 generated tools

PYTHONDONTWRITEBYTECODE=1 python3 scripts/parity_matrix.py --check exited 0. Output, verbatim:

Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps

PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_action_registry.py' exited 0. Output, verbatim:

...........
----------------------------------------------------------------------
Ran 11 tests in 0.661s

OK

PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_parity_matrix.py' exited 0. Output, verbatim:

....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

bun test packages/api-client/src/index.test.ts exited 0. Output, verbatim:

bun test v1.4.2 (744846f84)

packages/api-client/src/index.test.ts:
(pass) apiFetch > accepts empty 202 and 204 responses through the shared defaults [48.17ms]
(pass) apiFetch > does not hide malformed JSON in an empty-body success status [0.36ms]
(pass) apiFetch > still decodes a JSON 202 response body [5.84ms]
(pass) apiFetch > bypasses GET coalescing when the caller sets cache to no-store [0.80ms]
(pass) apiFetch > shares one pending GET and gives each caller an independent response [33.76ms]
(pass) apiFetch > keeps a shared GET alive when one caller aborts [25.46ms]
(pass) apiFetch > coalesces delayed page readers and drops the snapshot after a write or expiry [801.36ms]
(pass) apiFetch > bypasses and clears coalesced snapshots for a fresh API read [0.84ms]
(pass) apiFetch > sends typed query parameters, includes credentials, and accepts an abort signal [0.49ms]
(pass) apiFetch > carries the device time zone unless the caller names one (#141) [0.51ms]
(pass) apiFetch > throws the typed API error envelope [0.62ms]
(pass) apiFetch > keeps a typed failure body that is not an error envelope [0.75ms]
(pass) generated tool transports > sends raw text and returns a text envelope [5.64ms]
(pass) generated tool transports > sends and returns binary bytes with safe transfer metadata [1.24ms]
(pass) generated tool transports > cancels an idle stream and returns only complete SSE frames [44.67ms]
(pass) generated tool transports > retains upload Location on empty creation responses and enforces byte limits [0.96ms]
(pass) streams SSE even when the caller omits a cache option [10.86ms]
(pass) stops a byte response at the tool budget without waiting for EOF [0.61ms]

 18 pass
 0 fail
 49 expect() calls
Ran 18 tests across 1 file. [1023.00ms]

Known gaps

  • Findings are filed for the next implementation jobs. This review fixes no
    product behavior.
  • No server build, real-server smoke, live authorization matrix, adversarial
    round or provider-backed test ran. Source evidence is identified above.
  • The public agent-doc routes are reviewed on origin/job/agentdocs-630, not
    on a merged dev build.
  • Client-to-contract freshness passed. Server-to-contract regeneration still
    needs the merge-time server build.
  • UI menus and editor operations with dynamic routes still need semantic
    coverage. The parity inventory's zero-gap result does not close that work.
  • The report and issue bodies contain technical Money schema evidence only;
    no User financial data was read or recorded.

UX gaps closed: none; no UI changed. UX gaps left: the adapter usability
findings are assigned to #754, #760, #817, #818 and #821.

No design choice changed. The decisions above concern the review method and
issue destination only. No push, deployment or integration merge was done;
only the explicitly requested merge of origin/dev was attempted.

Cleanup

cargo clean exited 0. Output, verbatim:

     Removed 1 file, 356B total

Removed the Python bytecode created by the initial checks. There was no web
build output. Review evidence remains in the ignored
artifacts/rev-mcp-api/ directory; it is not committed.

Finished rev-mcp-api review. Branch: `job/rev-mcp-api` Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`) Head: `4a82c6414156f4362e0571afea542a8d0971b786` Changed file: `review-findings.md` only. Four atomic review commits. Worktree clean. Review delivered: 333 API operation bindings, shared adapter metadata and behavior review, and the separate agent-doc branch review. Eight new issues: #752, #754, #760, #814, #815, #817, #818 and #821. Existing owners retained for two findings: #667 and #688. High-priority findings: the missing Daily note read branch writes content under read classification (#752); the CLI can replay a non-idempotent create after an ambiguous gateway response (#814). These are source findings, not claims of live reproduction. Final gate output and decisions follow verbatim in the committed report. No Rust or web product code changed. Clippy, Rust tests, full web checks and full builds were excluded by this read-mostly job. One fetch and merge attempt before final checks returned Already up to date. cargo clean completed; web build output was absent. No push, deployment or integration merge. --- # MCP, API, CLI and WebMCP review Issue: #427. Review scope: #484 and DESIGN §§14, 18, 33, 34, 41 and 48. Review base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Branch: `job/rev-mcp-api`. Agent docs source: `origin/job/agentdocs-630` at `183ac356359f1f84afad30932f02e07323ddb7ed`. Review result: eight new issues and two confirmed findings assigned to existing issues. High-priority findings concern read-only content writes (#752) and unsafe CLI replay (#814). The other issues concern parser access (#754), large downloads (#760), required headers (#815), legacy contracts (#817), input errors (#818) and help (#821). No product code changed. This is a source review. It changes no product code. It does not prove live transport or authorization behavior. Findings below separate source evidence from results observed on a running server. ## Initial checks The registry has 333 HTTP operations across 270 paths. It has 315 generated tools. The route scope counts are 250 data, 44 account and 39 admin. The parity inventory has no missing generated adapter. This result does not prove that the adapters can complete each operation. Commands and output: ```text $ python3 scripts/action_registry.py --check Action registry: 333 operations, 315 generated tools $ python3 scripts/parity_matrix.py --check Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps ``` ## Confirmed source findings The review covers route and schema parity, tool names and input groups, pagination, response bounds, retry safety, actionable errors and agent docs. Each confirmed finding includes file and line evidence, the expected behavior, a regression test idea and its issue or existing duplicate. ## Decisions - Issue #427 has a Log attachment report. Use it for review progress because the job prompt assigns it. Follow the explicit API review scope. - Review existing references without changing product code or dependencies. - Use offline contract checks. Do not run full Rust or web builds for this read-mostly review, as required by the job brief. ## F01 — Daily note reads must preserve read-only App Password access Priority: High. Issue: [#752](https://git.kayg.org/kayg/calternal/issues/752). - `crates/calternal-server/src/wire.rs:2320` derives App Password access from `is_read_request`; `:2375` treats GET as read. - `crates/plugins/notes/src/lib.rs:3867` enters the missing Daily note branch and `:3871` writes a new Note, indexes it and updates adjacent Daily note navigation. - `scripts/action_registry.py:146` marks the GET action read-only. `crates/calternal-server/src/mcp.rs:797` describes `calternal_today` as a read. - On `origin/job/agentdocs-630` at `183ac356359f1f84afad30932f02e07323ddb7ed`, `crates/calternal-server/src/agent_docs/skill_intro.md:29` recommends a read-only MCP App Password and `:59` uses `calternal_today` as the first read. Source review shows that a route classified as a read contains a content-creation branch. Its authorization and its tool hints do not describe that branch. No live authorization test was run. Expected: Keep read-only requests free of content writes. Missing Daily notes need a read result or an explicit write action that checks write access on the server. Preserve the ordinary Daily note flow for a User who can write. Update the tool hints and agent examples from the same action intent. Test idea: Add a route-level regression for a missing Daily note under a read-only principal. Assert unchanged Home content and adjacent Daily notes. Check the API, MCP and generated adapters against the same contract. Keep existing status expectations. Duplicate check: Searched all issue states for `daily`, `read-only`, and `MCP`; read #661. #661 concerns a viewing-triggered rewrite of an existing Note in the editor, not the missing-Daily-note creation branch. ## F02 — Pure Composer and Task parser tools incorrectly require write access Priority: Medium. Issue: [#754](https://git.kayg.org/kayg/calternal/issues/754). - `crates/plugins/notes/src/composer_api.rs:1` documents a pure preview function with no reads or writes; `:219` returns `project(...)`. - `crates/plugins/notes/src/tasks_api.rs:91` parses Task text and returns a preview without a content write. - `scripts/action_registry.py:146` classifies every POST except ZIP download as a mutation. Both `notes_composer_parse` and `parse` have `read_only: false` and `destructive: true` in `contracts/actions.json`. - `crates/calternal-server/src/wire.rs:2375` has only the ZIP read exception. `crates/calternal-cli/src/remote_commands.rs:294` requires `--confirm`; `apps/web/src/lib/webmcp/generated.ts:88` confirms every action not marked read-only. A read-only API or MCP App Password cannot use the parsers. CLI and WebMCP request change confirmation for pure previews. The inventory gate still passes. Expected: Declare read intent for the pure parser actions once and use it in route access and adapter hints. Keep authentication, body validation and CPU bounds. CLI and WebMCP must allow the preview without change confirmation. Test idea: Add metadata assertions and read-only route tests for both parser actions. Add adapter tests that the pure previews do not ask for confirmation and do not write Home content. Duplicate check: Searched all issue states for `notes_composer_parse`, `parser`, `read-only` and `parity`. No specific parser-access issue found. ## F03 — Generated download tools cannot read valid attachments, Versions or ZIPs above 1 MiB Priority: Medium. Issue: [#760](https://git.kayg.org/kayg/calternal/issues/760). - `crates/calternal-server/src/mcp.rs:311`, `crates/calternal-cli/src/remote_commands.rs:343` and `packages/api-client/src/index.ts:456` stop generated responses above 1 MiB. - `contracts/actions.json` declares no Range or continuation input for `mail_download_attachment`, `download_version` or `download_zip`; `scripts/action_registry.py:49` supplies Range only to other download actions. - `crates/plugins/mail/src/routes.rs:45` permits attachments up to 25 MiB; `:1439` reads a whole attachment and accepts no Range header. - `crates/plugins/files/src/lib.rs:3620` streams a complete Version without a Range input. `crates/plugins/files/src/archive.rs:1` streams a complete ZIP. These HTTP operations and the UI accept valid results larger than the adapter cap. The generated CLI, MCP and WebMCP tools fail on those results. The error recommends a smaller page or range that these operations do not accept. CLI Mail export is a separate path; it does not make the MCP or WebMCP tools complete. Expected: Keep bounded tool outputs and provide a declared continuation or range path for these downloads, or an explicit supported transfer result. Map it to the existing server operation. Do not raise the cap without a memory bound. Test idea: Use a synthetic 2 MiB attachment, saved Version and ZIP. Verify complete transfer with bounded chunks through each generated adapter. Assert that small transfers still retain their current output shape. Duplicate check: Searched all issue states for `mail_download_attachment`, `attachment`, `pagination`, and `1 MiB`. #484 and #472 mention the operation inventory; no specific large-download parity issue found. ## F04 — CLI retries non-idempotent writes after ambiguous gateway responses Priority: High. Issue: [#814](https://git.kayg.org/kayg/calternal/issues/814). - `crates/calternal-sync/src/remote.rs:39` checks safe methods only for transport errors; `:45` retries 429, 502, 503 and 504 for every clonable request. - `crates/calternal-cli/src/remote_commands.rs:311` uses that policy for every generated action; `:390` also applies it to ergonomic commands. `crates/calternal-cli/src/main.rs:47` defaults to three retries. - `crates/plugins/notes/src/lib.rs:3410` creates a fresh Note identity on each request; `:4349` allocates fresh Log block IDs for each batch. The contracts for these actions have no request-operation identity. A gateway response can arrive after an upstream write committed. The CLI can then replay the same create and create a second item. Source evidence confirms the unconditional replay path; no live duplicate was produced in this review. Expected: Do not automatically replay non-idempotent writes after an ambiguous result. Use a server-recognized operation identity and receipt when #667 supplies them. Until then, retain bounded retries for safe reads and provide an actionable unknown-write-result error for ambiguous writes. Test idea: Use a test-only fault proxy that forwards one ordinary create, records its successful response, and returns a transient gateway status to the CLI. Assert one created item. Keep safe-read retry coverage and existing status expectations. Duplicate check: Searched all issue states for `send_with_retry`, `retry` and `idempotency`; read closed #350 and #460, and open #667. #667 owns the server receipt contract. This issue owns the concrete current CLI replay policy and depends on #667 for receipt-based retries. ## F05 — OpenAPI omits required revision and upload headers that generated tools require Priority: Medium. Issue: [#815](https://git.kayg.org/kayg/calternal/issues/815). - `crates/plugins/notes/src/reminders_api.rs:165` and `:251` declare only path inputs, but `:185` and `:267` call `match_etag` with the request headers. - `crates/plugins/files/src/public.rs:1446` omits the If-Match parameter from the public-edit contract, while `:1467` requires it. - `scripts/action_registry.py:41` adds required If-Match for these three operations. Its tus supplements at `:29` also add required upload protocol headers that OpenAPI omits. - `contracts/openapi.json` lacks those header parameters. `packages/api-client/src/generated.ts` is byte-identical to a regeneration, so it faithfully repeats the omissions. `contracts/actions.json` declares the extra required headers. An HTTP client or agent using the published OpenAPI schema can send a schema-valid request that the server rejects for a missing required header. The CLI, MCP and WebMCP schemas have a different required-input contract. Fresh generated files do not prevent this semantic drift. Expected: Declare existing required protocol and revision headers in the owning route annotations. Generate OpenAPI, actions and the client from that declaration. Remove each corresponding supplement once the primary contract contains it. Preserve revision conflict checks. Test idea: Assert that these operations expose required If-Match in both OpenAPI and the action schema. Check each tus operation for its required headers. Send requests formed from the documented schemas to the same route and keep stale-revision tests. Duplicate check: Searched all issue states for `notes_create_block_reminder` and `action-overrides`; read #484. Its general registry scope and documented supplements do not provide a specific issue for these missing primary-contract requirements. ## F06 — Legacy MCP and WebMCP tools have incompatible contracts and lose pagination Priority: Medium. Issue: [#817](https://git.kayg.org/kayg/calternal/issues/817). - `crates/calternal-server/src/mcp.rs:363` accepts `q` for `calternal_search`; `apps/web/src/lib/webmcp/tools.ts:151` accepts `query` for the same name. - `crates/calternal-server/src/mcp.rs:428` accepts a Note `id` for `calternal_open`; `apps/web/src/lib/webmcp/tools.ts:156` instead accepts `href` and navigates the page. - `crates/calternal-server/src/mcp.rs:436` accepts Home `path` and visibility flags for `calternal_list_files`; `apps/web/src/lib/webmcp/tools.ts:166` accepts `folderId`. - `apps/web/src/lib/webmcp/tools.ts:412` returns only the first 100 File rows and drops next_cursor, total and raw item IDs. The legacy MCP Files input has no cursor or limit. Mail reader inputs at `mcp.rs:404` also omit the page cursor supported by the API. - `mcp.rs:797` reads the Daily note for `calternal_today`; `tools.ts:402` returns Journal, Events and Tasks from several routes. Generated registry tools already have common contracts but the legacy names remain registered. An agent cannot reuse a legacy call across surfaces. Legacy file and Mail list tools cannot reach later pages. The WebMCP File result also prevents an agent from obtaining the folderId needed by its own list tool without parsing a link or changing tools. Expected: Keep compatibility through explicit wrappers, but choose one documented shared contract for each common data tool. Give page tools cursor and limit inputs and preserve stable IDs and continuation output. Give browser navigation a distinct name from reading content. Direct legacy descriptions to the common generated action when a compatibility shape must stay. Test idea: Compare schemas and results for tools sharing a name. Use a folder and Mail thread with more than one page. Traverse every page once, preserve IDs, and verify the documented compatibility path. Duplicate check: Searched all issue states for `calternal_list_files`, `parity`, `pagination` and `MCP`; no specific legacy-contract drift issue found. ## F07 — Generated tool input errors do not identify the field to fix Priority: Low. Issue: [#818](https://git.kayg.org/kayg/calternal/issues/818). - `crates/calternal-api/src/actions.rs:234` reports only Unexpected tool input field; `:243` reports only Missing required tool input field; `:267` reports only Invalid tool parameter type. - `apps/web/src/lib/webmcp/generated.ts:20` and `:21` return the same unnamed-field errors; `:39` omits the field and expected type. - Generated operations have nested path, query, headers and body groups. For example, update_body requires path.id, headers.If-Match and body. The current error does not say which group or field failed. An agent must guess which value to repair or reread the whole schema after a normal argument error. All generated adapters repeat this limitation. Expected: Report the action ID, field path and expected shape, such as headers.If-Match is required. Report unknown field names without echoing supplied values. Keep credential and Home content values out of error text. Test idea: Check missing nested headers, unknown fields and wrong primitive types through Rust and browser validators. Assert precise field paths and expected types. Include a sensitive test value and assert that the value is absent from the error. Duplicate check: Searched all issue states for the quoted exact phrase `Missing required tool input field`; no issue found. ## F08 — Generated tool names and help omit the object and outcome for basic actions Priority: Low. Issue: [#821](https://git.kayg.org/kayg/calternal/issues/821). - `scripts/action_registry.py:153` builds names from raw operation IDs, and `:154` falls back to an operation ID with spaces when there is no summary. - `contracts/actions.json` has 23 eligible actions with one-word help. Examples: calternal_api_update for public-link settings, calternal_api_properties for Task edits, and calternal_api_head and calternal_api_terminate for tus uploads. - `crates/calternal-server/src/mcp.rs:338` builds discovery from those fields. `apps/web/src/lib/webmcp/generated.ts:82` uses the same help for title and description. - `contracts/action-overrides.json:5` already shows the reuse path: create is named calternal_api_create_note with Create a Note help. The other ambiguous entries have no corresponding override. Discovery gives an agent no object type or outcome for basic operations. Generic fields such as id or path cannot supply all of the missing context. The tools also mix bare verbs and plugin-qualified names. Expected: Use the existing name/help override mechanism or route summaries to state an object and outcome. Prefer consistent plugin-qualified names for new tools; keep documented compatibility aliases for existing names. Include required revision, continuation and important result identities in useful help. Test idea: Add a metadata review check for the identified ambiguous operations. Assert that help states the object and action and that a name change preserves compatibility. Keep descriptions concise. Duplicate check: Searched all issue states for `action-overrides`, `calternal_list_files` and `parity`; no specific generated-help issue found. ## Existing issues, kept without duplicates ### D01 — Writes have no common idempotency identity or receipt Existing owner: [#667](https://git.kayg.org/kayg/calternal/issues/667). The registry has no declared idempotency header. Note create accepts a title, body, folder and tags, then allocates a new identity (`crates/plugins/notes/src/lib.rs:3410`). The Log batch accepts entries and allocates fresh block IDs (`:4349`). There is no common receipt lookup in the 333-operation contract. Some operations are already idempotent by their own identity, such as Calendar “Log this”; this does not establish a common write contract. #667 explicitly owns client-operation identities, repeated-ID results and durable receipts. #814 separately records unsafe CLI replay before those receipts exist. Test idea: send the same ordinary create intent twice, including concurrent calls and a lost acknowledgement. It must have one result. A changed payload under that identity must fail. Use the same route on all adapters. ### D02 — Collection results lack page and byte bounds Existing confirmed owner: [#688](https://git.kayg.org/kayg/calternal/issues/688). `crates/plugins/money/src/routes.rs:897` accepts only account and month filters. `:916` returns every matching transaction. The action schema has no cursor or limit and the response has no continuation. All generated tools stop at 1 MiB, so a large matching set has no complete adapter result. #688 has this exact route and the expected bounded-page contract. The route census also found collection responses without page inputs in Calendar, Files, Photos, Mail, Notes, Search, Settings and Admin. The existing list-bound campaigns are #678, #680, #682, #685, #703, #694, #691 and #697. A collection schema alone does not prove an unbounded implementation: some arrays have fixed domain limits. Do not file each schema array as a new bug. Test idea: use a large matching set with variable row sizes. Traverse bounded pages with stable identities and verify no missing or repeated unchanged row. Compare the HTTP and generated tool contracts. ## Agent docs review Read `agent_docs.rs`, `agent_docs/groups.rs`, `agent_docs/skill_intro.md`, the server build script and DESIGN §58 on the separate agent-doc branch. Its public routes are `/llms.txt`, `/skills/calternal/SKILL.md`, `/.well-known/agent-skills/index.json`, `/.well-known/mcp-server-card` and `/api/openapi.json`. - The CLI commands, token environment variables, App Password preset names and `document.modelContext` entry point match the reviewed source. - Skill group counts derive from non-admin MCP registry entries. The guide correctly says that live `tools/list` is authoritative. - The Server Card takes protocol versions from `MCP_PROTOCOLS`. Its Instance switch defaults match the Instance policy defaults. - The skill index hashes the rendered Skill bytes. The public documents query Instance flags and do not query User flags. Cache headers use `public, max-age=60, must-revalidate`; the security middleware preserves an explicitly set cache header. - The first-read example reaches the Daily note write branch in #752. This is the concrete mismatch between the guide's read intent and route behavior. - The guide is on a separate branch. Its routes are not claimed to exist on the reviewed `origin/dev` base. Public-link resolution and cache behavior were inspected in source; no public endpoint was called. ## Coverage and limits The census checks all 333 method/path bindings and each action's scopes, input groups, transfer encoding, help and successful-response media types. The registry and parity tests check generated freshness and dispatch hooks. The TypeScript client matches an offline regeneration with the pinned `openapi-typescript 7.13.0`. This proves contract-to-client freshness. It does not prove that the server emits the same OpenAPI bytes: no server build ran. Deep source reads cover the common Rust/browser mapping, CLI retry policy, MCP discovery and dispatch, legacy tools, Note and Log writes, parser previews, revision guards, tus inputs, binary downloads and Money lists. UI data calls were compared with the registry inventory. Dynamic menus, editor WebSockets and provider-backed behavior still need live semantic coverage. Existing `docs/action-registry.md` also reports incomplete full-smoke coverage. No feature or hot path changed. No performance profile or benchmark run is required for this review. No UI changed, so screenshots and UX interaction checks do not apply. No live adversarial or authorization claim is made. ## Final verification Ran `git fetch origin && git merge origin/dev` once before final checks. The fetched `origin/dev` remained at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Output: ```text Already up to date. ``` `git diff --check` and `cargo fmt --check` both exited 0 with no output. Every Cargo command used `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=4` and the worktree's `target/tmp`. The preset target directory was not changed. No Rust crate or web product file changed, so clippy, Rust tests, full web check and full web tests were not run. The review brief excludes full builds. `cmp packages/api-client/src/generated.ts artifacts/rev-mcp-api/generated.ts` exited 0. The generator used its existing pinned package without installing a dependency. Check output: ```text Generated TypeScript client matches contracts/openapi.json ``` `PYTHONDONTWRITEBYTECODE=1 python3 scripts/action_registry.py --check` exited 0. Output, verbatim: ```text Action registry: 333 operations, 315 generated tools ``` `PYTHONDONTWRITEBYTECODE=1 python3 scripts/parity_matrix.py --check` exited 0. Output, verbatim: ```text Parity matrix: 333 API actions, 126 shortcuts, 2 static commands, 145 menu actions, 35 settings groups, 0 actions with adapter gaps ``` `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_action_registry.py'` exited 0. Output, verbatim: ```text ........... ---------------------------------------------------------------------- Ran 11 tests in 0.661s OK ``` `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_parity_matrix.py'` exited 0. Output, verbatim: ```text .... ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK ``` `bun test packages/api-client/src/index.test.ts` exited 0. Output, verbatim: ```text bun test v1.4.2 (744846f84) packages/api-client/src/index.test.ts: (pass) apiFetch > accepts empty 202 and 204 responses through the shared defaults [48.17ms] (pass) apiFetch > does not hide malformed JSON in an empty-body success status [0.36ms] (pass) apiFetch > still decodes a JSON 202 response body [5.84ms] (pass) apiFetch > bypasses GET coalescing when the caller sets cache to no-store [0.80ms] (pass) apiFetch > shares one pending GET and gives each caller an independent response [33.76ms] (pass) apiFetch > keeps a shared GET alive when one caller aborts [25.46ms] (pass) apiFetch > coalesces delayed page readers and drops the snapshot after a write or expiry [801.36ms] (pass) apiFetch > bypasses and clears coalesced snapshots for a fresh API read [0.84ms] (pass) apiFetch > sends typed query parameters, includes credentials, and accepts an abort signal [0.49ms] (pass) apiFetch > carries the device time zone unless the caller names one (#141) [0.51ms] (pass) apiFetch > throws the typed API error envelope [0.62ms] (pass) apiFetch > keeps a typed failure body that is not an error envelope [0.75ms] (pass) generated tool transports > sends raw text and returns a text envelope [5.64ms] (pass) generated tool transports > sends and returns binary bytes with safe transfer metadata [1.24ms] (pass) generated tool transports > cancels an idle stream and returns only complete SSE frames [44.67ms] (pass) generated tool transports > retains upload Location on empty creation responses and enforces byte limits [0.96ms] (pass) streams SSE even when the caller omits a cache option [10.86ms] (pass) stops a byte response at the tool budget without waiting for EOF [0.61ms] 18 pass 0 fail 49 expect() calls Ran 18 tests across 1 file. [1023.00ms] ``` ## Known gaps - Findings are filed for the next implementation jobs. This review fixes no product behavior. - No server build, real-server smoke, live authorization matrix, adversarial round or provider-backed test ran. Source evidence is identified above. - The public agent-doc routes are reviewed on `origin/job/agentdocs-630`, not on a merged `dev` build. - Client-to-contract freshness passed. Server-to-contract regeneration still needs the merge-time server build. - UI menus and editor operations with dynamic routes still need semantic coverage. The parity inventory's zero-gap result does not close that work. - The report and issue bodies contain technical Money schema evidence only; no User financial data was read or recorded. UX gaps closed: none; no UI changed. UX gaps left: the adapter usability findings are assigned to #754, #760, #817, #818 and #821. No design choice changed. The decisions above concern the review method and issue destination only. No push, deployment or integration merge was done; only the explicitly requested merge of `origin/dev` was attempted. ## Cleanup `cargo clean` exited 0. Output, verbatim: ```text Removed 1 file, 356B total ``` Removed the Python bytecode created by the initial checks. There was no web build output. Review evidence remains in the ignored `artifacts/rev-mcp-api/` directory; it is not committed.
Author
Owner

Finished the source-only design-gap review on job/rev-design-gaps.

Head: 80083063d21f78a62b73d7fd410b31ce1437d86c. Reviewed origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Built: a committed source audit in review-findings.md. It records ten new issues and eighteen source observations mapped to fifteen existing tickets. Each new issue includes a DESIGN reference, file:line evidence, observed and expected behavior, a duplicate check and a test idea. No product implementation changed. Three atomic documentation commits preserve progress.

New issues:

  • #758: Saved searches: Trash and Unpin have no Undo action
  • #768: Reminders: Remove, Done and Snooze have no Undo action
  • #770: Files: restoring a Version has no Undo action
  • #771: Mail: read-state changes leave sidebar unread counts stale
  • #772: Calendar settings: failed format saves leave unsaved choices selected
  • #773: Search deep links: same-route query changes do not restore the palette
  • #775: Photos: changing a burst key photo has no Undo action
  • #776: Notifications: Delete has no Undo action
  • #827: Calendar: default Event calendar has no synced Settings choice
  • #828: Location: deleting a Saved place has no Undo action

Files: review-findings.md only. Review scripts, issue bodies and receipts remain in ignored artifacts/design-review/.

Final fetch/merge output (verbatim):

Already up to date.

Review check output (verbatim):

PASS: 10 new findings have unique issue receipts, DESIGN references, evidence and test ideas.
PASS: 18 observations link to existing issues.
PASS: 49 source anchors resolve in the reviewed tree.
PASS: only review-findings.md differs from the reviewed product source.

git diff --check: exit 0, no output. The final working tree is clean.

Cargo and web product gates: not run. This documentation-only brief requests no full builds and minimal Cargo use. No dependency changed, so no version lookup was needed. No benchmark, production screenshot, native-client check or live API probe ran. No web build output was created.

Cleanup output (verbatim):

     Removed 1 file, 356B total

UX gaps closed: none; this is a review job. UX gaps left: the filed and existing tickets in the report. Rendering, cap-height alignment, accessibility, touch, motion and offline behavior still need production evidence at 390/820/1440 px in both schemes with macOS platform emulation. Source findings do not assert those checks passed.

Decisions: follow the explicit wider review brief despite #427's attachment-bug body; use the latest owner decisions; exclude OPEN and later-only features; map specific duplicates to existing issues and file narrow surface defects beyond shared architecture umbrellas. No new product design was chosen. Stable Task IDs, saved-search rename/pin, Files pins and shared Location consent were traced and excluded as false leads. Place-reminder authoring remains under #393.

No product edits, push or deploy. This review does not fix the Log attachment bug in #427.

Finished the source-only design-gap review on `job/rev-design-gaps`. Head: `80083063d21f78a62b73d7fd410b31ce1437d86c`. Reviewed `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Built: a committed source audit in `review-findings.md`. It records ten new issues and eighteen source observations mapped to fifteen existing tickets. Each new issue includes a DESIGN reference, file:line evidence, observed and expected behavior, a duplicate check and a test idea. No product implementation changed. Three atomic documentation commits preserve progress. New issues: - [#758](https://git.kayg.org/kayg/calternal/issues/758): Saved searches: Trash and Unpin have no Undo action - [#768](https://git.kayg.org/kayg/calternal/issues/768): Reminders: Remove, Done and Snooze have no Undo action - [#770](https://git.kayg.org/kayg/calternal/issues/770): Files: restoring a Version has no Undo action - [#771](https://git.kayg.org/kayg/calternal/issues/771): Mail: read-state changes leave sidebar unread counts stale - [#772](https://git.kayg.org/kayg/calternal/issues/772): Calendar settings: failed format saves leave unsaved choices selected - [#773](https://git.kayg.org/kayg/calternal/issues/773): Search deep links: same-route query changes do not restore the palette - [#775](https://git.kayg.org/kayg/calternal/issues/775): Photos: changing a burst key photo has no Undo action - [#776](https://git.kayg.org/kayg/calternal/issues/776): Notifications: Delete has no Undo action - [#827](https://git.kayg.org/kayg/calternal/issues/827): Calendar: default Event calendar has no synced Settings choice - [#828](https://git.kayg.org/kayg/calternal/issues/828): Location: deleting a Saved place has no Undo action Files: `review-findings.md` only. Review scripts, issue bodies and receipts remain in ignored `artifacts/design-review/`. Final fetch/merge output (verbatim): ```text Already up to date. ``` Review check output (verbatim): ```text PASS: 10 new findings have unique issue receipts, DESIGN references, evidence and test ideas. PASS: 18 observations link to existing issues. PASS: 49 source anchors resolve in the reviewed tree. PASS: only review-findings.md differs from the reviewed product source. ``` `git diff --check`: exit 0, no output. The final working tree is clean. Cargo and web product gates: not run. This documentation-only brief requests no full builds and minimal Cargo use. No dependency changed, so no version lookup was needed. No benchmark, production screenshot, native-client check or live API probe ran. No web build output was created. Cleanup output (verbatim): ```text Removed 1 file, 356B total ``` UX gaps closed: none; this is a review job. UX gaps left: the filed and existing tickets in the report. Rendering, cap-height alignment, accessibility, touch, motion and offline behavior still need production evidence at 390/820/1440 px in both schemes with macOS platform emulation. Source findings do not assert those checks passed. Decisions: follow the explicit wider review brief despite #427's attachment-bug body; use the latest owner decisions; exclude OPEN and later-only features; map specific duplicates to existing issues and file narrow surface defects beyond shared architecture umbrellas. No new product design was chosen. Stable Task IDs, saved-search rename/pin, Files pins and shared Location consent were traced and excluded as false leads. Place-reminder authoring remains under #393. No product edits, push or deploy. This review does not fix the Log attachment bug in #427.
Author
Owner

BLOCKER #844: retrying a linked Note body failure can duplicate already acknowledged Logs. In the reviewed tasks-mode head, commitLogBatch acknowledges the Log rows before body writes; Send all marks drafts committed only after all those writes complete, so a rejected body call leaves the same drafts available. A retry allocates new Log IDs. This code is inherited and remains in the reviewed heads.

A local test of the actual batch function with mocked prepare and transport observes log-1 acknowledged, the body call rejected, then log-2 written from the same snapshot. No live-server reproduction claimed. File:line evidence, the regression test idea and exact test output are in the issue and review-findings.md. No product files changed.

BLOCKER #844: retrying a linked Note body failure can duplicate already acknowledged Logs. In the reviewed tasks-mode head, commitLogBatch acknowledges the Log rows before body writes; Send all marks drafts committed only after all those writes complete, so a rejected body call leaves the same drafts available. A retry allocates new Log IDs. This code is inherited and remains in the reviewed heads. A local test of the actual batch function with mocked prepare and transport observes log-1 acknowledged, the body call rejected, then log-2 written from the same snapshot. No live-server reproduction claimed. File:line evidence, the regression test idea and exact test output are in the issue and review-findings.md. No product files changed.
Author
Owner

Round 7b independent defensive review finished.

Branch: job/rev-7b-security
Head: e98c0119d3f5e8b3825d7feba9a454cd66721b44
Built: a pinned review report with coverage for all 25 queued branches, two duplicate-checked repair issues, and inert local cache checks.
Committed file: review-findings.md only. Five atomic report commits. No product edits, pushes or deploys. The requested origin/dev merge was already up to date.

Merge blockers:

  • P1 privacy leak: #736, cached Mail messages keep revoked sender image permission.
  • P1 crash risk: #816, the new media parser has no nesting bound.

The report below contains source locations, impact, repair and regression ideas, per-branch boundary checks, exact validation output, decisions and known gaps. No issue was closed.

Round 7b defensive security review

Tracking issue: #427. Review branch: job/rev-7b-security.

Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev, 2026-10-02).

This report records a static review. Product files stay unchanged. Branch heads
are pinned below. A finding needs a source location, impact, and a regression
test idea. Authorization holes and data leaks block merge.

Branch coverage

Branch Reviewed head Diff lines State
job/agentdocs-630 183ac356359f1f84afad30932f02e07323ddb7ed 1658 Checked
job/submenu-579 142c063a87206721042ae52d24140490a80627d3 635 Checked
job/tasks-mode f620390c724ee08540d38b0ba69c3d12225fc1e4 6431 Checked
job/toastname-586 093db3ec12a26d1d9ed303853ee733fbc5e79ccd 1035 Checked
job/tocrail-636 144f0316a7ed6eb9f1ed495d21918e3e78656365 2506 Checked
job/quirks-546 50a006765ab635665cc0fc53ccaef10d6210fdf0 2577 Checked
job/calcard-series 5f7fdb96706aca0c457a6b7851f548f7f618ac85 6833 Checked
job/editor-series a087d0aba9f39bbfb5447c2ce22db0de25fd37a0 3779 Checked
job/reload-423 2399db841cf16ade3fbf47fcfab50578a3abe364 8448 Checked
job/cal-e2e-569 96908cbef8bbf078ce73bbc1a51f432ba8c416c8 281 Checked
job/selalign-576 174b554e1a66f53b0c9d92ede41b47eac145f419 1119 Checked
job/maillayouts f9f360e68f4e9ca106ddd7fea24d1f4363881a2b 5043 Blocker #736
job/dragghost-612 bc08062b167e59ca7ac39426a96481b2664b2e75 514 Checked
job/writeonopen-661 04c4a651be5a0da6c1311af9ad2d39bd289b8a09 916 Checked
job/ryw-653 4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63 1277 Checked
job/perf-cache-665 b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2 2169 Checked
job/perf-snap-666 253c2a00cade24a7f845a5e67f309093641b8850 927 Checked
job/perf-mut-667 52d2b17f805072cd0304d7a05fe0534523cc7bc3 4747 Checked
job/fix-499 242022301673dc6746d89985ee36078743723591 2217 Checked
job/calimg-589 421dd63735d116cba4961a0a3ca4c985baa83480 5106 Checked
job/voicefiles-620 b7ef7a2ab57f45b5d46cd19b4560215acae918e3 3037 Blocker #816
job/imaptest-625 f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3 30 Checked
job/burst-709 c421756ac9af5a9b653b7c9f53c41b3aca24de89 377 Checked
job/admin-burst-705 23a6fe0e0e326f789c886f366880f5b86683b287 284 Checked
job/instant-663 e62249dedcc2c7d108e4432596d40aee6f5a4bc8 107 Checked

Findings

P1 — BLOCKER: revoked sender image permission remains in the reader cache

Issue: #736.
Branch: job/maillayouts.

The server stores image permission per User and sender. MailView.svelte:917
changes that permission but deletes only the selected message cache key at
line 926. Another cached message from the same sender keeps
remote_content_allowed = true. Selection uses that cached detail at lines
548–553 without checking permission again. MailReaderContent.svelte:56 and
frame.ts:131 then permit HTTPS images. The sender can receive the User's IP
address and message-open time after permission was removed. This data leak
blocks merge.

The inert local check uses the branch's actual BoundedReaderCache. It stores
two permitted message details, applies the current delete-and-replace operation
to one, and confirms that the other still has permission. It sends no request.
The check proves stale cache state; it does not measure browser network traffic.

Fix: update a live sender permission model, or invalidate all details and pending
loads for the sender. A regression test must cover a second warmed message and
a prefetch that finishes after permission changes.

P1 — BLOCKER: media header parser has no nesting bound

Issue: #816.
Branch: job/voicefiles-620.

crates/calternal-media/src/lib.rs:358 defines iso_box_tracks. Lines 366–367
recurse on User-controlled container headers without a depth limit or a shared
work budget. The 16 MiB input cap and per-call loop cap do not bound stack use.
Files indexing calls it at crates/plugins/files/src/index.rs:1105; Search
indexing calls it at crates/calternal-search/src/indexer.rs:2628. Stack
exhaustion can terminate the server. This crash risk blocks merge.

This is a static finding. No crash payload or threshold was tested. Fix the
shared parser with a finite container grammar or an iterative traversal with
depth and work limits. Test those limits and normal audio/video headers after
the repair. The related older #519 and DAV XML #785 do not track this parser.

Security boundary checks

Branch Checked boundary and result
job/agentdocs-630 Exact public document routes; generated contracts and instance switches only. No User settings, credentials or private response data found.
job/submenu-579 Menu focus and pointer state. No route, storage, HTML or authority change found.
job/tasks-mode Saved-view routes call the data principal and use the User's Home. IDs and view fields are validated. File reads and writes use Root and RelPath, with file and scan byte caps. Filters compare literals; they do not execute code.
job/toastname-586 Item names stay escaped text. Task Trash uses the existing server route; inline Tasks cannot trash a parent Note.
job/tocrail-636 Heading labels stay escaped text. Links use the Note ID and slug. Outline state is cleared when the Note changes.
job/quirks-546 Lazy Settings imports use a fixed loader map. Admin sections keep role checks and reject stale load completions. Other changes affect presentation.
job/calcard-series Attachment restore resolves targets within the User's Home, validates size and count, and uses the checked writer. Markdown labels and destinations use shared escaping. Quick Look and audio resolve stable IDs through the existing Files API.
job/editor-series Block transforms use existing editor commands. Card decorations do not change source bytes. Mention matching escapes literal titles; no new raw HTML sink found.
job/reload-423 Retained assets use strict immutable URL validation and hash-derived storage names through Root. User caches check owner and generation before publication. Boot additions contain no private data.
job/cal-e2e-569 Journal edit date fix reuses the existing landing-date rule. No new authority or input boundary.
job/selalign-576 Selection and shared motion changes. No route, storage or HTML boundary change found.
job/maillayouts Mail HTML stays server-sanitized in the restricted frame. Cached sender permission is stale after revoke: blocker #736.
job/dragghost-612 Calendar drag feedback and styles only. No new write or authority boundary.
job/writeonopen-661 Removes the editor's automatic trailing-node write on open. No new input boundary.
job/ryw-653 ID repair and projection publication use existing User-scoped checked writers and database transactions. No new authority or outbound client.
job/perf-cache-665 Cache keys include User, item, variant and revision. Session/access signals invalidate pending reads. Note ownership is checked before the exact-response ETag and 304 decision.
job/perf-snap-666 Snapshot keys include User and URL state. Limits include pending reservations. Cleanup prevents late publication. No route adapter uses this module in the reviewed head.
job/perf-mut-667 Receipts and inverse state use User-scoped keys and one writer transaction. Operation IDs and byte sizes are validated. Undo checks revision and expiry. The client checks owner and generation after awaits.
job/fix-499 Search focus, sizing and action controls reuse existing navigation and menu actions. No new authorization or raw HTML boundary.
job/calimg-589 Photo queries bind viewer and owner. Projection checks current Share roots. Plugin-owned path declarations exclude generic Activity; they do not grant authority. Thumbnail text uses existing SVG escaping.
job/voicefiles-620 Used-in queries bind the User for Notes, Logs, Tasks and Events. Target stat uses Root and RelPath. MIME changes preserve existing Search access checks. New parser nesting is unbounded: blocker #816.
job/imaptest-625 Vendored test change requires a fixed redacted provider error. No production change.
job/burst-709 Test probe only. It keeps credentials and response bodies out of its reported evidence. Not executed in this review.
job/admin-burst-705 Test HTTPS counters retain bounded transport state, with no request headers, paths or bodies. No new Admin route.
job/instant-663 Documentation only. Cache rules retain the separation between Derived data and Security state.

All new private routes inherit session authority from the server. Cookie writes
require the configured Origin. Read-only credentials reject writes and protocol
upgrades. Plugin handlers then require the data scope and bind queries or Home
access to that principal. No new outbound server fetch was found. Shared browser
caches were checked against session cleanup and late response publication.

The Tasks singleton and Mail reader cache do not each implement a session reset.
The reviewed shell unmounts private views synchronously at session end and reloads
before it mounts another User. That normal flow prevents the suspected User-switch
leak. It does not fix sender permission changes within the same Mail session.

These results cover the pinned source heads, not a combined application build.

Decisions

  • Use local queued branch heads, and record their exact SHAs. All local and
    remote heads are compared before review.
  • Use static review and small local checks. Do not build the workspace or
    change product code.
  • No product design decision was required. Keep crash and privacy findings
    separate, and distinguish static evidence from runtime observations.

Validation

Local cache check (exit 0):

CONFIRMED: another warmed message retains revoked sender image permission.

Inert shared-cache checks (exit 0):

PASS: revision cache isolates Users and rejects a completion after session end.
PASS: view snapshots isolate Users and reject a reservation after owner cleanup.

Final fetch and git merge origin/dev were run once. Integration output
(exit 0):

Already up to date.

The integration base remains
c4a61e8cf090170f35b1bed3350d9de20c83ecd5. All 25 remote heads still match
the reviewed heads.

Report and source-integrity checks (exit 0):

PASS: all 25 queued remote heads and saved diffs match the pinned review scope.
PASS: committed changes contain only review-findings.md.
PASS: inert cache checks use unchanged source from the reviewed branch heads.

git diff --check returned no output and exit 0. The inert checks above were
rerun after the final fetch and merge.

No Rust or web source was authored. cargo fmt --check, crate clippy/test,
bun run check and bun run test were not run. This report makes no claim
that the queued branches build or pass their gates. The review-specific job
requires minimal cargo use. No live adversarial round was run: there was no
API merge. The findings include regression test ideas for the repair jobs.

Cleanup: cargo clean (with the required cargo environment) returned exit 0:

     Removed 1 file, 356B total

Web build and .svelte-kit output were absent. Review artifacts stay
in the ignored artifacts/security-review/ directory.

Known gaps

  • Static review does not prove runtime behavior or absence of all flaws.
  • No combined build, browser network capture, live protocol probe or crash test
    was run. No queued branch was merged by this review.
  • Rust and web build gates are not applicable to the authored report. The job
    expressly limits builds. No crate or package source was edited.
  • Snapshot adapters must check live Share access when they adopt #666. The
    reviewed head contains the primitive only.
  • Findings are filed for repair; this review does not change product code.
  • No UI feature was built. UX gaps closed and UX gaps left are not applicable.
  • No dependencies or versions were added or upgraded. The new media crate in
    the reviewed branch sets AGPL-3.0-only and has no external dependency.
Round 7b independent defensive review finished. Branch: `job/rev-7b-security` Head: `e98c0119d3f5e8b3825d7feba9a454cd66721b44` Built: a pinned review report with coverage for all 25 queued branches, two duplicate-checked repair issues, and inert local cache checks. Committed file: `review-findings.md` only. Five atomic report commits. No product edits, pushes or deploys. The requested origin/dev merge was already up to date. Merge blockers: - P1 privacy leak: [#736](https://git.kayg.org/kayg/calternal/issues/736), cached Mail messages keep revoked sender image permission. - P1 crash risk: [#816](https://git.kayg.org/kayg/calternal/issues/816), the new media parser has no nesting bound. The report below contains source locations, impact, repair and regression ideas, per-branch boundary checks, exact validation output, decisions and known gaps. No issue was closed. # Round 7b defensive security review Tracking issue: #427. Review branch: `job/rev-7b-security`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`, 2026-10-02). This report records a static review. Product files stay unchanged. Branch heads are pinned below. A finding needs a source location, impact, and a regression test idea. Authorization holes and data leaks block merge. ## Branch coverage | Branch | Reviewed head | Diff lines | State | | --- | --- | ---: | --- | | `job/agentdocs-630` | `183ac356359f1f84afad30932f02e07323ddb7ed` | 1658 | Checked | | `job/submenu-579` | `142c063a87206721042ae52d24140490a80627d3` | 635 | Checked | | `job/tasks-mode` | `f620390c724ee08540d38b0ba69c3d12225fc1e4` | 6431 | Checked | | `job/toastname-586` | `093db3ec12a26d1d9ed303853ee733fbc5e79ccd` | 1035 | Checked | | `job/tocrail-636` | `144f0316a7ed6eb9f1ed495d21918e3e78656365` | 2506 | Checked | | `job/quirks-546` | `50a006765ab635665cc0fc53ccaef10d6210fdf0` | 2577 | Checked | | `job/calcard-series` | `5f7fdb96706aca0c457a6b7851f548f7f618ac85` | 6833 | Checked | | `job/editor-series` | `a087d0aba9f39bbfb5447c2ce22db0de25fd37a0` | 3779 | Checked | | `job/reload-423` | `2399db841cf16ade3fbf47fcfab50578a3abe364` | 8448 | Checked | | `job/cal-e2e-569` | `96908cbef8bbf078ce73bbc1a51f432ba8c416c8` | 281 | Checked | | `job/selalign-576` | `174b554e1a66f53b0c9d92ede41b47eac145f419` | 1119 | Checked | | `job/maillayouts` | `f9f360e68f4e9ca106ddd7fea24d1f4363881a2b` | 5043 | Blocker #736 | | `job/dragghost-612` | `bc08062b167e59ca7ac39426a96481b2664b2e75` | 514 | Checked | | `job/writeonopen-661` | `04c4a651be5a0da6c1311af9ad2d39bd289b8a09` | 916 | Checked | | `job/ryw-653` | `4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63` | 1277 | Checked | | `job/perf-cache-665` | `b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2` | 2169 | Checked | | `job/perf-snap-666` | `253c2a00cade24a7f845a5e67f309093641b8850` | 927 | Checked | | `job/perf-mut-667` | `52d2b17f805072cd0304d7a05fe0534523cc7bc3` | 4747 | Checked | | `job/fix-499` | `242022301673dc6746d89985ee36078743723591` | 2217 | Checked | | `job/calimg-589` | `421dd63735d116cba4961a0a3ca4c985baa83480` | 5106 | Checked | | `job/voicefiles-620` | `b7ef7a2ab57f45b5d46cd19b4560215acae918e3` | 3037 | Blocker #816 | | `job/imaptest-625` | `f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3` | 30 | Checked | | `job/burst-709` | `c421756ac9af5a9b653b7c9f53c41b3aca24de89` | 377 | Checked | | `job/admin-burst-705` | `23a6fe0e0e326f789c886f366880f5b86683b287` | 284 | Checked | | `job/instant-663` | `e62249dedcc2c7d108e4432596d40aee6f5a4bc8` | 107 | Checked | ## Findings ### P1 — BLOCKER: revoked sender image permission remains in the reader cache Issue: [#736](https://git.kayg.org/kayg/calternal/issues/736). Branch: `job/maillayouts`. The server stores image permission per User and sender. `MailView.svelte:917` changes that permission but deletes only the selected message cache key at line 926. Another cached message from the same sender keeps `remote_content_allowed = true`. Selection uses that cached detail at lines 548–553 without checking permission again. `MailReaderContent.svelte:56` and `frame.ts:131` then permit HTTPS images. The sender can receive the User's IP address and message-open time after permission was removed. This data leak blocks merge. The inert local check uses the branch's actual `BoundedReaderCache`. It stores two permitted message details, applies the current delete-and-replace operation to one, and confirms that the other still has permission. It sends no request. The check proves stale cache state; it does not measure browser network traffic. Fix: update a live sender permission model, or invalidate all details and pending loads for the sender. A regression test must cover a second warmed message and a prefetch that finishes after permission changes. ### P1 — BLOCKER: media header parser has no nesting bound Issue: [#816](https://git.kayg.org/kayg/calternal/issues/816). Branch: `job/voicefiles-620`. `crates/calternal-media/src/lib.rs:358` defines `iso_box_tracks`. Lines 366–367 recurse on User-controlled container headers without a depth limit or a shared work budget. The 16 MiB input cap and per-call loop cap do not bound stack use. Files indexing calls it at `crates/plugins/files/src/index.rs:1105`; Search indexing calls it at `crates/calternal-search/src/indexer.rs:2628`. Stack exhaustion can terminate the server. This crash risk blocks merge. This is a static finding. No crash payload or threshold was tested. Fix the shared parser with a finite container grammar or an iterative traversal with depth and work limits. Test those limits and normal audio/video headers after the repair. The related older #519 and DAV XML #785 do not track this parser. ## Security boundary checks | Branch | Checked boundary and result | | --- | --- | | `job/agentdocs-630` | Exact public document routes; generated contracts and instance switches only. No User settings, credentials or private response data found. | | `job/submenu-579` | Menu focus and pointer state. No route, storage, HTML or authority change found. | | `job/tasks-mode` | Saved-view routes call the data principal and use the User's Home. IDs and view fields are validated. File reads and writes use Root and RelPath, with file and scan byte caps. Filters compare literals; they do not execute code. | | `job/toastname-586` | Item names stay escaped text. Task Trash uses the existing server route; inline Tasks cannot trash a parent Note. | | `job/tocrail-636` | Heading labels stay escaped text. Links use the Note ID and slug. Outline state is cleared when the Note changes. | | `job/quirks-546` | Lazy Settings imports use a fixed loader map. Admin sections keep role checks and reject stale load completions. Other changes affect presentation. | | `job/calcard-series` | Attachment restore resolves targets within the User's Home, validates size and count, and uses the checked writer. Markdown labels and destinations use shared escaping. Quick Look and audio resolve stable IDs through the existing Files API. | | `job/editor-series` | Block transforms use existing editor commands. Card decorations do not change source bytes. Mention matching escapes literal titles; no new raw HTML sink found. | | `job/reload-423` | Retained assets use strict immutable URL validation and hash-derived storage names through Root. User caches check owner and generation before publication. Boot additions contain no private data. | | `job/cal-e2e-569` | Journal edit date fix reuses the existing landing-date rule. No new authority or input boundary. | | `job/selalign-576` | Selection and shared motion changes. No route, storage or HTML boundary change found. | | `job/maillayouts` | Mail HTML stays server-sanitized in the restricted frame. Cached sender permission is stale after revoke: blocker #736. | | `job/dragghost-612` | Calendar drag feedback and styles only. No new write or authority boundary. | | `job/writeonopen-661` | Removes the editor's automatic trailing-node write on open. No new input boundary. | | `job/ryw-653` | ID repair and projection publication use existing User-scoped checked writers and database transactions. No new authority or outbound client. | | `job/perf-cache-665` | Cache keys include User, item, variant and revision. Session/access signals invalidate pending reads. Note ownership is checked before the exact-response ETag and 304 decision. | | `job/perf-snap-666` | Snapshot keys include User and URL state. Limits include pending reservations. Cleanup prevents late publication. No route adapter uses this module in the reviewed head. | | `job/perf-mut-667` | Receipts and inverse state use User-scoped keys and one writer transaction. Operation IDs and byte sizes are validated. Undo checks revision and expiry. The client checks owner and generation after awaits. | | `job/fix-499` | Search focus, sizing and action controls reuse existing navigation and menu actions. No new authorization or raw HTML boundary. | | `job/calimg-589` | Photo queries bind viewer and owner. Projection checks current Share roots. Plugin-owned path declarations exclude generic Activity; they do not grant authority. Thumbnail text uses existing SVG escaping. | | `job/voicefiles-620` | Used-in queries bind the User for Notes, Logs, Tasks and Events. Target stat uses Root and RelPath. MIME changes preserve existing Search access checks. New parser nesting is unbounded: blocker #816. | | `job/imaptest-625` | Vendored test change requires a fixed redacted provider error. No production change. | | `job/burst-709` | Test probe only. It keeps credentials and response bodies out of its reported evidence. Not executed in this review. | | `job/admin-burst-705` | Test HTTPS counters retain bounded transport state, with no request headers, paths or bodies. No new Admin route. | | `job/instant-663` | Documentation only. Cache rules retain the separation between Derived data and Security state. | All new private routes inherit session authority from the server. Cookie writes require the configured Origin. Read-only credentials reject writes and protocol upgrades. Plugin handlers then require the data scope and bind queries or Home access to that principal. No new outbound server fetch was found. Shared browser caches were checked against session cleanup and late response publication. The Tasks singleton and Mail reader cache do not each implement a session reset. The reviewed shell unmounts private views synchronously at session end and reloads before it mounts another User. That normal flow prevents the suspected User-switch leak. It does not fix sender permission changes within the same Mail session. These results cover the pinned source heads, not a combined application build. ## Decisions - Use local queued branch heads, and record their exact SHAs. All local and remote heads are compared before review. - Use static review and small local checks. Do not build the workspace or change product code. - No product design decision was required. Keep crash and privacy findings separate, and distinguish static evidence from runtime observations. ## Validation Local cache check (exit 0): ```text CONFIRMED: another warmed message retains revoked sender image permission. ``` Inert shared-cache checks (exit 0): ```text PASS: revision cache isolates Users and rejects a completion after session end. PASS: view snapshots isolate Users and reject a reservation after owner cleanup. ``` Final fetch and `git merge origin/dev` were run once. Integration output (exit 0): ```text Already up to date. ``` The integration base remains `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. All 25 remote heads still match the reviewed heads. Report and source-integrity checks (exit 0): ```text PASS: all 25 queued remote heads and saved diffs match the pinned review scope. PASS: committed changes contain only review-findings.md. PASS: inert cache checks use unchanged source from the reviewed branch heads. ``` `git diff --check` returned no output and exit 0. The inert checks above were rerun after the final fetch and merge. No Rust or web source was authored. `cargo fmt --check`, crate clippy/test, `bun run check` and `bun run test` were not run. This report makes no claim that the queued branches build or pass their gates. The review-specific job requires minimal cargo use. No live adversarial round was run: there was no API merge. The findings include regression test ideas for the repair jobs. Cleanup: `cargo clean` (with the required cargo environment) returned exit 0: ```text Removed 1 file, 356B total ``` Web build and `.svelte-kit` output were absent. Review artifacts stay in the ignored `artifacts/security-review/` directory. ## Known gaps - Static review does not prove runtime behavior or absence of all flaws. - No combined build, browser network capture, live protocol probe or crash test was run. No queued branch was merged by this review. - Rust and web build gates are not applicable to the authored report. The job expressly limits builds. No crate or package source was edited. - Snapshot adapters must check live Share access when they adopt #666. The reviewed head contains the primitive only. - Findings are filed for repair; this review does not change product code. - No UI feature was built. UX gaps closed and UX gaps left are not applicable. - No dependencies or versions were added or upgraded. The new media crate in the reviewed branch sets AGPL-3.0-only and has no external dependency.
Author
Owner

The real-data probe reached the head server and found one Files identity difference after migration: the Notes directory row's item ID changed, while the child Note, Daily note, Task and renamed File rows kept their IDs, paths, sizes and hashes. In the latest run the old Files listing had Notes ID de446454-547c-457e-a9d8-1394ff05f4d5, the head listing returned ebce9041-8695-46bf-858a-05eaa42fa93e, and the settled SQLite row after server shutdown had a5e2298c-4d33-4a36-9d2f-48383de548f7. The head Index comment for #627 says legacy directory rows without saved parent fingerprints receive a new ID when the target fingerprint changes; the Notes files do change during the calternal-id migration. I am recording this separately and will compare file IDs and data checksums while allowing the documented legacy-directory refresh. This may invalidate an old folder deep link, so please confirm whether #627's migration behavior is acceptable.

The real-data probe reached the head server and found one Files identity difference after migration: the `Notes` directory row's item ID changed, while the child Note, Daily note, Task and renamed File rows kept their IDs, paths, sizes and hashes. In the latest run the old Files listing had Notes ID `de446454-547c-457e-a9d8-1394ff05f4d5`, the head listing returned `ebce9041-8695-46bf-858a-05eaa42fa93e`, and the settled SQLite row after server shutdown had `a5e2298c-4d33-4a36-9d2f-48383de548f7`. The head Index comment for #627 says legacy directory rows without saved parent fingerprints receive a new ID when the target fingerprint changes; the Notes files do change during the calternal-id migration. I am recording this separately and will compare file IDs and data checksums while allowing the documented legacy-directory refresh. This may invalidate an old folder deep link, so please confirm whether #627's migration behavior is acceptable.
Author
Owner

Non-blocking #847: the maillayouts warm list refresh retains every cached row absent from the fresh page, even when the page is complete and empty (next=null). It saves the deleted row again and can reopen its retained body. The exact merge expression was checked in a local fixture; no provider or browser reproduction is claimed.

All 25 pinned branch diffs have now been screened. The report lists coverage and evidence limits. Fetched origin/dev once and merged it once; dev remains c4a61e8cf0 and the merge reported Already up to date. No reviewed branch was merged. Final documentation checks and cleanup follow.

Non-blocking #847: the maillayouts warm list refresh retains every cached row absent from the fresh page, even when the page is complete and empty (next=null). It saves the deleted row again and can reopen its retained body. The exact merge expression was checked in a local fixture; no provider or browser reproduction is claimed. All 25 pinned branch diffs have now been screened. The report lists coverage and evidence limits. Fetched origin/dev once and merged it once; dev remains c4a61e8cf090170f35b1bed3350d9de20c83ecd5 and the merge reported Already up to date. No reviewed branch was merged. Final documentation checks and cleanup follow.
Author
Owner

Finished the independent round 7b data-integrity review of 25 pinned heads.

Review branch: job/rev-7b-data.
Head: 19b18f6160.
Built: committed review-findings.md plus ignored local evidence fixtures. No product edits, push, deploy, or reviewed-branch merge.

Filed seven separate findings after duplicate searches:

  • BLOCKER #731: saved Task view replacement loses another client's acknowledged edit.
  • BLOCKER #777: Calendar Undo uses a refreshed revision and removes a later edit.
  • BLOCKER #844: retry after a linked Note body failure duplicates acknowledged Logs.
  • Non-blocking #790: Calendar retries keep the rejected ETag.
  • Non-blocking #826: an earlier month read replaces an acknowledged snapshot.
  • Non-blocking #829: an online warm Note open retains a deleted body after refusal.
  • Non-blocking #847: a complete Mail refresh retains deleted cached rows.

Final documentation gate: git diff --check c4a61e8cf0 HEAD returned exit 0 with no output. git status --short also returned no output. Six local fixture checks passed by reproducing faults; full verbatim output follows in the report. No Rust/web build gates or live-server round were run, as required by this read-mostly brief. Findings remain open; they were not fixed in this review job.

Decisions: freeze reviewed heads; distinguish inherited defects; use local collaborator fixtures instead of full builds; classify stale displays without accepted write loss as non-blocking. No product design choice was made. UX gaps closed: none. UX gaps left: #790, #826, #829, #847.

The fetched origin/dev stayed at c4a61e8cf0. The one requested merge reported Already up to date. Cargo cleanup completed; web build output was absent.

Round 7b data-integrity review

Issue: #427. Review branch: job/rev-7b-data.
Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Scope

Review writes to User files and the Index. Check Undo, live rooms, API,
MCP and DAV writes, Calendar edits, Tasks, retries and deleted-data caches.
Product code is outside this job. Branch diffs are stored in the ignored
artifacts/rev-7b/ directory.

Branch heads at review start

Branch Head Diff base
job/agentdocs-630 183ac356359f1f84afad30932f02e07323ddb7ed 3f258302a0f2d6418ff60c9ce22cbb33e008ca99
job/submenu-579 142c063a87206721042ae52d24140490a80627d3 3f258302a0f2d6418ff60c9ce22cbb33e008ca99
job/tasks-mode f620390c724ee08540d38b0ba69c3d12225fc1e4 3f258302a0f2d6418ff60c9ce22cbb33e008ca99
job/toastname-586 093db3ec12a26d1d9ed303853ee733fbc5e79ccd 3f258302a0f2d6418ff60c9ce22cbb33e008ca99
job/tocrail-636 144f0316a7ed6eb9f1ed495d21918e3e78656365 687ff703136e71e89f8dfba139e93cd0788b25c1
job/quirks-546 50a006765ab635665cc0fc53ccaef10d6210fdf0 687ff703136e71e89f8dfba139e93cd0788b25c1
job/calcard-series 5f7fdb96706aca0c457a6b7851f548f7f618ac85 687ff703136e71e89f8dfba139e93cd0788b25c1
job/editor-series a087d0aba9f39bbfb5447c2ce22db0de25fd37a0 687ff703136e71e89f8dfba139e93cd0788b25c1
job/reload-423 2399db841cf16ade3fbf47fcfab50578a3abe364 687ff703136e71e89f8dfba139e93cd0788b25c1
job/cal-e2e-569 96908cbef8bbf078ce73bbc1a51f432ba8c416c8 687ff703136e71e89f8dfba139e93cd0788b25c1
job/selalign-576 174b554e1a66f53b0c9d92ede41b47eac145f419 687ff703136e71e89f8dfba139e93cd0788b25c1
job/maillayouts f9f360e68f4e9ca106ddd7fea24d1f4363881a2b 687ff703136e71e89f8dfba139e93cd0788b25c1
job/dragghost-612 bc08062b167e59ca7ac39426a96481b2664b2e75 687ff703136e71e89f8dfba139e93cd0788b25c1
job/writeonopen-661 04c4a651be5a0da6c1311af9ad2d39bd289b8a09 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/ryw-653 4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/perf-cache-665 b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/perf-snap-666 253c2a00cade24a7f845a5e67f309093641b8850 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/perf-mut-667 52d2b17f805072cd0304d7a05fe0534523cc7bc3 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/fix-499 242022301673dc6746d89985ee36078743723591 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/calimg-589 421dd63735d116cba4961a0a3ca4c985baa83480 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/voicefiles-620 b7ef7a2ab57f45b5d46cd19b4560215acae918e3 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/imaptest-625 f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/burst-709 c421756ac9af5a9b653b7c9f53c41b3aca24de89 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/admin-burst-705 23a6fe0e0e326f789c886f366880f5b86683b287 c4a61e8cf090170f35b1bed3350d9de20c83ecd5
job/instant-663 e62249dedcc2c7d108e4432596d40aee6f5a4bc8 c4a61e8cf090170f35b1bed3350d9de20c83ecd5

Findings

F1 — BLOCKER: saved Task views lose acknowledged edits

Branch: job/tasks-mode at f620390c724ee08540d38b0ba69c3d12225fc1e4.
Evidence: crates/plugins/notes/src/task_views.rs:39,180,194,202 and
apps/web/src/lib/tasks/api.ts:84. The route accepts a complete view without
the revision the client read. It replaces that view in the current file, then
checks the current file hash. Two clients can read filter A. The first saves
filter B. The second changes the layout from its old copy and restores filter A
with a successful 200. The User lock does not prevent this lost update.

Expected: require the client revision and return 412 on a stale replacement.
Test idea: read twice, save a filter, then submit the other retained copy with a
layout change; preserve the first filter and every other view.
Proof: static control-flow review. No live-server reproduction.
Duplicate check: all issue titles, saved-view and stale-write searches; no
separate issue found. Filed: #731.

F2 — BLOCKER: Calendar Undo overwrites a later edit

Branch: job/calcard-series at 5f7fdb96706aca0c457a6b7851f548f7f618ac85.
Evidence: Calendar route +page.svelte:851, Journal adapter journal.ts:30,58,67,
and edits.ts:353. Undo captures the old fields without the revision returned
by its forward write. A later readDay can replace the module ETag with the
revision of another committed edit. Undo then submits the old fields with that
new revision. The server accepts the inverse and removes the later edit.

Proof: the local test imports the reviewed Journal adapter and stubs transport.
It commits B at R2, installs C at R3, reads the day, then submits the same patch
used by the Undo closure. The adapter sends R3 and final title A replaces C.
The Svelte closure is checked statically. No live-server test was run.
Expected: bind the inverse to the forward result revision or durable receipt.
This Undo design is inherited, and remains present in the reviewed head.
Filed: #777.

F3 — Calendar retries retain the rejected ETag

Branch: job/calcard-series at the F2 head. journal.ts:58 returns the cached
ETag. PATCH and DELETE do not discard it on 412. forgetDays:54 clears only
day promises. A Calendar range refresh contains no ETags. After another
Installation changes the entry, normal edit retries keep using the rejected
ETag until a fresh Journal read or document reload.

Proof: two calls to the reviewed patchEntry, with forgetDays between them,
both send R0 after a fixture change to R1. Both return 412. Non-blocking: the
server keeps the newer data. Expected: discard a rejected revision and refresh
the write precondition before a deliberate retry.
Filed: #790.

F4 — a late read replaces an acknowledged month snapshot

Branch: job/reload-423 at its recorded head. Evidence:
money/store.svelte.ts:125,146,245 and the month route +page.svelte:81,121.
A pending read captures only the session generation. Mutation invalidation
removes stored reports but does not fence that read. After the save stores the
new report, the old response replaces memory, browser storage and the view.

Proof: local test runs the branch store with transport and storage fixtures.
It observes the older marker after write invalidation and rememberMonth.
Svelte state runes use identity stubs. No live server or rendering check.
Non-blocking: no lost server write. Fence reads issued before a mutation.
Filed: #826.

F5 — an online warm Note open retains a deleted body after refusal

Branch: job/perf-cache-665 at its recorded head. Evidence:
notes/NoteView.svelte:150,182,319 and notes/collab.ts:213.
A warm open skips getNote. A refused room then enables fallback editing
without reading the Note again or clearing its cached body. If a deletion event
was missed while disconnected, the online view still presents the deleted Note.

Proof: local test runs the exact load and connect function bodies with
fixtures for the editor and provider. After refusal, the view stays ready,
retains the body, enables fallback and has made zero authoritative reads.
The renderer and live transport are outside this check. Non-blocking: no
accepted write to the deleted Note. Revalidate refusal and render missing on 404.
Filed: #829.

F6 — BLOCKER: retry after a linked body failure duplicates Logs

Branches: inherited Composer code in the recorded job/tasks-mode head;
Log batch server reviewed at the recorded job/ryw-653 head.
Evidence: composer/commit.ts:326,343,349,
composer/Composer.svelte:1279,1294,1297,1300, and Notes lib.rs:4355.
commitLogBatch acknowledges durable Logs before linked Note body writes.
If a body write fails, Send all keeps the original drafts. markCommitted
is after the rejected await. Sending those drafts again allocates new Log IDs.

Proof: local test runs the exact batch function body with prepare and transport
fixtures. The first call acknowledges log-1 before its body call rejects.
Retry of the same snapshot creates log-2. The draft retention path is checked
statically. No live server or rendered Composer test was run.
Expected: retry the retained body against the acknowledged stable Log ID.
This flaw is inherited, not claimed as a new change in either branch.
Filed: #844.

F7 — a complete Mail page retains deleted cached rows

Branch: job/maillayouts at its recorded head. Evidence:
mail/MailView.svelte:623,630,463,547 and mail/readerCache.ts:67.
The warm-page refresh appends every old row absent from the fresh page.
Even an empty complete page with next = null retains all deleted rows and
stores them again. A retained row can open its cached body without a new read.

Proof: local test executes the branch's exact list merge expression. A cached
removed row survives an authoritative empty complete page. The provider and
rendered route are outside this check. Non-blocking: no accepted deleted write.
Expected: reconcile the complete window and invalidate removed bodies.
Filed: #847.

Review observations

  • #667 puts the preference write, inverse and receipt in one writer transaction.
    Replay compares the canonical input. Legacy writes increase the revision.
    Undo rejects a newer revision and rolls back the Undo mark on rejection.

  • #653 publishes Note, Journal, Calendar and DAV projections before batch 201.
    The durable queued job remains for repair. File updates still use checked
    replacement and the same User lock.

  • #661 disables the editor's automatic trailing paragraph. The change removes
    a document edit triggered by mount and decoration transactions.

  • The #634 live-room path retains its flush lock, source ETag check and external
    block merge. Daily notes do not join live rooms. This is a static check, not
    evidence from a concurrent server run.

  • #620 changes voice memo MIME and backlink reads. It adds no audio file write.
    The Index rechecks the source fingerprint after its bounded MIME probe.

  • #666 adds a bounded snapshot primitive. It does not wire every Tab to it;
    adapter adoption stays in follow-up work.

  • #667 adds core migration 7 and Mail migration 10. The fetched origin/dev
    has core migrations through 6 and Mail through 9. No number collision was
    found. The SQL adds a table and a defaulted column; downgrade compatibility
    was read, not tested with an older binary.

Coverage

Every recorded branch diff was screened. Large branches received focused
write-path reads; UI-only changes received a check for storage callbacks.
This is not a full correctness audit of every line.

Branch Data-integrity checks
agentdocs-630 Public read-only documents; exact path bypass; no User-specific response state.
submenu-579 Menu callbacks and submenus; no new file or Index writer.
tasks-mode Saved Bases writes, Task creation, Status, repeat/DAV conversion, refresh and Composer. F1, F6.
toastname-586 Feedback labels retain existing mutation callbacks; inherited Undo issue is F2.
tocrail-636 Outline state and heading link decorations; explicit Copy link keeps existing anchor writer.
quirks-546 Layout and settings presentation; no new User-file write path.
calcard-series Journal revision cache, edits/moves, attachment removal/restore and Undo. F2, F3.
editor-series Card decorations keep the document tree; formatting uses existing editor commands.
reload-423 Warm stores, session generation and save guard; month mutation/read race is F4.
cal-e2e-569 Date landing rule preserves the source Daily note for a bare-time edit.
selalign-576 Selection and shared motion; no new file writer.
maillayouts User-scoped body/list caches, read-state/category updates and list refresh. F7.
dragghost-612 Ghost and label presentation; snap data and mutation callbacks stay in place.
writeonopen-661 Automatic trailing paragraph disabled; no new server writer.
ryw-653 Checked file writes, synchronous projections, queued repair and batch identity. F6 inherited retry gap.
perf-cache-665 Exact representation ETags, bounded reads, session invalidation and warm Note refusal. F5.
perf-snap-666 Snapshot bounds, reservations and invalidation; no domain adapter writes.
perf-mut-667 Receipt transaction, same-input replay, expired identity retention, inverse revision and legacy writer revision.
fix-499 Search and Calendar selection callbacks; no new storage mutation.
calimg-589 Calendar derived rows, photo dates, owned-path filtering and read-only thumbnail excerpt.
voicefiles-620 MIME probe version check, audio classification and read-only used-in references.
imaptest-625 Changes a redaction test to match the fixed error; no production mutation change.
burst-709 Projection test probe; no product writer change.
admin-burst-705 Test transport diagnostics; no admin mutation contract change.
instant-663 Decisions only; follow-up adapters remain separate.

Limits and known gaps

  • Three blockers and four non-blocking findings were filed. No product fixes
    belong to this review job. They remain open for their implementers.
  • Findings have static or local fixture evidence. No merged branch build,
    live API/DAV/MCP concurrency run, crash recovery test or browser run was done.
  • Branch composition was not executed. Review heads are fixed by the table;
    later branch commits need a delta review.
  • The fixtures are ignored review artifacts, not shipped regression tests.
    Each finding issue gives a regression test idea for its implementer.
  • No UI feature was built. UX gaps closed: none. UX gaps left: F3, F4, F5 and F7.
  • No hot path changed, so no new benchmark or performance measurement was run.

Final base update

Ran git fetch origin once, then git merge origin/dev once. Fetched dev:
c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Merge output:

Already up to date.

Validation

No product files changed. No Rust or web build run. Final fixture checks ran
five files in separate Bun processes: six checks reproduced the reported faults.
These tests pass when the defects occur; they do not prove the product is fixed.

Documentation gate: git diff --check and
git diff --check c4a61e8cf090170f35b1bed3350d9de20c83ecd5 HEAD.
Both returned exit 0 with no output. Rust and full web gates were not run:
this read-mostly job changed only this report and the brief excludes full builds.

Cleanup: cargo clean used the preset job target and required environment.
Verbatim output:

     Removed 1 file, 356B total

apps/web/build and apps/web/.svelte-kit were absent.
Local check: bun test artifacts/rev-7b/calendar-write-review.test.ts.
The tests assert that the faults occur. They are evidence, not product gates.

bun test v1.4.2 (744846f84)

artifacts/rev-7b/calendar-write-review.test.ts:
(pass) review: conflict retries keep sending the rejected ETag [2.54ms]
(pass) review: Undo uses a refreshed global ETag instead of its own committed revision [0.60ms]

 2 pass
 0 fail
 10 expect() calls
Ran 2 tests across 1 file. [224.00ms]

Local check: bun test artifacts/rev-7b/money-snapshot-review.test.ts.

bun test v1.4.2 (744846f84)

artifacts/rev-7b/money-snapshot-review.test.ts:
(pass) review: old read replaces an acknowledged month after write invalidation [2.16ms]

 1 pass
 0 fail
 3 expect() calls
Ran 1 test across 1 file. [61.00ms]

Local check: bun test artifacts/rev-7b/note-cache-refusal-review.test.ts.

bun test v1.4.2 (744846f84)

artifacts/rev-7b/note-cache-refusal-review.test.ts:
(pass) review: online warm Note refusal retains deleted body and enables fallback editing [2.05ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [129.00ms]

Local check: bun test artifacts/rev-7b/composer-partial-retry-review.test.ts.

bun test v1.4.2 (744846f84)

artifacts/rev-7b/composer-partial-retry-review.test.ts:
(pass) review: retry after a linked body failure creates another acknowledged Log [1.86ms]

 1 pass
 0 fail
 4 expect() calls
Ran 1 test across 1 file. [42.00ms]

Local check: bun test artifacts/rev-7b/mail-stale-page-review.test.ts.

bun test v1.4.2 (744846f84)

artifacts/rev-7b/mail-stale-page-review.test.ts:
(pass) review: authoritative empty Mail page retains every cached deleted row [0.57ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [63.00ms]

Decisions

Use the local job branch heads when available. Record their full SHA so
the review has a fixed scope. Use remote heads only when no local branch exists.
Separate inherited flaws from newly added behavior. File data corruption and
lost acknowledged edits as blockers. File stale displays as non-blocking when
no accepted server mutation is shown. Use small collaborator fixtures to check
client control flow without a full build on the shared host. These are review
method choices; no product design decision was made.

Finished the independent round 7b data-integrity review of 25 pinned heads. Review branch: job/rev-7b-data. Head: 19b18f6160324e72c5a5c9fa6ff2f9facdd7ad31. Built: committed review-findings.md plus ignored local evidence fixtures. No product edits, push, deploy, or reviewed-branch merge. Filed seven separate findings after duplicate searches: - BLOCKER #731: saved Task view replacement loses another client's acknowledged edit. - BLOCKER #777: Calendar Undo uses a refreshed revision and removes a later edit. - BLOCKER #844: retry after a linked Note body failure duplicates acknowledged Logs. - Non-blocking #790: Calendar retries keep the rejected ETag. - Non-blocking #826: an earlier month read replaces an acknowledged snapshot. - Non-blocking #829: an online warm Note open retains a deleted body after refusal. - Non-blocking #847: a complete Mail refresh retains deleted cached rows. Final documentation gate: git diff --check c4a61e8cf090170f35b1bed3350d9de20c83ecd5 HEAD returned exit 0 with no output. git status --short also returned no output. Six local fixture checks passed by reproducing faults; full verbatim output follows in the report. No Rust/web build gates or live-server round were run, as required by this read-mostly brief. Findings remain open; they were not fixed in this review job. Decisions: freeze reviewed heads; distinguish inherited defects; use local collaborator fixtures instead of full builds; classify stale displays without accepted write loss as non-blocking. No product design choice was made. UX gaps closed: none. UX gaps left: #790, #826, #829, #847. The fetched origin/dev stayed at c4a61e8cf090170f35b1bed3350d9de20c83ecd5. The one requested merge reported Already up to date. Cargo cleanup completed; web build output was absent. # Round 7b data-integrity review Issue: #427. Review branch: `job/rev-7b-data`. Base: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. ## Scope Review writes to User files and the Index. Check Undo, live rooms, API, MCP and DAV writes, Calendar edits, Tasks, retries and deleted-data caches. Product code is outside this job. Branch diffs are stored in the ignored `artifacts/rev-7b/` directory. ## Branch heads at review start | Branch | Head | Diff base | | --- | --- | --- | | `job/agentdocs-630` | `183ac356359f1f84afad30932f02e07323ddb7ed` | `3f258302a0f2d6418ff60c9ce22cbb33e008ca99` | | `job/submenu-579` | `142c063a87206721042ae52d24140490a80627d3` | `3f258302a0f2d6418ff60c9ce22cbb33e008ca99` | | `job/tasks-mode` | `f620390c724ee08540d38b0ba69c3d12225fc1e4` | `3f258302a0f2d6418ff60c9ce22cbb33e008ca99` | | `job/toastname-586` | `093db3ec12a26d1d9ed303853ee733fbc5e79ccd` | `3f258302a0f2d6418ff60c9ce22cbb33e008ca99` | | `job/tocrail-636` | `144f0316a7ed6eb9f1ed495d21918e3e78656365` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/quirks-546` | `50a006765ab635665cc0fc53ccaef10d6210fdf0` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/calcard-series` | `5f7fdb96706aca0c457a6b7851f548f7f618ac85` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/editor-series` | `a087d0aba9f39bbfb5447c2ce22db0de25fd37a0` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/reload-423` | `2399db841cf16ade3fbf47fcfab50578a3abe364` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/cal-e2e-569` | `96908cbef8bbf078ce73bbc1a51f432ba8c416c8` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/selalign-576` | `174b554e1a66f53b0c9d92ede41b47eac145f419` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/maillayouts` | `f9f360e68f4e9ca106ddd7fea24d1f4363881a2b` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/dragghost-612` | `bc08062b167e59ca7ac39426a96481b2664b2e75` | `687ff703136e71e89f8dfba139e93cd0788b25c1` | | `job/writeonopen-661` | `04c4a651be5a0da6c1311af9ad2d39bd289b8a09` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/ryw-653` | `4723c5f3b1ebfaa90905376e4a3d14e2ee60ae63` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/perf-cache-665` | `b88bc6ac888fd18e7e8a256f0b5b65ecaeed92c2` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/perf-snap-666` | `253c2a00cade24a7f845a5e67f309093641b8850` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/perf-mut-667` | `52d2b17f805072cd0304d7a05fe0534523cc7bc3` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/fix-499` | `242022301673dc6746d89985ee36078743723591` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/calimg-589` | `421dd63735d116cba4961a0a3ca4c985baa83480` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/voicefiles-620` | `b7ef7a2ab57f45b5d46cd19b4560215acae918e3` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/imaptest-625` | `f811d7aa4bf922ceb3948ddb5b6241944fb5cbd3` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/burst-709` | `c421756ac9af5a9b653b7c9f53c41b3aca24de89` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/admin-burst-705` | `23a6fe0e0e326f789c886f366880f5b86683b287` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | | `job/instant-663` | `e62249dedcc2c7d108e4432596d40aee6f5a4bc8` | `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` | ## Findings ### F1 — BLOCKER: saved Task views lose acknowledged edits Branch: `job/tasks-mode` at `f620390c724ee08540d38b0ba69c3d12225fc1e4`. Evidence: `crates/plugins/notes/src/task_views.rs:39,180,194,202` and `apps/web/src/lib/tasks/api.ts:84`. The route accepts a complete view without the revision the client read. It replaces that view in the current file, then checks the current file hash. Two clients can read filter A. The first saves filter B. The second changes the layout from its old copy and restores filter A with a successful 200. The User lock does not prevent this lost update. Expected: require the client revision and return 412 on a stale replacement. Test idea: read twice, save a filter, then submit the other retained copy with a layout change; preserve the first filter and every other view. Proof: static control-flow review. No live-server reproduction. Duplicate check: all issue titles, saved-view and stale-write searches; no separate issue found. Filed: [#731](https://git.kayg.org/kayg/calternal/issues/731). ### F2 — BLOCKER: Calendar Undo overwrites a later edit Branch: `job/calcard-series` at `5f7fdb96706aca0c457a6b7851f548f7f618ac85`. Evidence: Calendar route `+page.svelte:851`, Journal adapter `journal.ts:30,58,67`, and `edits.ts:353`. Undo captures the old fields without the revision returned by its forward write. A later `readDay` can replace the module ETag with the revision of another committed edit. Undo then submits the old fields with that new revision. The server accepts the inverse and removes the later edit. Proof: the local test imports the reviewed Journal adapter and stubs transport. It commits B at R2, installs C at R3, reads the day, then submits the same patch used by the Undo closure. The adapter sends R3 and final title A replaces C. The Svelte closure is checked statically. No live-server test was run. Expected: bind the inverse to the forward result revision or durable receipt. This Undo design is inherited, and remains present in the reviewed head. Filed: [#777](https://git.kayg.org/kayg/calternal/issues/777). ### F3 — Calendar retries retain the rejected ETag Branch: `job/calcard-series` at the F2 head. `journal.ts:58` returns the cached ETag. PATCH and DELETE do not discard it on 412. `forgetDays:54` clears only day promises. A Calendar range refresh contains no ETags. After another Installation changes the entry, normal edit retries keep using the rejected ETag until a fresh Journal read or document reload. Proof: two calls to the reviewed `patchEntry`, with `forgetDays` between them, both send R0 after a fixture change to R1. Both return 412. Non-blocking: the server keeps the newer data. Expected: discard a rejected revision and refresh the write precondition before a deliberate retry. Filed: [#790](https://git.kayg.org/kayg/calternal/issues/790). ### F4 — a late read replaces an acknowledged month snapshot Branch: `job/reload-423` at its recorded head. Evidence: `money/store.svelte.ts:125,146,245` and the month route `+page.svelte:81,121`. A pending read captures only the session generation. Mutation invalidation removes stored reports but does not fence that read. After the save stores the new report, the old response replaces memory, browser storage and the view. Proof: local test runs the branch store with transport and storage fixtures. It observes the older marker after write invalidation and `rememberMonth`. Svelte state runes use identity stubs. No live server or rendering check. Non-blocking: no lost server write. Fence reads issued before a mutation. Filed: [#826](https://git.kayg.org/kayg/calternal/issues/826). ### F5 — an online warm Note open retains a deleted body after refusal Branch: `job/perf-cache-665` at its recorded head. Evidence: `notes/NoteView.svelte:150,182,319` and `notes/collab.ts:213`. A warm open skips `getNote`. A refused room then enables fallback editing without reading the Note again or clearing its cached body. If a deletion event was missed while disconnected, the online view still presents the deleted Note. Proof: local test runs the exact `load` and `connect` function bodies with fixtures for the editor and provider. After refusal, the view stays ready, retains the body, enables fallback and has made zero authoritative reads. The renderer and live transport are outside this check. Non-blocking: no accepted write to the deleted Note. Revalidate refusal and render missing on 404. Filed: [#829](https://git.kayg.org/kayg/calternal/issues/829). ### F6 — BLOCKER: retry after a linked body failure duplicates Logs Branches: inherited Composer code in the recorded `job/tasks-mode` head; Log batch server reviewed at the recorded `job/ryw-653` head. Evidence: `composer/commit.ts:326,343,349`, `composer/Composer.svelte:1279,1294,1297,1300`, and Notes `lib.rs:4355`. `commitLogBatch` acknowledges durable Logs before linked Note body writes. If a body write fails, Send all keeps the original drafts. `markCommitted` is after the rejected await. Sending those drafts again allocates new Log IDs. Proof: local test runs the exact batch function body with prepare and transport fixtures. The first call acknowledges `log-1` before its body call rejects. Retry of the same snapshot creates `log-2`. The draft retention path is checked statically. No live server or rendered Composer test was run. Expected: retry the retained body against the acknowledged stable Log ID. This flaw is inherited, not claimed as a new change in either branch. Filed: [#844](https://git.kayg.org/kayg/calternal/issues/844). ### F7 — a complete Mail page retains deleted cached rows Branch: `job/maillayouts` at its recorded head. Evidence: `mail/MailView.svelte:623,630,463,547` and `mail/readerCache.ts:67`. The warm-page refresh appends every old row absent from the fresh page. Even an empty complete page with `next = null` retains all deleted rows and stores them again. A retained row can open its cached body without a new read. Proof: local test executes the branch's exact list merge expression. A cached removed row survives an authoritative empty complete page. The provider and rendered route are outside this check. Non-blocking: no accepted deleted write. Expected: reconcile the complete window and invalidate removed bodies. Filed: [#847](https://git.kayg.org/kayg/calternal/issues/847). ### Review observations - #667 puts the preference write, inverse and receipt in one writer transaction. Replay compares the canonical input. Legacy writes increase the revision. Undo rejects a newer revision and rolls back the Undo mark on rejection. - #653 publishes Note, Journal, Calendar and DAV projections before batch 201. The durable queued job remains for repair. File updates still use checked replacement and the same User lock. - #661 disables the editor's automatic trailing paragraph. The change removes a document edit triggered by mount and decoration transactions. - The #634 live-room path retains its flush lock, source ETag check and external block merge. Daily notes do not join live rooms. This is a static check, not evidence from a concurrent server run. - #620 changes voice memo MIME and backlink reads. It adds no audio file write. The Index rechecks the source fingerprint after its bounded MIME probe. - #666 adds a bounded snapshot primitive. It does not wire every Tab to it; adapter adoption stays in follow-up work. - #667 adds core migration 7 and Mail migration 10. The fetched `origin/dev` has core migrations through 6 and Mail through 9. No number collision was found. The SQL adds a table and a defaulted column; downgrade compatibility was read, not tested with an older binary. ## Coverage Every recorded branch diff was screened. Large branches received focused write-path reads; UI-only changes received a check for storage callbacks. This is not a full correctness audit of every line. | Branch | Data-integrity checks | | --- | --- | | `agentdocs-630` | Public read-only documents; exact path bypass; no User-specific response state. | | `submenu-579` | Menu callbacks and submenus; no new file or Index writer. | | `tasks-mode` | Saved Bases writes, Task creation, Status, repeat/DAV conversion, refresh and Composer. F1, F6. | | `toastname-586` | Feedback labels retain existing mutation callbacks; inherited Undo issue is F2. | | `tocrail-636` | Outline state and heading link decorations; explicit Copy link keeps existing anchor writer. | | `quirks-546` | Layout and settings presentation; no new User-file write path. | | `calcard-series` | Journal revision cache, edits/moves, attachment removal/restore and Undo. F2, F3. | | `editor-series` | Card decorations keep the document tree; formatting uses existing editor commands. | | `reload-423` | Warm stores, session generation and save guard; month mutation/read race is F4. | | `cal-e2e-569` | Date landing rule preserves the source Daily note for a bare-time edit. | | `selalign-576` | Selection and shared motion; no new file writer. | | `maillayouts` | User-scoped body/list caches, read-state/category updates and list refresh. F7. | | `dragghost-612` | Ghost and label presentation; snap data and mutation callbacks stay in place. | | `writeonopen-661` | Automatic trailing paragraph disabled; no new server writer. | | `ryw-653` | Checked file writes, synchronous projections, queued repair and batch identity. F6 inherited retry gap. | | `perf-cache-665` | Exact representation ETags, bounded reads, session invalidation and warm Note refusal. F5. | | `perf-snap-666` | Snapshot bounds, reservations and invalidation; no domain adapter writes. | | `perf-mut-667` | Receipt transaction, same-input replay, expired identity retention, inverse revision and legacy writer revision. | | `fix-499` | Search and Calendar selection callbacks; no new storage mutation. | | `calimg-589` | Calendar derived rows, photo dates, owned-path filtering and read-only thumbnail excerpt. | | `voicefiles-620` | MIME probe version check, audio classification and read-only used-in references. | | `imaptest-625` | Changes a redaction test to match the fixed error; no production mutation change. | | `burst-709` | Projection test probe; no product writer change. | | `admin-burst-705` | Test transport diagnostics; no admin mutation contract change. | | `instant-663` | Decisions only; follow-up adapters remain separate. | ## Limits and known gaps - Three blockers and four non-blocking findings were filed. No product fixes belong to this review job. They remain open for their implementers. - Findings have static or local fixture evidence. No merged branch build, live API/DAV/MCP concurrency run, crash recovery test or browser run was done. - Branch composition was not executed. Review heads are fixed by the table; later branch commits need a delta review. - The fixtures are ignored review artifacts, not shipped regression tests. Each finding issue gives a regression test idea for its implementer. - No UI feature was built. UX gaps closed: none. UX gaps left: F3, F4, F5 and F7. - No hot path changed, so no new benchmark or performance measurement was run. ## Final base update Ran `git fetch origin` once, then `git merge origin/dev` once. Fetched dev: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Merge output: ```text Already up to date. ``` ## Validation No product files changed. No Rust or web build run. Final fixture checks ran five files in separate Bun processes: six checks reproduced the reported faults. These tests pass when the defects occur; they do not prove the product is fixed. Documentation gate: `git diff --check` and `git diff --check c4a61e8cf090170f35b1bed3350d9de20c83ecd5 HEAD`. Both returned exit 0 with no output. Rust and full web gates were not run: this read-mostly job changed only this report and the brief excludes full builds. Cleanup: `cargo clean` used the preset job target and required environment. Verbatim output: ```text Removed 1 file, 356B total ``` `apps/web/build` and `apps/web/.svelte-kit` were absent. Local check: `bun test artifacts/rev-7b/calendar-write-review.test.ts`. The tests assert that the faults occur. They are evidence, not product gates. ```text bun test v1.4.2 (744846f84) artifacts/rev-7b/calendar-write-review.test.ts: (pass) review: conflict retries keep sending the rejected ETag [2.54ms] (pass) review: Undo uses a refreshed global ETag instead of its own committed revision [0.60ms] 2 pass 0 fail 10 expect() calls Ran 2 tests across 1 file. [224.00ms] ``` Local check: `bun test artifacts/rev-7b/money-snapshot-review.test.ts`. ```text bun test v1.4.2 (744846f84) artifacts/rev-7b/money-snapshot-review.test.ts: (pass) review: old read replaces an acknowledged month after write invalidation [2.16ms] 1 pass 0 fail 3 expect() calls Ran 1 test across 1 file. [61.00ms] ``` Local check: `bun test artifacts/rev-7b/note-cache-refusal-review.test.ts`. ```text bun test v1.4.2 (744846f84) artifacts/rev-7b/note-cache-refusal-review.test.ts: (pass) review: online warm Note refusal retains deleted body and enables fallback editing [2.05ms] 1 pass 0 fail 1 expect() calls Ran 1 test across 1 file. [129.00ms] ``` Local check: `bun test artifacts/rev-7b/composer-partial-retry-review.test.ts`. ```text bun test v1.4.2 (744846f84) artifacts/rev-7b/composer-partial-retry-review.test.ts: (pass) review: retry after a linked body failure creates another acknowledged Log [1.86ms] 1 pass 0 fail 4 expect() calls Ran 1 test across 1 file. [42.00ms] ``` Local check: `bun test artifacts/rev-7b/mail-stale-page-review.test.ts`. ```text bun test v1.4.2 (744846f84) artifacts/rev-7b/mail-stale-page-review.test.ts: (pass) review: authoritative empty Mail page retains every cached deleted row [0.57ms] 1 pass 0 fail 1 expect() calls Ran 1 test across 1 file. [63.00ms] ``` ## Decisions Use the local job branch heads when available. Record their full SHA so the review has a fixed scope. Use remote heads only when no local branch exists. Separate inherited flaws from newly added behavior. File data corruption and lost acknowledged edits as blockers. File stale displays as non-blocking when no accepted server mutation is shown. Use small collaborator fixtures to check client control flow without a full build on the shared host. These are review method choices; no product design decision was made.
Author
Owner

rev-a11y accessibility source audit complete.

Branch: job/rev-a11y. Head: 25eb9623626d597ef16357ada4aa0bc030953c7d. Audited origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.

Built and committed review-findings.md in five atomic documentation commits. Twelve new component-family issues are linked below; #658 received duplicate evidence. Each new issue contains context, source anchors, expected behavior, a fix and a test idea. Follow-up comments correct several initial line references. The final diff changes only the report. No product edits, push or deployment.

The requested one-time origin/dev sync was already up to date. Final checks and their output, cleanup, UX gaps and decisions follow. Full app gates and production visual/assistive-technology checks were not run, as specified for this read-mostly job.

Web accessibility review — rev-a11y

This report records the source audit requested by the rev-a11y job. It does
not certify WCAG conformance. No product files change in this job.

Scope and evidence

  • Branch: job/rev-a11y.
  • Initial origin/dev: c4a61e8cf090170f35b1bed3350d9de20c83ecd5.
  • Rules: CLAUDE.md, CONTEXT.md, DESIGN §§28, 34, 35, 38 and 57; the current
    keyboard-motion rule (#611) replaces #527.
  • Standard: WCAG 2.2, levels A and AA.
    The project also requires 44 px touch targets and reduced motion. WCAG
    2.5.8 AA uses 24 px with exceptions; 44 px is the stricter project rule.
  • Method: source inspection of packages/ui and apps/web, duplicate
    searches in Forgejo, and small checks where needed. Production builds,
    screenshots and full Rust gates are outside this read-mostly job.
  • The job assigns #427. That issue currently describes Log attachments.
    The start comment records the mismatch. The audit follows the job prompt.

Findings

A1 — Shared focus containment accepts hidden controls

Priority: P1. Family: focus traps and Settings surfaces.
Tracking: #740.
Criteria: 2.4.3 Focus Order, 2.1.1 Keyboard.

Evidence:

  • apps/web/src/lib/a11y/focusTrap.ts:87: tabbables() checks the control's
    own hidden flag and an inert ancestor. It does not check a hidden
    ancestor, CSS visibility, or hidden input types.
  • apps/web/src/lib/a11y/focusTrap.ts:142: moveFocusIn() accepts an
    explicit target without a visibility check. A failed focus has no fallback.
  • apps/web/src/routes/settings/[...path]/+page.svelte:294: initial focus
    can select .settings-shell .shell-back.
  • apps/web/src/routes/settings/[...path]/+page.svelte:523: the phone
    layout hides the content's .settings-detail-nav, which contains that
    Back button. The visible Back button is in the shared sheet chrome,
    outside .settings-shell.

Effect: a phone Settings detail link can request focus on a hidden Back
button. Hidden candidates can also prevent Tab wrapping at the visible end
of a surface.

Fix: use one visibility-aware focus candidate predicate. Apply it to
initial focus and containment. Check hidden/inert ancestors and hidden
input types. Resolve CSS visibility only during focus operations, with no
per-frame observers. If the requested target cannot take focus, use the
first visible control or the surface. Select the visible sheet Back button
for phone Settings.

Test: open a Settings detail link with a keyboard at 390 px. Check that
focus is inside the visible dialog. Add hidden first/last candidates to the
focus-trap tests. Check Tab and Shift+Tab wrapping and Escape restoration.

A2 — PDF previews expose page numbers without document text

Priority: P1. Family: shared PDF viewer.
Tracking: #741.
Criteria: 1.1.1 Non-text Content, 1.3.1 Info and Relationships.

Evidence:

  • packages/ui/src/components/viewer/PdfView.svelte:6: the module states
    that there is no text layer.
  • packages/ui/src/components/viewer/PdfView.svelte:74: pdf.js draws each
    page on a canvas.
  • packages/ui/src/components/viewer/PdfView.svelte:111: each canvas has
    only a page-number label and no content alternative.
  • packages/ui/src/components/viewer/QuickLook.svelte:235: Quick Look uses
    this viewer for PDF files.

Effect: a screen reader can find the PDF name and page number, but cannot
read its text in the preview. This applies to Files, Photos and public-link
previews that use Quick Look.

Fix: add a lazy pdf.js text layer and the available document structure.
Keep canvas paint decorative when an equivalent text layer exists. Keep
page loading bounded. For image-only pages, show a real text-unavailable
state and retain Download. Do not invent OCR output.

Test: open a small text PDF in Quick Look. Check that body text and reading
order reach the accessibility tree, that links work with the keyboard, and
that unloaded pages do not allocate all text layers at once.

A3 — Search scope and subfolder controls have no keyboard route

Priority: P1. Family: search filter chips.
Tracking: #742.
Criterion: 2.1.1 Keyboard.

Evidence:

  • apps/web/src/lib/search/SearchField.svelte:276: the Ask context removal
    button has tabindex="-1".
  • apps/web/src/lib/search/SearchField.svelte:282: the Calendar scope
    removal button has tabindex="-1".
  • apps/web/src/lib/search/SearchField.svelte:295: Include subfolders has
    tabindex="-1". Its click handler is the only UI call to setSubfolders().
  • apps/web/src/lib/search/SearchField.svelte:182: the chip key handler
    can select, remove or edit ordinary pills. It cannot focus or activate
    these nested controls or clear the separate Calendar/Ask scope chips.

Effect: a keyboard User cannot use the visible Include subfolders action
or remove those scope chips through their controls.

Fix: give the controls real Tab stops, or extend the shared chip keyboard
model to reach and activate every action. Preserve the typed query. Give
the active control visible focus and announce changes once.

Test: open scoped search with a keyboard. Toggle Include subfolders with
Space, clear Calendar scope, and clear Ask context. Check the result scope
and query without pointer input.

A4 — Warm tooltips cannot stay open under the pointer

Priority: P2. Family: shared warm tooltips.
Tracking: #744.
Criterion: 1.4.13 Content on Hover or Focus.

Evidence:

  • packages/ui/src/components/tooltip/TooltipLayer.svelte:208: pointer
    exit hides the bubble unless the pointer stays inside the trigger.
  • packages/ui/src/components/tooltip/TooltipLayer.svelte:391: the bubble
    has pointer-events: none.

Effect: moving from a small toolbar control onto its tooltip closes the
text. A User who magnifies the interface cannot keep the text visible while
moving to read it. Escape dismissal already exists.

Fix: retain the tooltip while either the trigger or bubble is hovered.
Allow pointer entry to the bubble. Use a bounded grace period across the
gap. Preserve Escape dismissal, keyboard behavior and touch peek behavior.

Test: hover a toolbar action, move through the gap into the tooltip, and
check that it stays visible. Escape must close it without moving focus.
Leaving both regions must close it.

A5 — Interactive TagPill targets stay compact on touch

Priority: P2. Family: shared TagPill.
Tracking: #745.
Rule: DESIGN §35, 44 px touch targets. Also inspect 2.5.8 with spacing.

Evidence:

  • packages/ui/src/components/TagPill.svelte:43: the interactive button
    does not use touch-hit or a minimum target token.
  • packages/ui/src/components/TagPill.svelte:68: the small form uses text
    size and 3 px vertical padding. The medium form has a 40 px height fallback.
  • apps/web/src/lib/components/TagEditor.svelte:143: matching suggestions
    render interactive TagPills without a larger control wrapper.
  • packages/ui/src/components/calendar/ItemPreview.svelte:220: Tags in
    the Calendar preview use the same compact interactive form.

Effect: the touch User must hit a compact chip. Shared sizing tokens do not
increase this button to the project minimum.

Fix: keep the compact paint. Add a shared 44 px coarse-pointer target and
enough spacing that adjacent targets do not overlap. Reuse touch-hit if
its expanded area fits the layout; otherwise grow the button's layout box.

Test: inspect the effective targets in Tag editor suggestions and Calendar
preview Tags at 390 and 820 px with touch emulation. Check short Tags and
adjacent remove actions. Each action must have a separate 44 px target.

A6 — Focus style divergence is already tracked by #658

Tracking: #658. No duplicate
issue is needed.

Evidence: apps/web/src/calternal-app.css:252 adds two box-shadow bands to
the global focus outline; packages/ui/src/tokens.css:1487 also defines a
global outline. Local variants include Checkbox.svelte:181,
Select.svelte:107, TimelineScrubber.svelte:213 and
actions/resizableEdge.ts:94. The native Checkbox input is transparent and
paints its focus on the sibling indicator. The guard must check this
indirect focus paint as well as the active input.

Fix and acceptance remain in #658. Keep keyboard focus visible when styles
are consolidated. Do not remove motion for keyboard input.

A7 — Composer has invisible actionable Tab stops

Priority: P2. Family: Composer.
Tracking: #819.
Criterion: 2.4.7 Focus Visible.

Evidence: apps/web/src/lib/composer/Composer.svelte:1849 and :1850
render Stack draft and Discard draft as native .sr-only buttons. They
remain Tab stops. apps/web/src/calternal-app.css:260 clips that class to a
1 px box, with no focus reveal in the app or component.

Effect: a keyboard User reaches an invisible action and cannot see its
name or focus. The screen-reader alternative must remain available.

Fix: reuse a shared focus-reveal utility or the visible Composer action
surface. Show the label and full ring while the action has keyboard focus.
Keep draft guards and recovery. Keep shared keyboard motion.

Test: Tab to both actions with a real draft. Check visible names and focus,
Enter/Space activation, and discard recovery at each required width/theme.

A8 — Photos scrubber has a 28 px touch target

Priority: P2. Family: Photos timeline scrubber.
Tracking: #820.
Rule: DESIGN §35, 44 px touch targets.

Evidence: apps/web/src/lib/photos/TimelineScrubber.svelte:274 makes the
touch track ignore pointers. Its touch thumb is 28 × 48 px at :291 and
accepts pointers only while active or dragging at :313. The outer 44 px
strip does not increase the effective target.

Fix: keep the narrow painted grip. Expand its effective target to 44 × 48 px
inside the strip. Keep normal scrolling outside the target and pointer
capture on the hit target. Use CSS.

Test: scroll a multi-month timeline until the handle appears. Start a touch
drag beside the painted grip within the expanded target. Check capture,
jump, adjacent item menus, and scrolling outside the target. Check existing
keyboard slider actions. This is a project-rule finding, not a claim that
the basic 24 px WCAG 2.5.8 AA size fails.

A9 — Zoomed image previews cannot pan with the keyboard

Priority: P1. Family: shared image viewer.
Tracking: #830.
Criteria: 2.1.1 Keyboard, 2.5.7 Dragging Movements.

Evidence: packages/ui/src/components/viewer/ImageView.svelte:97 changes
the image offset only in the pointer-drag path. The exported API at :114
supplies zoom only. viewer/QuickLook.svelte:155 maps navigation keys to
item changes and at :165 maps zoom keys to zoomBy(). The stage clips
enlarged edges at ImageView.svelte:161.

Effect: a keyboard User can enlarge an image but cannot inspect its edges
at that zoom. Custom panning also lacks a single-click or tap alternative.

Fix: add a bounded shared pan command. Expose keyboard actions and named
pan controls that work by one click or tap. Keep item navigation distinct.
Use the shared shortcut registry, tooltip and 44 px target primitives.

Test: inspect every image edge at enlarged zoom with keyboard actions, then
with individual clicks/taps without dragging. Check Reset, item navigation,
focus, names, target size and reduced motion at all required widths/themes.

A10 — Category labels break persistent error descriptions

Priority: P2. Family: inline category fields.
Tracking: #831.
Criterion: 1.3.1 Info and Relationships.

Evidence: apps/web/src/lib/components/money/AssignedCell.svelte:92 and
:102 build the error reference and ID from the category label. The caller
at apps/web/src/routes/money/[budget]/[month]/+page.svelte:268 passes
category.name. A space in a label splits aria-describedby into separate
ID references. Repeated labels can also collide.

Effect: the initial alert can speak, but the invalid field has no valid
persistent error description for these labels.

Fix: use $props.id() or a supplied stable item ID and a fixed suffix. Keep
the visible category label as the field name. Do not sanitize labels into IDs.

Test: use a label with spaces and repeated labels across groups. Each
invalid field must reference one existing, unique error element. Check the
accessible description when focus returns. Use no real financial data.

A11 — Analytics heatmap readouts lack full-size touch access

Priority: P2. Family: Analytics marks.
Tracking: #845.
Rule: DESIGN §35, 44 px touch targets.

Evidence: apps/web/src/lib/components/analytics/BklitAnalyticsHeatmapMarks.tsx:49
and :55 use the painted bin dimensions minus the gap. These dimensions
reach the buttons at BklitKeyboardMarks.tsx:120. The shared heatmap column
tokens at packages/ui/src/tokens.css:20 range from 13 to 18 px at the
default 16 px root. BklitAnalytics.tsx:995 caps Calendar heatmap width with
the maximum token and supplies a 2 px gap. Its mark CSS at :335 adds no
larger touch target. No full-size readout alternative exists in the wrapper.

Fix: preserve exact-cell fine-pointer hover and keyboard access. Provide a
real-data readout list or day/hour selector with separate 44 px targets for
touch. Reuse the same readout values. Do not overlap enlarged cell targets.

Test: reach each value in a long Calendar range and the Time of day chart
through full-size touch actions. Check parity with hover and keyboard
readouts. Measure target size and spacing for WCAG 2.5.8. The date-navigation
exemption does not supply an equivalent Analytics readout.

A12 — Live Photo playback has a 28 px touch target

Priority: P2. Family: Live Photo playback.
Tracking: #846.
Rule: DESIGN §35, 44 px touch targets.

Evidence: packages/ui/src/components/viewer/LiveMotion.svelte:74 renders
a named native toggle, but the target is 28 px high at :109. It has no
expanded hit area or coarse-pointer size rule. QuickLook.svelte:233 places
it outside the header action group that has larger control targets.

Fix: preserve the compact paint and expand the coarse-pointer target with
the shared target primitive, or grow its control box. Keep adjacent actions
separate and preserve pressed state and explicit reduced-motion playback.

Test: tap the expanded area, check playback/state, and verify 44 px effective
targets. Check Space/Enter and reduced motion at all required widths/themes.

A13 — Modal backgrounds stay live despite the inert contract

Priority: P1. Family: modal shell.
Tracking: #848.
Rule: shared modal contract and the ARIA modal-dialog pattern.

Evidence: apps/web/src/lib/overlay/state.svelte.ts:3 says the layout makes
the feed inert. open() at :50 changes a count and dismisses transient UI.
apps/web/src/routes/+layout.svelte:796 and :819 do not bind inert to the
app frame or route content. Only the bottom Tab Bar and Primary Pill consume
overlay state to remove interaction. The desktop sidebar at
apps/web/src/lib/components/app-sidebar.svelte:451 is inert only when
collapsed. packages/ui/src/actions/portal.ts:24 moves a branch without
isolating siblings. focusTrap.ts:229 listens only for local keydown, with
no document focus-entry guard.

Effect: background controls can still receive programmatic focus and then
keyboard input. A scrim and local Tab loop do not make a background inert.
This confirms the source contract gap. Reader-specific exposure despite
aria-modal requires a production assistive-technology check.

Fix: give modal background state one shared owner. Keep background app
content and lower dialogs inert while the top dialog is open, including its
exit. Preserve nested menus and the supported toast action path. Restore
prior state and focus on close. Update the stale comments with the fix.

Test: open Search over focusable route content and an expanded sidebar.
Attempt a delayed background focus. Open a confirmation over Settings;
check upper-dialog focus, then restoration to Settings and the app opener.
Check Escape, nested menus, toast actions and screen-reader exploration.

Behavior evidence

A small browser harness bundles the actual focusTrap.ts and its local
dependencies. It uses one Chromium browser at 390 px with macOS platform
emulation. The fixture is behavior evidence, not a UI visual review.

node artifacts/rev-a11y/focus-probe.mjs exits 0 and prints:

REPRODUCED #740: hidden explicit initial target leaves focus outside the trap.
REPRODUCED #740: a hidden last candidate lets Tab leave the trap.
REPRODUCED #848 supporting behavior: the shared trap permits programmatic background focus.
REPRODUCED #831: a category label with a space resolves no error-description ID references.

The first case requests a button under display:none and leaves focus on
the outside opener. In the second case, a hidden last candidate prevents
wrapping and native Tab reaches an outside button. The full phone Settings
route remains an acceptance test for the fix.

The added #848 case tests the shared action with visible controls. It does
not render OverlaySurface; source inspection establishes the missing inert
binding in the app. The #831 fixture tests the browser's ID-reference parsing
with a neutral label. It does not render the full category editor.

Source-lint coverage

A sequential scan uses the installed Svelte compiler. It removes only
accessibility-suppression comments in memory, while preserving positions.
It writes no product output. First scan output:

Source lint: Svelte 5.57.1; 243 files; 19 accessibility warnings; 0 compiler errors. Warnings require human review.

Warnings alone are not findings. Several wrappers delegate keyboard actions
to their child controls; programmatic deep-link targets and initial focus
also require context. The report records only defects with source evidence.

Coverage and limits

The source scan covers 243 Svelte files in packages/ui/src and
apps/web/src, excluding the test-fixture directory. Manual review follows
the shared controls and their route consumers. It includes the React chart
interaction adapters that Svelte mounts.

Family or route Source checks and result
Shared controls Checkbox input/mark modes, Toggle, Select, Disclosure, Pill, TagPill and SegmentedControl names, roles, state and input handling. A5 and A6 need fixes.
Shell and navigation Skip link, route focus target, ModeHeader, Tab Bar, sidebar, shortcut card, roving toolbar and resizable separators. Modal behavior needs A1/A13; ring variants belong to #658.
Menus and surfaces Menu roving focus, typeahead, disabled rows, submenu/drill Back, Escape and restoration; ConfirmSheet safe initial focus and error alert; portal and focus-trap contracts. Hidden targets and background isolation need A1/A13.
Calendar and Journal Day/Week/Month/Year grids, Agenda, MiniMonth, DateStrip, ItemPreview, attachment actions, Daily-note links and Journal loading/error states. Date-grid size exceptions have equivalent navigation in DESIGN §35. Task completion is already #657.
Files and sharing FileCollection listbox selection/active descendant, rename input, FilesBrowser, Trash/Recent, ShareDialog labels/errors, public password/upload/editor controls and Quick Look. Do not count decorative selection marks as nested checkboxes. A2/A9/A12 affect shared previews.
Photos Timeline virtual focus and selection naming, item links/menus, Quick Look and slider keyboard/value text. A8 records its touch-target gap. A9/A12 affect the viewer.
Notes and editor Named multiline editor, format toolbar, block actions/handle, properties, mentions and live/offline states. Focused block handles have their own reveal rules. A6 still covers focus consistency.
Search and Tags Combobox/result options, virtual active descendant, photo results, saved-search naming/actions, scope/filter chips and previews. A3 and A5 need fixes.
Composer Field labels/help, draft/status live regions, overlay focus, attachment actions and gesture alternatives. A7 records clipped actionable Tab stops.
Analytics Chart mark names, roving focus, Escape/readout state, real empty/loading/error states, range picker and reduced-motion hooks. A11 requests full-size touch access.
Inline category forms Wrapped labels, input modes, validation alerts and inline keyboard editing. A10 records invalid error-description IDs. No financial data is collected or reported.
Mail, notifications and agent turns Native row links/actions, disclosure state, named message frame, unread text, reminder controls, notification status and transcript log. Shared overlays need A13.
Authentication and Settings Sign-in/setup/recovery labels, recovery-word combobox, errors/status, switches, disclosures, Appearance sliders and admin configuration error description. Phone Settings focus needs A1.
Route errors and link resolution Named unknown-route/offline/error states, Retry/Sign in actions and status announcements. No placeholder data is used as evidence.
Tokens and motion Shared focus/input-modality guard, 44 px coarse-pointer roles, CSS reduced-motion override, JS motion helpers and curated material contrast tests. Keyboard timing follows #611.

The 19 compiler warnings were read in context. Most are programmatic focus
targets, wrapper listeners with child keyboard controls, or delegated input
handlers. VideoView has no caption track and suppresses that warning; media
caption/transcript requirements need a content and playback review. No blanket
media conformance claim is made.

Source checks cannot certify screen-reader reading order, actual focus-ring
paint, reflow at zoom, touch spacing or contrast over User backgrounds.
The fix jobs need real production builds at 390, 820 and 1440 px, light and
dark, with macOS rendering. No screenshots were taken in this read-mostly
job. VoiceOver, another screen reader, native media controls and real touch
devices were not tested.

Verification

The required one-time git fetch origin && git merge origin/dev ran before
the final checks. Output, exit 0:

Already up to date.

origin/dev remains c4a61e8cf090170f35b1bed3350d9de20c83ecd5; no reviewed
product source changed. The final diff contains only this report.

The focused existing test uses Vitest 5.0.1 with one worker and an audit-only
config. It imports the real shared theme module directly through the app's
theme facade. This avoids the UI barrel and a full app build. Output, exit 0:

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/rev-a11y/apps/web


 Test Files  1 passed (1)
      Tests  74 passed (74)
   Start at  15:23:39
   Duration  57.58s (tests 79%, transform 17%, import 3%, worker 1%)

The theme checks include AA text roles across curated materials and control
contrast. They do not replace rendered checks. Two initial harness setup
attempts stopped before tests: missing generated SvelteKit configuration,
then the unresolved UI package. The isolated config resolved both; no test
expectation or product file changed.

The final browser probe output is quoted above; exit 0. The source-lint
summary is quoted above; exit 0. git diff --check has no output and exits 0.

Full bun run check, bun run test, Rust format/clippy/test and a server
adversarial run were not executed. The job asks for a read-mostly review,
minimal crate use and no full builds. No Rust, API, route or product source
changes. No feature benchmark is required for this report-only change.

Cleanup uses the preset /mnt/hdd/targets/jobs/rev-a11y target, with
CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0,
CARGO_BUILD_JOBS=4 and worktree target/tmp. cargo clean exits 0:

     Removed 1 file, 356B total

Web-output cleanup exits 0:

No web build output at apps/web/build
No web build output at apps/web/.svelte-kit/output

UX gaps closed

No product gap is fixed in this review. Twelve component-family issues now
have source evidence, an exact fix and an acceptance test. Focus entry and
escape defects have a browser reproduction. Existing #658 has additional
evidence instead of a duplicate issue.

UX gaps left

A1–A13 remain implementation work. The existing Calendar Task completion
gap is #657; no duplicate was
filed. Production visual and assistive-technology acceptance checks remain
for the fix jobs. The open shared sr-only reuse issue
#308 is separate from A7's
clipped focusable controls.

Decisions

  • Follow the explicit accessibility job scope despite #427's current Log
    attachment title. Record all audit links on the assigned issue.
  • Keep the review source-only, with small behavior fixtures and one existing
    theme test. Do not make product edits or build standalone review pages.
  • Separate WCAG AA criteria from stricter project rules. Do not label every
    sub-44 px target an AA failure or treat Focus Appearance as AA.
  • Group issues by component family. Reuse #658 and #657. Do not file a defect
    only because a compiler warning exists or a rendered check has not run.

No product design decision or dependency change was made.

rev-a11y accessibility source audit complete. Branch: `job/rev-a11y`. Head: `25eb9623626d597ef16357ada4aa0bc030953c7d`. Audited `origin/dev`: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Built and committed `review-findings.md` in five atomic documentation commits. Twelve new component-family issues are linked below; #658 received duplicate evidence. Each new issue contains context, source anchors, expected behavior, a fix and a test idea. Follow-up comments correct several initial line references. The final diff changes only the report. No product edits, push or deployment. The requested one-time origin/dev sync was already up to date. Final checks and their output, cleanup, UX gaps and decisions follow. Full app gates and production visual/assistive-technology checks were not run, as specified for this read-mostly job. # Web accessibility review — rev-a11y This report records the source audit requested by the rev-a11y job. It does not certify WCAG conformance. No product files change in this job. ## Scope and evidence - Branch: `job/rev-a11y`. - Initial `origin/dev`: `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. - Rules: `CLAUDE.md`, `CONTEXT.md`, DESIGN §§28, 34, 35, 38 and 57; the current keyboard-motion rule (#611) replaces #527. - Standard: [WCAG 2.2](https://www.w3.org/TR/WCAG22/), levels A and AA. The project also requires 44 px touch targets and reduced motion. WCAG 2.5.8 AA uses 24 px with exceptions; 44 px is the stricter project rule. - Method: source inspection of `packages/ui` and `apps/web`, duplicate searches in Forgejo, and small checks where needed. Production builds, screenshots and full Rust gates are outside this read-mostly job. - The job assigns #427. That issue currently describes Log attachments. The start comment records the mismatch. The audit follows the job prompt. ## Findings ### A1 — Shared focus containment accepts hidden controls Priority: P1. Family: focus traps and Settings surfaces. Tracking: [#740](https://git.kayg.org/kayg/calternal/issues/740). Criteria: 2.4.3 Focus Order, 2.1.1 Keyboard. Evidence: - `apps/web/src/lib/a11y/focusTrap.ts:87`: `tabbables()` checks the control's own `hidden` flag and an inert ancestor. It does not check a hidden ancestor, CSS visibility, or hidden input types. - `apps/web/src/lib/a11y/focusTrap.ts:142`: `moveFocusIn()` accepts an explicit target without a visibility check. A failed focus has no fallback. - `apps/web/src/routes/settings/[...path]/+page.svelte:294`: initial focus can select `.settings-shell .shell-back`. - `apps/web/src/routes/settings/[...path]/+page.svelte:523`: the phone layout hides the content's `.settings-detail-nav`, which contains that Back button. The visible Back button is in the shared sheet chrome, outside `.settings-shell`. Effect: a phone Settings detail link can request focus on a hidden Back button. Hidden candidates can also prevent Tab wrapping at the visible end of a surface. Fix: use one visibility-aware focus candidate predicate. Apply it to initial focus and containment. Check hidden/inert ancestors and hidden input types. Resolve CSS visibility only during focus operations, with no per-frame observers. If the requested target cannot take focus, use the first visible control or the surface. Select the visible sheet Back button for phone Settings. Test: open a Settings detail link with a keyboard at 390 px. Check that focus is inside the visible dialog. Add hidden first/last candidates to the focus-trap tests. Check Tab and Shift+Tab wrapping and Escape restoration. ### A2 — PDF previews expose page numbers without document text Priority: P1. Family: shared PDF viewer. Tracking: [#741](https://git.kayg.org/kayg/calternal/issues/741). Criteria: 1.1.1 Non-text Content, 1.3.1 Info and Relationships. Evidence: - `packages/ui/src/components/viewer/PdfView.svelte:6`: the module states that there is no text layer. - `packages/ui/src/components/viewer/PdfView.svelte:74`: pdf.js draws each page on a canvas. - `packages/ui/src/components/viewer/PdfView.svelte:111`: each canvas has only a page-number label and no content alternative. - `packages/ui/src/components/viewer/QuickLook.svelte:235`: Quick Look uses this viewer for PDF files. Effect: a screen reader can find the PDF name and page number, but cannot read its text in the preview. This applies to Files, Photos and public-link previews that use Quick Look. Fix: add a lazy pdf.js text layer and the available document structure. Keep canvas paint decorative when an equivalent text layer exists. Keep page loading bounded. For image-only pages, show a real text-unavailable state and retain Download. Do not invent OCR output. Test: open a small text PDF in Quick Look. Check that body text and reading order reach the accessibility tree, that links work with the keyboard, and that unloaded pages do not allocate all text layers at once. ### A3 — Search scope and subfolder controls have no keyboard route Priority: P1. Family: search filter chips. Tracking: [#742](https://git.kayg.org/kayg/calternal/issues/742). Criterion: 2.1.1 Keyboard. Evidence: - `apps/web/src/lib/search/SearchField.svelte:276`: the Ask context removal button has `tabindex="-1"`. - `apps/web/src/lib/search/SearchField.svelte:282`: the Calendar scope removal button has `tabindex="-1"`. - `apps/web/src/lib/search/SearchField.svelte:295`: Include subfolders has `tabindex="-1"`. Its click handler is the only UI call to `setSubfolders()`. - `apps/web/src/lib/search/SearchField.svelte:182`: the chip key handler can select, remove or edit ordinary pills. It cannot focus or activate these nested controls or clear the separate Calendar/Ask scope chips. Effect: a keyboard User cannot use the visible Include subfolders action or remove those scope chips through their controls. Fix: give the controls real Tab stops, or extend the shared chip keyboard model to reach and activate every action. Preserve the typed query. Give the active control visible focus and announce changes once. Test: open scoped search with a keyboard. Toggle Include subfolders with Space, clear Calendar scope, and clear Ask context. Check the result scope and query without pointer input. ### A4 — Warm tooltips cannot stay open under the pointer Priority: P2. Family: shared warm tooltips. Tracking: [#744](https://git.kayg.org/kayg/calternal/issues/744). Criterion: 1.4.13 Content on Hover or Focus. Evidence: - `packages/ui/src/components/tooltip/TooltipLayer.svelte:208`: pointer exit hides the bubble unless the pointer stays inside the trigger. - `packages/ui/src/components/tooltip/TooltipLayer.svelte:391`: the bubble has `pointer-events: none`. Effect: moving from a small toolbar control onto its tooltip closes the text. A User who magnifies the interface cannot keep the text visible while moving to read it. Escape dismissal already exists. Fix: retain the tooltip while either the trigger or bubble is hovered. Allow pointer entry to the bubble. Use a bounded grace period across the gap. Preserve Escape dismissal, keyboard behavior and touch peek behavior. Test: hover a toolbar action, move through the gap into the tooltip, and check that it stays visible. Escape must close it without moving focus. Leaving both regions must close it. ### A5 — Interactive TagPill targets stay compact on touch Priority: P2. Family: shared TagPill. Tracking: [#745](https://git.kayg.org/kayg/calternal/issues/745). Rule: DESIGN §35, 44 px touch targets. Also inspect 2.5.8 with spacing. Evidence: - `packages/ui/src/components/TagPill.svelte:43`: the interactive button does not use `touch-hit` or a minimum target token. - `packages/ui/src/components/TagPill.svelte:68`: the small form uses text size and 3 px vertical padding. The medium form has a 40 px height fallback. - `apps/web/src/lib/components/TagEditor.svelte:143`: matching suggestions render interactive TagPills without a larger control wrapper. - `packages/ui/src/components/calendar/ItemPreview.svelte:220`: Tags in the Calendar preview use the same compact interactive form. Effect: the touch User must hit a compact chip. Shared sizing tokens do not increase this button to the project minimum. Fix: keep the compact paint. Add a shared 44 px coarse-pointer target and enough spacing that adjacent targets do not overlap. Reuse `touch-hit` if its expanded area fits the layout; otherwise grow the button's layout box. Test: inspect the effective targets in Tag editor suggestions and Calendar preview Tags at 390 and 820 px with touch emulation. Check short Tags and adjacent remove actions. Each action must have a separate 44 px target. ### A6 — Focus style divergence is already tracked by #658 Tracking: [#658](https://git.kayg.org/kayg/calternal/issues/658). No duplicate issue is needed. Evidence: `apps/web/src/calternal-app.css:252` adds two box-shadow bands to the global focus outline; `packages/ui/src/tokens.css:1487` also defines a global outline. Local variants include `Checkbox.svelte:181`, `Select.svelte:107`, `TimelineScrubber.svelte:213` and `actions/resizableEdge.ts:94`. The native Checkbox input is transparent and paints its focus on the sibling indicator. The guard must check this indirect focus paint as well as the active input. Fix and acceptance remain in #658. Keep keyboard focus visible when styles are consolidated. Do not remove motion for keyboard input. ### A7 — Composer has invisible actionable Tab stops Priority: P2. Family: Composer. Tracking: [#819](https://git.kayg.org/kayg/calternal/issues/819). Criterion: 2.4.7 Focus Visible. Evidence: `apps/web/src/lib/composer/Composer.svelte:1849` and `:1850` render Stack draft and Discard draft as native `.sr-only` buttons. They remain Tab stops. `apps/web/src/calternal-app.css:260` clips that class to a 1 px box, with no focus reveal in the app or component. Effect: a keyboard User reaches an invisible action and cannot see its name or focus. The screen-reader alternative must remain available. Fix: reuse a shared focus-reveal utility or the visible Composer action surface. Show the label and full ring while the action has keyboard focus. Keep draft guards and recovery. Keep shared keyboard motion. Test: Tab to both actions with a real draft. Check visible names and focus, Enter/Space activation, and discard recovery at each required width/theme. ### A8 — Photos scrubber has a 28 px touch target Priority: P2. Family: Photos timeline scrubber. Tracking: [#820](https://git.kayg.org/kayg/calternal/issues/820). Rule: DESIGN §35, 44 px touch targets. Evidence: `apps/web/src/lib/photos/TimelineScrubber.svelte:274` makes the touch track ignore pointers. Its touch thumb is 28 × 48 px at `:291` and accepts pointers only while active or dragging at `:313`. The outer 44 px strip does not increase the effective target. Fix: keep the narrow painted grip. Expand its effective target to 44 × 48 px inside the strip. Keep normal scrolling outside the target and pointer capture on the hit target. Use CSS. Test: scroll a multi-month timeline until the handle appears. Start a touch drag beside the painted grip within the expanded target. Check capture, jump, adjacent item menus, and scrolling outside the target. Check existing keyboard slider actions. This is a project-rule finding, not a claim that the basic 24 px WCAG 2.5.8 AA size fails. ### A9 — Zoomed image previews cannot pan with the keyboard Priority: P1. Family: shared image viewer. Tracking: [#830](https://git.kayg.org/kayg/calternal/issues/830). Criteria: 2.1.1 Keyboard, 2.5.7 Dragging Movements. Evidence: `packages/ui/src/components/viewer/ImageView.svelte:97` changes the image offset only in the pointer-drag path. The exported API at `:114` supplies zoom only. `viewer/QuickLook.svelte:155` maps navigation keys to item changes and at `:165` maps zoom keys to `zoomBy()`. The stage clips enlarged edges at `ImageView.svelte:161`. Effect: a keyboard User can enlarge an image but cannot inspect its edges at that zoom. Custom panning also lacks a single-click or tap alternative. Fix: add a bounded shared pan command. Expose keyboard actions and named pan controls that work by one click or tap. Keep item navigation distinct. Use the shared shortcut registry, tooltip and 44 px target primitives. Test: inspect every image edge at enlarged zoom with keyboard actions, then with individual clicks/taps without dragging. Check Reset, item navigation, focus, names, target size and reduced motion at all required widths/themes. ### A10 — Category labels break persistent error descriptions Priority: P2. Family: inline category fields. Tracking: [#831](https://git.kayg.org/kayg/calternal/issues/831). Criterion: 1.3.1 Info and Relationships. Evidence: `apps/web/src/lib/components/money/AssignedCell.svelte:92` and `:102` build the error reference and ID from the category label. The caller at `apps/web/src/routes/money/[budget]/[month]/+page.svelte:268` passes `category.name`. A space in a label splits `aria-describedby` into separate ID references. Repeated labels can also collide. Effect: the initial alert can speak, but the invalid field has no valid persistent error description for these labels. Fix: use `$props.id()` or a supplied stable item ID and a fixed suffix. Keep the visible category label as the field name. Do not sanitize labels into IDs. Test: use a label with spaces and repeated labels across groups. Each invalid field must reference one existing, unique error element. Check the accessible description when focus returns. Use no real financial data. ### A11 — Analytics heatmap readouts lack full-size touch access Priority: P2. Family: Analytics marks. Tracking: [#845](https://git.kayg.org/kayg/calternal/issues/845). Rule: DESIGN §35, 44 px touch targets. Evidence: `apps/web/src/lib/components/analytics/BklitAnalyticsHeatmapMarks.tsx:49` and `:55` use the painted bin dimensions minus the gap. These dimensions reach the buttons at `BklitKeyboardMarks.tsx:120`. The shared heatmap column tokens at `packages/ui/src/tokens.css:20` range from 13 to 18 px at the default 16 px root. `BklitAnalytics.tsx:995` caps Calendar heatmap width with the maximum token and supplies a 2 px gap. Its mark CSS at `:335` adds no larger touch target. No full-size readout alternative exists in the wrapper. Fix: preserve exact-cell fine-pointer hover and keyboard access. Provide a real-data readout list or day/hour selector with separate 44 px targets for touch. Reuse the same readout values. Do not overlap enlarged cell targets. Test: reach each value in a long Calendar range and the Time of day chart through full-size touch actions. Check parity with hover and keyboard readouts. Measure target size and spacing for WCAG 2.5.8. The date-navigation exemption does not supply an equivalent Analytics readout. ### A12 — Live Photo playback has a 28 px touch target Priority: P2. Family: Live Photo playback. Tracking: [#846](https://git.kayg.org/kayg/calternal/issues/846). Rule: DESIGN §35, 44 px touch targets. Evidence: `packages/ui/src/components/viewer/LiveMotion.svelte:74` renders a named native toggle, but the target is 28 px high at `:109`. It has no expanded hit area or coarse-pointer size rule. `QuickLook.svelte:233` places it outside the header action group that has larger control targets. Fix: preserve the compact paint and expand the coarse-pointer target with the shared target primitive, or grow its control box. Keep adjacent actions separate and preserve pressed state and explicit reduced-motion playback. Test: tap the expanded area, check playback/state, and verify 44 px effective targets. Check Space/Enter and reduced motion at all required widths/themes. ### A13 — Modal backgrounds stay live despite the inert contract Priority: P1. Family: modal shell. Tracking: [#848](https://git.kayg.org/kayg/calternal/issues/848). Rule: shared modal contract and the ARIA modal-dialog pattern. Evidence: `apps/web/src/lib/overlay/state.svelte.ts:3` says the layout makes the feed inert. `open()` at `:50` changes a count and dismisses transient UI. `apps/web/src/routes/+layout.svelte:796` and `:819` do not bind inert to the app frame or route content. Only the bottom Tab Bar and Primary Pill consume overlay state to remove interaction. The desktop sidebar at `apps/web/src/lib/components/app-sidebar.svelte:451` is inert only when collapsed. `packages/ui/src/actions/portal.ts:24` moves a branch without isolating siblings. `focusTrap.ts:229` listens only for local keydown, with no document focus-entry guard. Effect: background controls can still receive programmatic focus and then keyboard input. A scrim and local Tab loop do not make a background inert. This confirms the source contract gap. Reader-specific exposure despite `aria-modal` requires a production assistive-technology check. Fix: give modal background state one shared owner. Keep background app content and lower dialogs inert while the top dialog is open, including its exit. Preserve nested menus and the supported toast action path. Restore prior state and focus on close. Update the stale comments with the fix. Test: open Search over focusable route content and an expanded sidebar. Attempt a delayed background focus. Open a confirmation over Settings; check upper-dialog focus, then restoration to Settings and the app opener. Check Escape, nested menus, toast actions and screen-reader exploration. ## Behavior evidence A small browser harness bundles the actual `focusTrap.ts` and its local dependencies. It uses one Chromium browser at 390 px with macOS platform emulation. The fixture is behavior evidence, not a UI visual review. `node artifacts/rev-a11y/focus-probe.mjs` exits 0 and prints: ```text REPRODUCED #740: hidden explicit initial target leaves focus outside the trap. REPRODUCED #740: a hidden last candidate lets Tab leave the trap. REPRODUCED #848 supporting behavior: the shared trap permits programmatic background focus. REPRODUCED #831: a category label with a space resolves no error-description ID references. ``` The first case requests a button under `display:none` and leaves focus on the outside opener. In the second case, a hidden last candidate prevents wrapping and native Tab reaches an outside button. The full phone Settings route remains an acceptance test for the fix. The added #848 case tests the shared action with visible controls. It does not render OverlaySurface; source inspection establishes the missing inert binding in the app. The #831 fixture tests the browser's ID-reference parsing with a neutral label. It does not render the full category editor. ## Source-lint coverage A sequential scan uses the installed Svelte compiler. It removes only accessibility-suppression comments in memory, while preserving positions. It writes no product output. First scan output: ```text Source lint: Svelte 5.57.1; 243 files; 19 accessibility warnings; 0 compiler errors. Warnings require human review. ``` Warnings alone are not findings. Several wrappers delegate keyboard actions to their child controls; programmatic deep-link targets and initial focus also require context. The report records only defects with source evidence. ## Coverage and limits The source scan covers 243 Svelte files in `packages/ui/src` and `apps/web/src`, excluding the test-fixture directory. Manual review follows the shared controls and their route consumers. It includes the React chart interaction adapters that Svelte mounts. | Family or route | Source checks and result | | --- | --- | | Shared controls | Checkbox input/mark modes, Toggle, Select, Disclosure, Pill, TagPill and SegmentedControl names, roles, state and input handling. A5 and A6 need fixes. | | Shell and navigation | Skip link, route focus target, ModeHeader, Tab Bar, sidebar, shortcut card, roving toolbar and resizable separators. Modal behavior needs A1/A13; ring variants belong to #658. | | Menus and surfaces | Menu roving focus, typeahead, disabled rows, submenu/drill Back, Escape and restoration; ConfirmSheet safe initial focus and error alert; portal and focus-trap contracts. Hidden targets and background isolation need A1/A13. | | Calendar and Journal | Day/Week/Month/Year grids, Agenda, MiniMonth, DateStrip, ItemPreview, attachment actions, Daily-note links and Journal loading/error states. Date-grid size exceptions have equivalent navigation in DESIGN §35. Task completion is already #657. | | Files and sharing | FileCollection listbox selection/active descendant, rename input, FilesBrowser, Trash/Recent, ShareDialog labels/errors, public password/upload/editor controls and Quick Look. Do not count decorative selection marks as nested checkboxes. A2/A9/A12 affect shared previews. | | Photos | Timeline virtual focus and selection naming, item links/menus, Quick Look and slider keyboard/value text. A8 records its touch-target gap. A9/A12 affect the viewer. | | Notes and editor | Named multiline editor, format toolbar, block actions/handle, properties, mentions and live/offline states. Focused block handles have their own reveal rules. A6 still covers focus consistency. | | Search and Tags | Combobox/result options, virtual active descendant, photo results, saved-search naming/actions, scope/filter chips and previews. A3 and A5 need fixes. | | Composer | Field labels/help, draft/status live regions, overlay focus, attachment actions and gesture alternatives. A7 records clipped actionable Tab stops. | | Analytics | Chart mark names, roving focus, Escape/readout state, real empty/loading/error states, range picker and reduced-motion hooks. A11 requests full-size touch access. | | Inline category forms | Wrapped labels, input modes, validation alerts and inline keyboard editing. A10 records invalid error-description IDs. No financial data is collected or reported. | | Mail, notifications and agent turns | Native row links/actions, disclosure state, named message frame, unread text, reminder controls, notification status and transcript log. Shared overlays need A13. | | Authentication and Settings | Sign-in/setup/recovery labels, recovery-word combobox, errors/status, switches, disclosures, Appearance sliders and admin configuration error description. Phone Settings focus needs A1. | | Route errors and link resolution | Named unknown-route/offline/error states, Retry/Sign in actions and status announcements. No placeholder data is used as evidence. | | Tokens and motion | Shared focus/input-modality guard, 44 px coarse-pointer roles, CSS reduced-motion override, JS motion helpers and curated material contrast tests. Keyboard timing follows #611. | The 19 compiler warnings were read in context. Most are programmatic focus targets, wrapper listeners with child keyboard controls, or delegated input handlers. VideoView has no caption track and suppresses that warning; media caption/transcript requirements need a content and playback review. No blanket media conformance claim is made. Source checks cannot certify screen-reader reading order, actual focus-ring paint, reflow at zoom, touch spacing or contrast over User backgrounds. The fix jobs need real production builds at 390, 820 and 1440 px, light and dark, with macOS rendering. No screenshots were taken in this read-mostly job. VoiceOver, another screen reader, native media controls and real touch devices were not tested. ## Verification The required one-time `git fetch origin && git merge origin/dev` ran before the final checks. Output, exit 0: ```text Already up to date. ``` `origin/dev` remains `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`; no reviewed product source changed. The final diff contains only this report. The focused existing test uses Vitest 5.0.1 with one worker and an audit-only config. It imports the real shared theme module directly through the app's theme facade. This avoids the UI barrel and a full app build. Output, exit 0: ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/rev-a11y/apps/web Test Files 1 passed (1) Tests 74 passed (74) Start at 15:23:39 Duration 57.58s (tests 79%, transform 17%, import 3%, worker 1%) ``` The theme checks include AA text roles across curated materials and control contrast. They do not replace rendered checks. Two initial harness setup attempts stopped before tests: missing generated SvelteKit configuration, then the unresolved UI package. The isolated config resolved both; no test expectation or product file changed. The final browser probe output is quoted above; exit 0. The source-lint summary is quoted above; exit 0. `git diff --check` has no output and exits 0. Full `bun run check`, `bun run test`, Rust format/clippy/test and a server adversarial run were not executed. The job asks for a read-mostly review, minimal crate use and no full builds. No Rust, API, route or product source changes. No feature benchmark is required for this report-only change. Cleanup uses the preset `/mnt/hdd/targets/jobs/rev-a11y` target, with `CARGO_PROFILE_DEV_DEBUG=line-tables-only`, `CARGO_INCREMENTAL=0`, `CARGO_BUILD_JOBS=4` and worktree `target/tmp`. `cargo clean` exits 0: ```text Removed 1 file, 356B total ``` Web-output cleanup exits 0: ```text No web build output at apps/web/build No web build output at apps/web/.svelte-kit/output ``` ## UX gaps closed No product gap is fixed in this review. Twelve component-family issues now have source evidence, an exact fix and an acceptance test. Focus entry and escape defects have a browser reproduction. Existing #658 has additional evidence instead of a duplicate issue. ## UX gaps left A1–A13 remain implementation work. The existing Calendar Task completion gap is [#657](https://git.kayg.org/kayg/calternal/issues/657); no duplicate was filed. Production visual and assistive-technology acceptance checks remain for the fix jobs. The open shared sr-only reuse issue [#308](https://git.kayg.org/kayg/calternal/issues/308) is separate from A7's clipped focusable controls. ## Decisions - Follow the explicit accessibility job scope despite #427's current Log attachment title. Record all audit links on the assigned issue. - Keep the review source-only, with small behavior fixtures and one existing theme test. Do not make product edits or build standalone review pages. - Separate WCAG AA criteria from stricter project rules. Do not label every sub-44 px target an AA failure or treat Focus Appearance as AA. - Group issues by component family. Reuse #658 and #657. Do not file a defect only because a compiler warning exists or a rendered check has not run. No product design decision or dependency change was made.
Author
Owner

One real-data probe returned HTTP 503 for a valid Reminders VTODO PUT to the local DAV endpoint with If-None-Match: *, after Mail had cached 1,999 messages and 2,000 UID memberships. During that run, SQLite showed mail.sync and files.thumbnail jobs still leased; the shared account thread count reached 5,996. An earlier run completed the same DAV PUT and GET successfully. The first 503 response body was not retained, so I cannot classify it beyond a load-sensitive 503. I moved the DAV seed before the Mail backfill and added bounded response text to the next failure diagnostic; no assertion is being weakened.

One real-data probe returned HTTP 503 for a valid Reminders VTODO `PUT` to the local DAV endpoint with `If-None-Match: *`, after Mail had cached 1,999 messages and 2,000 UID memberships. During that run, SQLite showed `mail.sync` and `files.thumbnail` jobs still leased; the shared account thread count reached 5,996. An earlier run completed the same DAV PUT and GET successfully. The first 503 response body was not retained, so I cannot classify it beyond a load-sensitive 503. I moved the DAV seed before the Mail backfill and added bounded response text to the next failure diagnostic; no assertion is being weakened.
Author
Owner

The Cross-User classification gate found the new Connected Accounts operations missing explicit policy and path-ID semantics. I added owner-scoped classifications for list/create/update/delete and legacy migration status under DESIGN §49 / #407, plus integration_account_id for the update/delete {id} paths. XUSER_CLASSIFY_ONLY=1 python3 tests/adversarial/xuser_matrix.py now reports Cross-User classification gate: 342 operations classified and Generated entry point classification: 960 tools classified. The full matrix has not reached server setup yet; the media-runtime host thread guard is the next gate.

The Cross-User classification gate found the new Connected Accounts operations missing explicit policy and path-ID semantics. I added owner-scoped classifications for list/create/update/delete and legacy migration status under DESIGN §49 / #407, plus `integration_account_id` for the update/delete `{id}` paths. `XUSER_CLASSIFY_ONLY=1 python3 tests/adversarial/xuser_matrix.py` now reports `Cross-User classification gate: 342 operations classified` and `Generated entry point classification: 960 tools classified`. The full matrix has not reached server setup yet; the media-runtime host thread guard is the next gate.
Author
Owner

The requested Cross-User/Admin runner now passes both classification inventories and the Admin coverage check, but the real server matrix does not start: prepare-media-runtime.sh exits with too many local threads for the bounded media sandbox test: 6759. The full robustness run stopped at the same guard after classification and Admin coverage, this time reporting 6,370 threads. Both exits happen before browser setup or API attacks. I did not bypass the sandbox's explicit 4,090-thread safety threshold or retry the suite; no Two-User isolation or robustness result is claimed.

The requested Cross-User/Admin runner now passes both classification inventories and the Admin coverage check, but the real server matrix does not start: `prepare-media-runtime.sh` exits with `too many local threads for the bounded media sandbox test: 6759`. The full robustness run stopped at the same guard after classification and Admin coverage, this time reporting 6,370 threads. Both exits happen before browser setup or API attacks. I did not bypass the sandbox's explicit 4,090-thread safety threshold or retry the suite; no Two-User isolation or robustness result is claimed.
Author
Owner

Mail TLS Dovecot e2e update (commit efe8323fe): the old flow opened /settings/mail/connect and waited for an Other radio. That route now directs Users to Connected Accounts, so the fixture could not reach its form. The e2e now seeds the controlled localhost TLS account through the authenticated Mail API, then exercises the real Mail UI.

The first run stopped at the removed radio. The updated run passed account creation and live sidebar progress, but 2,000-message backfill exceeded its 180-second completion budget. The server logged SQLite pool waits up to 19.5 seconds and a Mail row insert taking 3.8 seconds while other worktrees were building/checking. This run did not establish a Mail product failure; the completion gate remains unverified under this host load.

Mail TLS Dovecot e2e update (commit `efe8323fe`): the old flow opened `/settings/mail/connect` and waited for an `Other` radio. That route now directs Users to Connected Accounts, so the fixture could not reach its form. The e2e now seeds the controlled localhost TLS account through the authenticated Mail API, then exercises the real Mail UI. The first run stopped at the removed radio. The updated run passed account creation and live sidebar progress, but 2,000-message backfill exceeded its 180-second completion budget. The server logged SQLite pool waits up to 19.5 seconds and a Mail row insert taking 3.8 seconds while other worktrees were building/checking. This run did not establish a Mail product failure; the completion gate remains unverified under this host load.
Author
Owner

#427 merge-round 7a — Round 2 final report

Branch: job/merge-round-7a
Head: 0a16bf704284f5d3b9a27a9046512155d64c2935
Base check: fetched and merged origin/dev once before final gates; it was up to date. No push, deploy, or branch merge was performed.

Built and files

The committed round-2 gate fixes are:

  • apps/web/e2e/files.mjs: Recent now follows gamma.md after it is renamed to delta.md; type-to-select sends the filename prefix key.
  • apps/web/e2e/harness.mjs, apps/web/e2e/integrations-review.mjs, apps/web/e2e/mail-sync-613.mjs: emulate macOS platform metadata for screenshot review.
  • apps/web/e2e/mail-sync-613.mjs: seed the TLS Dovecot account through the current authenticated Mail API, then exercise the real Mail UI.
  • tests/adversarial/authz_matrix.py, tests/adversarial/xuser_matrix.py: classify Connected Accounts operations and their {id} parameter for the User/Admin matrices.

The full branch contains 259 changed files across the previously reported merge groups. Earlier group details and per-crate gates are in the earlier #427 comments. Rust source did not change after those Rust gates.

Upgrade proof

Built origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5 and this head. The old binary seeded real-shaped data through its API; the new binary migrated the same data directory; then the old binary was started again.

"new_ready_ms":11605
"mail_uid_fetch_commands_after_upgrade":0
"migration_after":{"missing":[]
"upgrade_checks":"pass"
"downgrade":{"starts":true,"health":200,"notes_api":200,"mail_api":200,"mail_sync":{"backfill_complete":true,"folders":[{"name":"INBOX","uid_validity":1782014281,"backfill_top_uid":12658,"low_water_uid":0,"backfill_complete":true,"message_count":1999}]},"works":true}

All requested migrations applied: core 0007–0012, Calendar 0005, Notes 0025/0026, Files 0016–0019, Search 0004. The 1,999 Mail message rows, 2,000 memberships (including two duplicate RFC Message-ID memberships), and message checksum were unchanged; known Mail UIDs were not refetched. Notes/Daily content remained stable apart from documented IDs/version metadata. Reminders over DAV remained present. The old binary started and served health, Notes and Mail successfully after migration.

Gate output

cargo fmt --check
(exit 0; no output)

Per-crate cargo clippy -p <crate> --all-targets -- -D warnings and cargo test -p <crate> passed for the 19 changed Rust crates listed in the earlier #427 final report. The Notes suite had one load-sensitive failure (daily_and_composer_preserve_unrelated_bytes, 404 where its existing assertion expects 200); its isolated rerun was 1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out. No test expectation changed.

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-round-7a/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings
Test Files  22 failed | 131 passed (153)
     Tests  29 failed | 1041 passed (1070)
    Errors  1 error
Start at  17:16:16
Duration  1175.81s (transform 58%, environment 15%, import 13%, tests 10%, setup 3%)

  Transform  |component| transforming modules took 2377.64s · 50% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

[vitest-pool]: Timeout terminating forks worker for test files /home/kayg/Developer/calternal-wt/merge-round-7a/apps/web/src/lib/a11y/inputModality.test.ts.
error: script "test" exited with code 1
Cross-User classification gate: 342 operations classified
Generated entry point classification: 960 tools classified
Admin coverage: 39 reviewed operations; contract and Rust guards agree
too many local threads for the bounded media sandbox test: 6759

The requested robustness suite reached the same classification output, then stopped before attack setup at:

too many local threads for the bounded media sandbox test: 6370
PASS Connected Accounts review: 66 screenshots in /home/kayg/Developer/calternal-wt/merge-round-7a/artifacts/connected-accounts
PASS MCP Events malformed drafts, SSRF guards, and User/Admin surface controls

Mail TLS Dovecot e2e connected through the real API and showed live folder/message progress, but its 2,000-message backfill did not finish in 180 seconds:

PASS connected a real Mail account through the TLS Dovecot provider
PASS live folder and message progress appeared in the Mail sidebar
error: Mail backfill did not complete within three minutes

The Files e2e stopped before rename/Recent at fixture upload because the local media/server environment returned 503:

AssertionError: Expected values to be strictly equal:

503 !== 201
Removed 7239 files, 4.6GiB total

Known gaps and UX review

UX gaps closed: corrected the stale Files Recent expectation after rename; extended User/Admin classification to Connected Accounts; changed Mail e2e setup to the current account API; generated the Connected Accounts review screenshots with macOS platform emulation at 390/820/1440 px in light and dark themes.

UX gaps left: the corrected Files typeahead/Recent flow was not verified end-to-end after the fixture upload returned 503. Mail account creation and live progress passed, but full backfill completion remains unverified within the e2e budget. The web unit suite has 29 timeout failures and one worker-start error across unrelated tests; no expectations were changed and no retry was run. XUser and robustness attack phases did not start because the bounded media runtime rejected the host thread count. Thumbnail readiness stayed false (decoder-failed-v1) under the media runtime guard.

Upgrade finding for owner review: the Notes directory identity changed from ace3ae33-0db4-41a3-9f6e-89bfd9d6908b to 7304b637-0602-4b6f-a062-7f4d6b39c0e9. The existing #627 behavior creates a new ID when the parent fingerprint is unavailable; confirm whether this is acceptable for stable folder links.

The Connected Accounts screenshot set has 66 images and is saved at artifacts/connected-accounts/; the archive is artifacts/connected-accounts-review.zip. The installed scripts/fj CLI has no issue attachment/upload command, so I could not attach it to this comment. The macOS VM is offline; screenshots used Playwright macOS platform emulation and no real-Mac run was attempted.

Decisions not settled by DESIGN

  • Seed the Mail TLS fixture via the authenticated account API because the old /settings/mail/connect e2e flow no longer exposes its Other setup form; continue the actual sync check in the Mail UI.
  • Use a one-character filename prefix for the Files typeahead assertion because selection matches the filename prefix.
  • Preserve the #627 Notes directory-ID behavior when the parent fingerprint is unavailable; owner review is needed for link impact.

No pushes, deploys, or merges were performed.

#427 merge-round 7a — Round 2 final report **Branch:** `job/merge-round-7a` **Head:** `0a16bf704284f5d3b9a27a9046512155d64c2935` **Base check:** fetched and merged `origin/dev` once before final gates; it was up to date. No push, deploy, or branch merge was performed. ## Built and files The committed round-2 gate fixes are: - `apps/web/e2e/files.mjs`: Recent now follows `gamma.md` after it is renamed to `delta.md`; type-to-select sends the filename prefix key. - `apps/web/e2e/harness.mjs`, `apps/web/e2e/integrations-review.mjs`, `apps/web/e2e/mail-sync-613.mjs`: emulate macOS platform metadata for screenshot review. - `apps/web/e2e/mail-sync-613.mjs`: seed the TLS Dovecot account through the current authenticated Mail API, then exercise the real Mail UI. - `tests/adversarial/authz_matrix.py`, `tests/adversarial/xuser_matrix.py`: classify Connected Accounts operations and their `{id}` parameter for the User/Admin matrices. The full branch contains 259 changed files across the previously reported merge groups. Earlier group details and per-crate gates are in the earlier #427 comments. Rust source did not change after those Rust gates. ## Upgrade proof Built `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` and this head. The old binary seeded real-shaped data through its API; the new binary migrated the same data directory; then the old binary was started again. ```text "new_ready_ms":11605 "mail_uid_fetch_commands_after_upgrade":0 "migration_after":{"missing":[] "upgrade_checks":"pass" "downgrade":{"starts":true,"health":200,"notes_api":200,"mail_api":200,"mail_sync":{"backfill_complete":true,"folders":[{"name":"INBOX","uid_validity":1782014281,"backfill_top_uid":12658,"low_water_uid":0,"backfill_complete":true,"message_count":1999}]},"works":true} ``` All requested migrations applied: core 0007–0012, Calendar 0005, Notes 0025/0026, Files 0016–0019, Search 0004. The 1,999 Mail message rows, 2,000 memberships (including two duplicate RFC Message-ID memberships), and message checksum were unchanged; known Mail UIDs were not refetched. Notes/Daily content remained stable apart from documented IDs/version metadata. Reminders over DAV remained present. The old binary started and served health, Notes and Mail successfully after migration. ## Gate output ```text cargo fmt --check (exit 0; no output) ``` Per-crate `cargo clippy -p <crate> --all-targets -- -D warnings` and `cargo test -p <crate>` passed for the 19 changed Rust crates listed in the earlier #427 final report. The Notes suite had one load-sensitive failure (`daily_and_composer_preserve_unrelated_bytes`, 404 where its existing assertion expects 200); its isolated rerun was `1 passed; 0 failed; 0 ignored; 0 measured; 182 filtered out`. No test expectation changed. ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/merge-round-7a/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` ```text Test Files 22 failed | 131 passed (153) Tests 29 failed | 1041 passed (1070) Errors 1 error Start at 17:16:16 Duration 1175.81s (transform 58%, environment 15%, import 13%, tests 10%, setup 3%) Transform |component| transforming modules took 2377.64s · 50% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns [vitest-pool]: Timeout terminating forks worker for test files /home/kayg/Developer/calternal-wt/merge-round-7a/apps/web/src/lib/a11y/inputModality.test.ts. error: script "test" exited with code 1 ``` ```text Cross-User classification gate: 342 operations classified Generated entry point classification: 960 tools classified Admin coverage: 39 reviewed operations; contract and Rust guards agree too many local threads for the bounded media sandbox test: 6759 ``` The requested robustness suite reached the same classification output, then stopped before attack setup at: ```text too many local threads for the bounded media sandbox test: 6370 ``` ```text PASS Connected Accounts review: 66 screenshots in /home/kayg/Developer/calternal-wt/merge-round-7a/artifacts/connected-accounts PASS MCP Events malformed drafts, SSRF guards, and User/Admin surface controls ``` Mail TLS Dovecot e2e connected through the real API and showed live folder/message progress, but its 2,000-message backfill did not finish in 180 seconds: ```text PASS connected a real Mail account through the TLS Dovecot provider PASS live folder and message progress appeared in the Mail sidebar error: Mail backfill did not complete within three minutes ``` The Files e2e stopped before rename/Recent at fixture upload because the local media/server environment returned 503: ```text AssertionError: Expected values to be strictly equal: 503 !== 201 ``` ```text Removed 7239 files, 4.6GiB total ``` ## Known gaps and UX review **UX gaps closed:** corrected the stale Files Recent expectation after rename; extended User/Admin classification to Connected Accounts; changed Mail e2e setup to the current account API; generated the Connected Accounts review screenshots with macOS platform emulation at 390/820/1440 px in light and dark themes. **UX gaps left:** the corrected Files typeahead/Recent flow was not verified end-to-end after the fixture upload returned 503. Mail account creation and live progress passed, but full backfill completion remains unverified within the e2e budget. The web unit suite has 29 timeout failures and one worker-start error across unrelated tests; no expectations were changed and no retry was run. XUser and robustness attack phases did not start because the bounded media runtime rejected the host thread count. Thumbnail readiness stayed false (`decoder-failed-v1`) under the media runtime guard. **Upgrade finding for owner review:** the `Notes` directory identity changed from `ace3ae33-0db4-41a3-9f6e-89bfd9d6908b` to `7304b637-0602-4b6f-a062-7f4d6b39c0e9`. The existing #627 behavior creates a new ID when the parent fingerprint is unavailable; confirm whether this is acceptable for stable folder links. The Connected Accounts screenshot set has 66 images and is saved at `artifacts/connected-accounts/`; the archive is `artifacts/connected-accounts-review.zip`. The installed `scripts/fj` CLI has no issue attachment/upload command, so I could not attach it to this comment. The macOS VM is offline; screenshots used Playwright macOS platform emulation and no real-Mac run was attempted. ## Decisions not settled by DESIGN - Seed the Mail TLS fixture via the authenticated account API because the old `/settings/mail/connect` e2e flow no longer exposes its `Other` setup form; continue the actual sync check in the Mail UI. - Use a one-character filename prefix for the Files typeahead assertion because selection matches the filename prefix. - Preserve the #627 Notes directory-ID behavior when the parent fingerprint is unavailable; owner review is needed for link impact. No pushes, deploys, or merges were performed.
Author
Owner

Starting merge-round-7a from branch job/merge-round-7a at 0a16bf7042. I am merging origin/dev first, then I will check host load and continue the requested deploy-readiness probes.

Starting merge-round-7a from branch job/merge-round-7a at 0a16bf704284f5d3b9a27a9046512155d64c2935. I am merging origin/dev first, then I will check host load and continue the requested deploy-readiness probes.
Author
Owner

Round 3 report — merge-round-7a

Deploy readiness: No. It is not demonstrated by this round. At 21:45:29 the host load average was 30.94/27.23/27.94 on 12 cores, so the quiet-host condition was false. The verification policy in this job also reserves the full web suite and XUser/robustness matrices for the merge round; I did not run them here.

Changes

No product source changed. I fetched and merged origin/dev (c4faf184d) once. The resulting head is 01d28acb9f5d1226a431dca82db154770c144698; it contains media guard fix 440e19dce (verified as an ancestor). The merge changed deploy/media-sandbox, docs/DESIGN.md, and tests/adversarial/prepare-media-runtime.sh. No push or deploy was performed.

Gate output

cargo fmt --check exited 0; stdout and stderr were empty. No crate clippy/tests or web checks ran: this round authored no Rust or web source. cargo clean completed with Removed 1 file, 356B total. apps/web/build and /mnt/hdd/targets/shared-server/CURRENT were absent.

Findings and remaining gaps

The preceding issue report records: Files fixture upload returned 503 !== 201; Mail account setup and live progress passed, but the 2,000-message backfill exceeded 180 seconds; XUser and robustness stopped before attack setup at the old media thread guard (6,759 and 6,370); thumbnail readiness was decoder-failed-v1. The previous bun run test reported 22 failed files, 131 passed, 29 failed tests, 1,041 passed and one worker error over 1,175.81 seconds. There is no same-condition origin/dev comparison in the evidence.

The old Files run's server log line was not retained in the issue history or this worktree, so I cannot classify its 503 as load or a server refusal. The current Files e2e assertion prints the status but not the captured startServer() diagnostics. Re-run it with diagnostics preserved; if the response is a real refusal, fix it with a regression test. No new backfill rate was measured, and thumbnail readiness was not rechecked after the guard fix.

UX gaps closed: none in this verification-only round.

UX gaps left: Files upload/Recent flow, Mail backfill completion, and thumbnail readiness remain unverified. The previous web unit failures also need the merge-round run.

Decisions not covered by DESIGN: none; no product behavior was changed.

For the merge round

Run the web unit suite once with cd apps/web && bun run test --maxWorkers=2. Run tests/adversarial/run.sh once on the quiet host; this is the full adversarial round for XUser, robustness and media/thumbnail checks. For Files and Mail e2e, build the production SPA and server first because both shared build paths were absent here:

export CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 TMPDIR="$PWD/target/tmp"
mkdir -p "$TMPDIR"
cargo build -p calternal-server --features mail-test-provider
bun run --cwd apps/web build
cd apps/web
bun run test:e2e:files
bun run test:e2e:mail-sync-613

On a Files upload 503, preserve and inspect the server diagnostics before the harness closes it. The Files e2e exercises its real thumbnail pipeline; the adversarial run also exercises the media input and thumbnail checks. Do not change the known pre-existing Notes directory ID behavior from #627.

## Round 3 report — merge-round-7a **Deploy readiness: No.** It is not demonstrated by this round. At 21:45:29 the host load average was 30.94/27.23/27.94 on 12 cores, so the quiet-host condition was false. The verification policy in this job also reserves the full web suite and XUser/robustness matrices for the merge round; I did not run them here. ### Changes No product source changed. I fetched and merged `origin/dev` (`c4faf184d`) once. The resulting head is `01d28acb9f5d1226a431dca82db154770c144698`; it contains media guard fix `440e19dce` (verified as an ancestor). The merge changed `deploy/media-sandbox`, `docs/DESIGN.md`, and `tests/adversarial/prepare-media-runtime.sh`. No push or deploy was performed. ### Gate output `cargo fmt --check` exited 0; stdout and stderr were empty. No crate clippy/tests or web checks ran: this round authored no Rust or web source. `cargo clean` completed with `Removed 1 file, 356B total`. `apps/web/build` and `/mnt/hdd/targets/shared-server/CURRENT` were absent. ### Findings and remaining gaps The preceding issue report records: Files fixture upload returned `503 !== 201`; Mail account setup and live progress passed, but the 2,000-message backfill exceeded 180 seconds; XUser and robustness stopped before attack setup at the old media thread guard (6,759 and 6,370); thumbnail readiness was `decoder-failed-v1`. The previous `bun run test` reported 22 failed files, 131 passed, 29 failed tests, 1,041 passed and one worker error over 1,175.81 seconds. There is no same-condition `origin/dev` comparison in the evidence. The old Files run's server log line was not retained in the issue history or this worktree, so I cannot classify its 503 as load or a server refusal. The current Files e2e assertion prints the status but not the captured `startServer()` diagnostics. Re-run it with diagnostics preserved; if the response is a real refusal, fix it with a regression test. No new backfill rate was measured, and thumbnail readiness was not rechecked after the guard fix. **UX gaps closed:** none in this verification-only round. **UX gaps left:** Files upload/Recent flow, Mail backfill completion, and thumbnail readiness remain unverified. The previous web unit failures also need the merge-round run. **Decisions not covered by DESIGN:** none; no product behavior was changed. ### For the merge round Run the web unit suite once with `cd apps/web && bun run test --maxWorkers=2`. Run `tests/adversarial/run.sh` once on the quiet host; this is the full adversarial round for XUser, robustness and media/thumbnail checks. For Files and Mail e2e, build the production SPA and server first because both shared build paths were absent here: ```sh export CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 TMPDIR="$PWD/target/tmp" mkdir -p "$TMPDIR" cargo build -p calternal-server --features mail-test-provider bun run --cwd apps/web build cd apps/web bun run test:e2e:files bun run test:e2e:mail-sync-613 ``` On a Files upload 503, preserve and inspect the server diagnostics before the harness closes it. The Files e2e exercises its real thumbnail pipeline; the adversarial run also exercises the media input and thumbnail checks. Do not change the known pre-existing Notes directory ID behavior from #627.
Author
Owner

Round 4 verification started on job/merge-round-7a, base 01d28acb9. This job is the merge round. I will build the production SPA and server, run the full adversarial runner, full web unit suite with two workers, Files upload/rename/Recent, Mail 2,000-message backfill, and thumbnail readiness. No push or deploy. Logs stay in artifacts/round4; final readiness will cite actual completed output.

Round 4 verification started on job/merge-round-7a, base 01d28acb9. This job is the merge round. I will build the production SPA and server, run the full adversarial runner, full web unit suite with two workers, Files upload/rename/Recent, Mail 2,000-message backfill, and thumbnail readiness. No push or deploy. Logs stay in artifacts/round4; final readiness will cite actual completed output.
Author
Owner

Round 4 finding: the full web unit run reported a failure in OverlaySurface.svelte.test.ts (Inspector phone sheet header). The run is still active; I will rerun each failed file alone after it exits. No expectation has changed. Production SPA build and cargo fmt --check passed. bun run check output: svelte-check found 0 errors and 0 warnings.

The Files e2e now prints redacted server diagnostics on failure. The Mail e2e now measures the 2,000-message budget from account creation and prints seconds and messages/s, including progress on timeout. Node syntax checks passed for both scripts. Browser runs wait for the server build and run one at a time.

Round 4 finding: the full web unit run reported a failure in OverlaySurface.svelte.test.ts (Inspector phone sheet header). The run is still active; I will rerun each failed file alone after it exits. No expectation has changed. Production SPA build and cargo fmt --check passed. bun run check output: `svelte-check found 0 errors and 0 warnings`. The Files e2e now prints redacted server diagnostics on failure. The Mail e2e now measures the 2,000-message budget from account creation and prints seconds and messages/s, including progress on timeout. Node syntax checks passed for both scripts. Browser runs wait for the server build and run one at a time.
Author
Owner

Round 4 full web unit suite completed. Verbatim summary:

 Test Files  1 failed | 153 passed (154)
      Tests  1 failed | 1072 passed (1073)
   Start at  21:59:30
   Duration  563.57s (transform 42%, import 28%, environment 16%, tests 9%, setup 4%)

The only failure was OverlaySurface.svelte.test.ts > moves an Inspector title and close action into the phone sheet header: Error: Test timed out in 5000ms.

As instructed, I reran that file alone with bunx vitest run src/lib/components/OverlaySurface.svelte.test.ts --maxWorkers=2. Verbatim summary:

 Test Files  1 passed (1)
      Tests  11 passed (11)
   Start at  22:10:06
   Duration  76.71s (transform 89%, tests 4%, import 3%, environment 2%, setup 1%)

This is load under the job's classification rule. No expectation or timeout changed. Other gates completed: production SPA build, cargo fmt --check (exit 0, no output), and bun run check (svelte-check found 0 errors and 0 warnings). The server build is active. Adversarial and browser runs are queued; no readiness claim yet.

Round 4 full web unit suite completed. Verbatim summary: ``` Test Files 1 failed | 153 passed (154) Tests 1 failed | 1072 passed (1073) Start at 21:59:30 Duration 563.57s (transform 42%, import 28%, environment 16%, tests 9%, setup 4%) ``` The only failure was `OverlaySurface.svelte.test.ts > moves an Inspector title and close action into the phone sheet header`: `Error: Test timed out in 5000ms.` As instructed, I reran that file alone with `bunx vitest run src/lib/components/OverlaySurface.svelte.test.ts --maxWorkers=2`. Verbatim summary: ``` Test Files 1 passed (1) Tests 11 passed (11) Start at 22:10:06 Duration 76.71s (transform 89%, tests 4%, import 3%, environment 2%, setup 1%) ``` This is load under the job's classification rule. No expectation or timeout changed. Other gates completed: production SPA build, `cargo fmt --check` (exit 0, no output), and `bun run check` (`svelte-check found 0 errors and 0 warnings`). The server build is active. Adversarial and browser runs are queued; no readiness claim yet.
Author
Owner

Round 4 server build hit an infrastructure failure in the shared compiler cache, not a Rust diagnostic. Evidence:

error: could not compile `futures-intrusive` (lib)

The compiler wrapper exited with status 254. Another concurrent compiler reported:

error: error writing dependencies to `/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache1WNIut/deps.d`: No such file or directory (os error 2)
error: could not compile `sqlx-sqlite` (lib) due to 1 previous error

This job exports its own TMPDIR. The shared sccache process used a temporary path from another job. I will resume the same build with RUSTC_WRAPPER= only for this job, retaining the assigned CARGO_TARGET_DIR, four build jobs, line-tables-only and no incremental compilation. I will not restart or alter the shared cache service. Production SPA and web checks are already complete; no gate is waived.

Round 4 server build hit an infrastructure failure in the shared compiler cache, not a Rust diagnostic. Evidence: ``` error: could not compile `futures-intrusive` (lib) ``` The compiler wrapper exited with status 254. Another concurrent compiler reported: ``` error: error writing dependencies to `/home/kayg/Developer/calternal-wt/authfix/target/tmp/sccache1WNIut/deps.d`: No such file or directory (os error 2) error: could not compile `sqlx-sqlite` (lib) due to 1 previous error ``` This job exports its own TMPDIR. The shared sccache process used a temporary path from another job. I will resume the same build with `RUSTC_WRAPPER=` only for this job, retaining the assigned CARGO_TARGET_DIR, four build jobs, line-tables-only and no incremental compilation. I will not restart or alter the shared cache service. Production SPA and web checks are already complete; no gate is waived.
Author
Owner

Round 4 telemetry review found a measurement edge case: Mail runs sidebar, first-page and navigation checks before its completion helper. Those waits can consume the 180-second budget. A loop that checks its deadline before its first status read would then report zero messages without measuring progress. The helper now reads real status at least once before reporting a timeout. The 180-second completion budget stays in place; no status-code assertion or fixture changed. Node syntax and diff checks passed. The real Mail run is still queued behind the server build and adversarial round.

The server has reached linking. The linker is active and waiting on disk I/O. No build diagnostic has appeared in the direct-compiler retry. No readiness claim yet.

Round 4 telemetry review found a measurement edge case: Mail runs sidebar, first-page and navigation checks before its completion helper. Those waits can consume the 180-second budget. A loop that checks its deadline before its first status read would then report zero messages without measuring progress. The helper now reads real status at least once before reporting a timeout. The 180-second completion budget stays in place; no status-code assertion or fixture changed. Node syntax and diff checks passed. The real Mail run is still queued behind the server build and adversarial round. The server has reached linking. The linker is active and waiting on disk I/O. No build diagnostic has appeared in the direct-compiler retry. No readiness claim yet.
Author
Owner

Round 4 server build completed after bypassing the shared sccache temporary-directory failure. Verbatim final build output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 80m 45s

The build contains the production SPA and the explicit Mail test-provider feature, plus the CLI and sync binaries needed by the adversarial runner. The full tests/adversarial/run.sh is now active. Its opening static gates reported:

Cross-User classification gate: 342 operations classified
Generated entry point classification: 960 tools classified
Admin coverage: 39 reviewed operations; contract and Rust guards agree

These are classification gates only; the live matrices are still to run. Current head: ba5134fa2. Files and Mail browser flows remain queued after this one full adversarial round. No push or deploy.

Round 4 server build completed after bypassing the shared sccache temporary-directory failure. Verbatim final build output: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 80m 45s ``` The build contains the production SPA and the explicit Mail test-provider feature, plus the CLI and sync binaries needed by the adversarial runner. The full `tests/adversarial/run.sh` is now active. Its opening static gates reported: ``` Cross-User classification gate: 342 operations classified Generated entry point classification: 960 tools classified Admin coverage: 39 reviewed operations; contract and Rust guards agree ``` These are classification gates only; the live matrices are still to run. Current head: ba5134fa2. Files and Mail browser flows remain queued after this one full adversarial round. No push or deploy.
Author
Owner

Round 4 update: production SPA and server builds are complete. Full Vitest ran: 153 files passed; one OverlaySurface test timed out and its unchanged file passed alone (11/11), so that failure meets the requested load classification.

Adversarial runner exited 1. Search reported missing committed hits, watcher overflow recovery failure, repair HTTP 503 and unfinished startup repair (#956). XUser and authz aborted during fixture Upload/App Password setup (#957). Editor reported editor findings=10 seed=25608414 historySeeds=25608414; restart barrier timeout aborted remaining robustness phases and cleanup hung until the job's server was terminated (#959). No complete authorization or robustness pass can be claimed.

Fixed and regression-tested a separate startup cause: nested probes inherited parent Notes listener settings without that parent's TLS files. Fresh e2e servers now remove inherited Notes listener settings; explicit data-directory fixtures keep them. Focused real-server regression: # tests 1, # pass 1, # fail 0. Commit 331231cb1c. Existing standalone theme mocks also fail with window is not defined; expectations and fixtures were left unchanged and #958 records that gap.

Files and Mail checks are now running sequentially. Current deploy readiness is no.

Round 4 update: production SPA and server builds are complete. Full Vitest ran: 153 files passed; one OverlaySurface test timed out and its unchanged file passed alone (11/11), so that failure meets the requested load classification. Adversarial runner exited 1. Search reported missing committed hits, watcher overflow recovery failure, repair HTTP 503 and unfinished startup repair (#956). XUser and authz aborted during fixture Upload/App Password setup (#957). Editor reported `editor findings=10 seed=25608414 historySeeds=25608414`; restart barrier timeout aborted remaining robustness phases and cleanup hung until the job's server was terminated (#959). No complete authorization or robustness pass can be claimed. Fixed and regression-tested a separate startup cause: nested probes inherited parent Notes listener settings without that parent's TLS files. Fresh e2e servers now remove inherited Notes listener settings; explicit data-directory fixtures keep them. Focused real-server regression: `# tests 1`, `# pass 1`, `# fail 0`. Commit 331231cb1ccd0dff351e6183f2e4c04179d6231c. Existing standalone theme mocks also fail with `window is not defined`; expectations and fixtures were left unchanged and #958 records that gap. Files and Mail checks are now running sequentially. Current deploy readiness is no.
Author
Owner

Mail e2e passed on the production SPA and real server with the isolated TLS Dovecot provider:

MAIL BACKFILL: 2000 messages in 153.344 s (13.04 messages/s); budget 180 s
PASS 2,000 TLS fixture messages are listed and completion is real
PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark
CSP REPORTS mail-sync-613: 0 across 1 pages

Exit 0. The budget begins before account creation, not after UI navigation. macOS emulation was installed before registration. All 12 Settings/Mail screenshots are attached: Mail macOS evidence. This is test-only provider data, not shipped UI data.

Files upload returned 503 before rename/Recent or worker thumbnail readiness. The redacted server log shows database checkout waits up to 16.20410488 seconds but no upload failure line. A focused rerun now retains the response error envelope through the existing byte uploader, with the unchanged 201 assertion and identical fixture bytes. Commit 1a0539880.

Mail e2e passed on the production SPA and real server with the isolated TLS Dovecot provider: ``` MAIL BACKFILL: 2000 messages in 153.344 s (13.04 messages/s); budget 180 s PASS 2,000 TLS fixture messages are listed and completion is real PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark CSP REPORTS mail-sync-613: 0 across 1 pages ``` Exit 0. The budget begins before account creation, not after UI navigation. macOS emulation was installed before registration. All 12 Settings/Mail screenshots are attached: [Mail macOS evidence](https://git.kayg.org/attachments/d66959bf-b834-46e8-8e71-4a9ba7caf179). This is test-only provider data, not shipped UI data. Files upload returned 503 before rename/Recent or worker thumbnail readiness. The redacted server log shows database checkout waits up to 16.20410488 seconds but no upload failure line. A focused rerun now retains the response error envelope through the existing byte uploader, with the unchanged 201 assertion and identical fixture bytes. Commit 1a0539880.
Author
Owner

Deploy readiness: no. No push or deploy was performed.

Built the production SPA plus calternal-server, calternal-cli and calternal-sync. The server includes mail-test-provider for the isolated local TLS fixture. Fetched and merged origin/dev once before final verification: Already up to date. The first server build failed because the shared sccache process used a deleted worktree's temporary directory. The same assigned target built successfully with RUSTC_WRAPPER empty for this job; no shared process was reset.

Built/fixed: fresh e2e servers no longer inherit a parent Notes listener configuration without its private TLS files. The real-server regression failed before the fix and passed afterward. Files failure reports retain redacted server diagnostics and use the existing byte uploader to preserve failure envelopes. Mail measures the full 180-second budget from account creation and reports actual progress if UI checks consume the budget. Thumbnail evidence reports successful renderer-specific WebP responses. A focused upload/rename/Recent mode shares the full probe's rename action; it does not bypass any full-flow assertion or claim the full suite passed.

Files: apps/web/e2e/harness.mjs, harness.test.mjs, files.mjs and mail-sync-613.mjs. No production Rust or UI source changed. Existing test expectations and fixture bytes were preserved. Seven atomic commits made in this round. Head: da5c2890108494294e3e25ce22a62bdb09419bfc on job/merge-round-7a.

Gate output, verbatim:

  Wrote site to "build"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 80m 45s
svelte-check found 0 errors and 0 warnings
 Test Files  1 failed | 153 passed (154)
      Tests  1 failed | 1072 passed (1073)
 Test Files  1 passed (1)
      Tests  11 passed (11)
# tests 1
# pass 1
# fail 0

The full Vitest suite exited 1 for one OverlaySurface 5-second timeout. That unchanged file passed alone, so it meets the requested load classification. cargo fmt --check exited 0 with no output. node --check and git diff --check passed. No crate changed, so per-changed-crate clippy/test gates do not apply.

Adversarial command tests/adversarial/run.sh exited 1. Verbatim summaries:

Cross-User classification gate: 342 operations classified
Generated entry point classification: 960 tools classified
Admin coverage: 39 reviewed operations; contract and Rust guards agree
PASS: bounded sealed image/video probes, thumbnails and HLS transcode
PASS: document namespace, private input, inherited limits and network policy
PASS: PDF link/script metadata and bounded SVG references/entities stay isolated
PASS: explicit PDF and SVG loaders produce WebP; PDF action rejects JPEG
RuntimeError: fixture Upload create returned HTTP -1
RuntimeError: Admin denial App Password fixture api_read returned -1
editor findings=10 seed=25608414 historySeeds=25608414

Search recovery failures and repair 503 are #956. Live XUser/authz matrices aborted before coverage (#957). Seven existing standalone theme mock tests fail with window undefined (#958); their fixtures/expectations were not changed. Editor restart barrier timeout aborted remaining robustness probes, then cleanup waited indefinitely; this job's stuck server was killed (#959). The full matrices and remaining robustness phases did not complete and cannot be called passing. The original ordinary Files upload returned 503, with captured database checkout waits up to 16.20410488 seconds but no upload failure line (#960). Its precise cause remains unresolved; a successful repeat is not a root-cause fix.

Mail, verbatim:

MAIL BACKFILL: 2000 messages in 153.344 s (13.04 messages/s); budget 180 s
PASS 2,000 TLS fixture messages are listed and completion is real
PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark
CSP REPORTS mail-sync-613: 0 across 1 pages

Mail exited 0. All 12 macOS-rendered Settings/Mail screenshots are attached for the orchestrator's visual review. No screenshot is committed. No Mail rate issue is required because the measured backfill meets the budget.

Thumbnail readiness in the focused repeat, verbatim:

PASS text-card thumbnail ready: thumbnail-cache-547.txt (200 image/webp)
PASS pdf thumbnail ready: thumbnail-cache-547.pdf (200 image/webp)

Files focused command bun apps/web/e2e/files.mjs --rename-recent-only exited 0, verbatim:

PASS Files upload -> rename -> Recent (stable item identity)
CSP REPORTS files: 0 across 1 pages

It uses macOS emulation, uploads through the real file input, asserts rename HTTP 204 and stable item ID, then checks the committed name in Recent. The complete repeat did not pass: #961 records the slow-click wait failure. Its shell session ended with 143 after logging the failure, rather than producing the wrapper's exit marker. No positive full-suite result is inferred.

Decisions: no new UI design. Fresh default data-directory fixtures remove all inherited Notes listener fields, since any nested field enables that optional configuration; explicit data-directory restart fixtures retain their settings. Measure Mail from the start of account creation, including preceding UI checks, rather than restarting the timer at completion polling. Preserve response diagnostics using the existing uploader, without retrying or weakening a failing assertion.

UX gaps closed/verified: real Mail connection, live progress, 2,000-message listing and completion; Files worker thumbnails and upload -> committed rename -> Recent with stable item identity. UX gaps left: the complete Files repeat stopped at slow second click rename (TimeoutError: waitFor: Timeout 30000ms exceeded., #961). No complete Files e2e pass is claimed. Authorization and robustness gaps are listed above. No staging checks ran because this job explicitly forbids deploying. Performance measurements did not run because this issue is verification, not performance.

Remaining verification: after fixing #956/#957/#959/#960, run tests/adversarial/run.sh through live XUser/authz and all robustness/restart phases. After resolving #961, run CALTERNAL_SERVER_BIN=<rebuilt-server> bun apps/web/e2e/files.mjs through the full Files UI flow. This job did not repeat the full adversarial round or weaken an assertion.

Cleanup completed: cargo clean reported Removed 7538 files, 5.1GiB total; production web build output and private retained adversarial fixtures were removed. Logs and screenshot evidence remain in artifacts/round4. The working tree is clean.

**Deploy readiness: no.** No push or deploy was performed. Built the production SPA plus calternal-server, calternal-cli and calternal-sync. The server includes mail-test-provider for the isolated local TLS fixture. Fetched and merged origin/dev once before final verification: Already up to date. The first server build failed because the shared sccache process used a deleted worktree's temporary directory. The same assigned target built successfully with RUSTC_WRAPPER empty for this job; no shared process was reset. Built/fixed: fresh e2e servers no longer inherit a parent Notes listener configuration without its private TLS files. The real-server regression failed before the fix and passed afterward. Files failure reports retain redacted server diagnostics and use the existing byte uploader to preserve failure envelopes. Mail measures the full 180-second budget from account creation and reports actual progress if UI checks consume the budget. Thumbnail evidence reports successful renderer-specific WebP responses. A focused upload/rename/Recent mode shares the full probe's rename action; it does not bypass any full-flow assertion or claim the full suite passed. Files: apps/web/e2e/harness.mjs, harness.test.mjs, files.mjs and mail-sync-613.mjs. No production Rust or UI source changed. Existing test expectations and fixture bytes were preserved. Seven atomic commits made in this round. Head: `da5c2890108494294e3e25ce22a62bdb09419bfc` on `job/merge-round-7a`. Gate output, verbatim: ``` Wrote site to "build" Finished `dev` profile [unoptimized + debuginfo] target(s) in 80m 45s svelte-check found 0 errors and 0 warnings Test Files 1 failed | 153 passed (154) Tests 1 failed | 1072 passed (1073) Test Files 1 passed (1) Tests 11 passed (11) # tests 1 # pass 1 # fail 0 ``` The full Vitest suite exited 1 for one OverlaySurface 5-second timeout. That unchanged file passed alone, so it meets the requested load classification. cargo fmt --check exited 0 with no output. node --check and git diff --check passed. No crate changed, so per-changed-crate clippy/test gates do not apply. Adversarial command tests/adversarial/run.sh exited 1. Verbatim summaries: ``` Cross-User classification gate: 342 operations classified Generated entry point classification: 960 tools classified Admin coverage: 39 reviewed operations; contract and Rust guards agree PASS: bounded sealed image/video probes, thumbnails and HLS transcode PASS: document namespace, private input, inherited limits and network policy PASS: PDF link/script metadata and bounded SVG references/entities stay isolated PASS: explicit PDF and SVG loaders produce WebP; PDF action rejects JPEG RuntimeError: fixture Upload create returned HTTP -1 RuntimeError: Admin denial App Password fixture api_read returned -1 editor findings=10 seed=25608414 historySeeds=25608414 ``` Search recovery failures and repair 503 are #956. Live XUser/authz matrices aborted before coverage (#957). Seven existing standalone theme mock tests fail with window undefined (#958); their fixtures/expectations were not changed. Editor restart barrier timeout aborted remaining robustness probes, then cleanup waited indefinitely; this job's stuck server was killed (#959). The full matrices and remaining robustness phases did not complete and cannot be called passing. The original ordinary Files upload returned 503, with captured database checkout waits up to 16.20410488 seconds but no upload failure line (#960). Its precise cause remains unresolved; a successful repeat is not a root-cause fix. Mail, verbatim: ``` MAIL BACKFILL: 2000 messages in 153.344 s (13.04 messages/s); budget 180 s PASS 2,000 TLS fixture messages are listed and completion is real PASS Mail screenshots captured at 390, 820, and 1440px in Light and Dark CSP REPORTS mail-sync-613: 0 across 1 pages ``` Mail exited 0. [All 12 macOS-rendered Settings/Mail screenshots](https://git.kayg.org/attachments/d66959bf-b834-46e8-8e71-4a9ba7caf179) are attached for the orchestrator's visual review. No screenshot is committed. No Mail rate issue is required because the measured backfill meets the budget. Thumbnail readiness in the focused repeat, verbatim: ``` PASS text-card thumbnail ready: thumbnail-cache-547.txt (200 image/webp) PASS pdf thumbnail ready: thumbnail-cache-547.pdf (200 image/webp) ``` Files focused command `bun apps/web/e2e/files.mjs --rename-recent-only` exited 0, verbatim: ``` PASS Files upload -> rename -> Recent (stable item identity) CSP REPORTS files: 0 across 1 pages ``` It uses macOS emulation, uploads through the real file input, asserts rename HTTP 204 and stable item ID, then checks the committed name in Recent. The complete repeat did not pass: #961 records the slow-click wait failure. Its shell session ended with 143 after logging the failure, rather than producing the wrapper's exit marker. No positive full-suite result is inferred. Decisions: no new UI design. Fresh default data-directory fixtures remove all inherited Notes listener fields, since any nested field enables that optional configuration; explicit data-directory restart fixtures retain their settings. Measure Mail from the start of account creation, including preceding UI checks, rather than restarting the timer at completion polling. Preserve response diagnostics using the existing uploader, without retrying or weakening a failing assertion. UX gaps closed/verified: real Mail connection, live progress, 2,000-message listing and completion; Files worker thumbnails and upload -> committed rename -> Recent with stable item identity. UX gaps left: the complete Files repeat stopped at slow second click rename (`TimeoutError: waitFor: Timeout 30000ms exceeded.`, #961). No complete Files e2e pass is claimed. Authorization and robustness gaps are listed above. No staging checks ran because this job explicitly forbids deploying. Performance measurements did not run because this issue is verification, not performance. Remaining verification: after fixing #956/#957/#959/#960, run `tests/adversarial/run.sh` through live XUser/authz and all robustness/restart phases. After resolving #961, run `CALTERNAL_SERVER_BIN=<rebuilt-server> bun apps/web/e2e/files.mjs` through the full Files UI flow. This job did not repeat the full adversarial round or weaken an assertion. Cleanup completed: `cargo clean` reported `Removed 7538 files, 5.1GiB total`; production web build output and private retained adversarial fixtures were removed. Logs and screenshot evidence remain in artifacts/round4. The working tree is clean.
Author
Owner

Staging smoke: merge round 7a on dev.calternal.com

Image: staging-a0f94231b from 06:02Z to 06:30Z. At 06:30:19Z another job redeployed staging with staging-imap-0e8fde491, which is 7a plus #941 (Notes IMAP edge) and has no new migrations. Calendar and Notes ran on the 7a image. Tasks, Files, Photos, Search, Settings and Mail ran on the imap image. Accounts: interop-admin (Owner) and macinterop (User), both with saved browser states. Screenshots at 1440 px, light theme, are in the private staging/smoke-7a/ folder.

Flows

Flow Result
Sign-in (Owner and User) PASS. Both sessions load and /auth/me returns 200.
Calendar: create, edit, move, delete and Undo a Log entry PASS. The writes are POST log/batch 201, PATCH 200 (edit), PATCH 200 (move to 07:00), DELETE 204, Undo, DELETE 204.
Calendar: Task tick No tick control exists in Calendar (chip, preview and context menu). I tested the tick in Notes.
Notes: create, edit, reload, link PASS. [[Smoke7a task]] becomes a Markdown link. Ctrl+click opens the target and Linked mentions shows 1. Copy link gives /n/<id> and that link opens the note.
Task tick and Undo (inline Task in Notes) PASS. The Task API shows done. Ctrl+Z with the cursor in the text sets it back to todo.
Files: upload, F2 rename, Recent, slow second click, Trash and Undo PASS
Photos: upload and Quick Look PARTIAL. The upload (201/204) and Quick Look work. The photo does not appear live (see 3 below). Thumbnails 404 (see 4 below).
Search: new note, file name, file content PASS
Settings: Connected Accounts, App Password create and revoke (Owner and User) PASS. Create gets 403, then passkey step-up, then 200. Revoke returns DELETE 204.
Mail page PASS. It shows the empty state and there are no console errors.

Server log (06:02Z to 06:52Z)

  • No panic, no ERROR and no 5xx produced by the server.
  • Two 502s came from the edge. The first was during the other job's restart. The second was one CalDAV DELETE on the imap image; the retry got 204.
  • 682 × WARN Skipped a Task projection with an empty title (#623), mostly as bursts at startup.
  • 9 × WARN Notes change feed publication failed error=invalid relative path, only on the imap image, during outside WebDAV writes.

Upgrade

git log c4a61e8cf..a0f94231b -- '**/migrations/**' adds 13 migrations: db 0007–0012, auth 0012, Calendar 0005, Files 0019/0020, Notes 0025/0026, Search 0004. No migration changed after the tested head 0a16bf704.

  • Staging upgraded its c4a61e8cf data to 7a at 05:52:55Z. _migrations lists all 13 entries, integration_migration_status is legacy_accounts_v1 = complete and integration_migration_failures has no rows.
  • Round 2 tested real data, then a downgrade (missing: [], upgrade_checks: pass), and Mail counts and checksums did not change. That report lists "Files 0016–0019", but 0016–0018 are already in production and 7a's new Files migrations are 0019/0020. Group 2 set the expected maximum to 20, and staging applied 0020.
  • Verdict: production data will upgrade. One open owner question: the Notes folder ID changes on upgrade (#627), so old folder links to Notes break.

Bugs found (not 7a regressions: the same code is in c4a61e8cf)

  1. If you click Save in the Composer before the live parse returns, the edit fails with "Changes could not be saved" and no request is sent. In Log mode the send uses liveParse, which can still be null (Composer.svelte:1069).
  2. Ticking the root checkbox of a File Task in the editor writes - [x] but leaves status: todo. The Task stays open in the API, Calendar and Reminders, because the sync only goes from frontmatter to checkbox.
  3. Photos does not show a new upload live. refreshBuckets() returns early when the buckets are less than 30 s old (BUCKETS_STALE_MS), so an upload made soon after the page loads stays hidden until a reload. The SSE events arrive.
  4. The media sandbox cannot start on staging: bwrap: Creating new namespace failed: Resource temporarily unavailable (the outer NPROC limit is 64). Every thumbnail gets decoder-failed-v1 and nothing is logged (54 failure markers, the first from 2026-10-02 19:24 on c4a61e8cf). Check that production thumbnails work.
  5. Search responses carry the item counts of the whole instance's Index. Fixed in 79effe5cf, which is after a0f94231b.

The macOS Calendar and Reminders check was skipped because the staging-imap job held the Mac lock. CalDAV PUT and GET for a Log VEVENT and a VTODO work with the Mac App Password.

Ready for production: yes. This smoke found no 7a regression and the upgrade is proven. The round's own adversarial run on a0f94231b exited 1; its Search size finding is the leak fixed in 79effe5cf.

## Staging smoke: merge round 7a on dev.calternal.com **Image:** `staging-a0f94231b` from 06:02Z to 06:30Z. At 06:30:19Z another job redeployed staging with `staging-imap-0e8fde491`, which is 7a plus #941 (Notes IMAP edge) and has no new migrations. Calendar and Notes ran on the 7a image. Tasks, Files, Photos, Search, Settings and Mail ran on the imap image. Accounts: `interop-admin` (Owner) and `macinterop` (User), both with saved browser states. Screenshots at 1440 px, light theme, are in the private `staging/smoke-7a/` folder. ### Flows | Flow | Result | |---|---| | Sign-in (Owner and User) | PASS. Both sessions load and `/auth/me` returns 200. | | Calendar: create, edit, move, delete and Undo a Log entry | PASS. The writes are `POST log/batch` 201, `PATCH` 200 (edit), `PATCH` 200 (move to 07:00), `DELETE` 204, Undo, `DELETE` 204. | | Calendar: Task tick | No tick control exists in Calendar (chip, preview and context menu). I tested the tick in Notes. | | Notes: create, edit, reload, link | PASS. `[[Smoke7a task]]` becomes a Markdown link. Ctrl+click opens the target and Linked mentions shows 1. Copy link gives `/n/<id>` and that link opens the note. | | Task tick and Undo (inline Task in Notes) | PASS. The Task API shows `done`. Ctrl+Z with the cursor in the text sets it back to `todo`. | | Files: upload, F2 rename, Recent, slow second click, Trash and Undo | PASS | | Photos: upload and Quick Look | PARTIAL. The upload (201/204) and Quick Look work. The photo does not appear live (see 3 below). Thumbnails 404 (see 4 below). | | Search: new note, file name, file content | PASS | | Settings: Connected Accounts, App Password create and revoke (Owner and User) | PASS. Create gets 403, then passkey step-up, then 200. Revoke returns `DELETE` 204. | | Mail page | PASS. It shows the empty state and there are no console errors. | ### Server log (06:02Z to 06:52Z) - No panic, no `ERROR` and no 5xx produced by the server. - Two 502s came from the edge. The first was during the other job's restart. The second was one CalDAV DELETE on the imap image; the retry got 204. - 682 × `WARN Skipped a Task projection with an empty title (#623)`, mostly as bursts at startup. - 9 × `WARN Notes change feed publication failed error=invalid relative path`, only on the imap image, during outside WebDAV writes. ### Upgrade `git log c4a61e8cf..a0f94231b -- '**/migrations/**'` adds 13 migrations: db 0007–0012, auth 0012, Calendar 0005, Files 0019/0020, Notes 0025/0026, Search 0004. No migration changed after the tested head `0a16bf704`. - Staging upgraded its c4a61e8cf data to 7a at 05:52:55Z. `_migrations` lists all 13 entries, `integration_migration_status` is `legacy_accounts_v1 = complete` and `integration_migration_failures` has no rows. - Round 2 tested real data, then a downgrade (`missing: []`, `upgrade_checks: pass`), and Mail counts and checksums did not change. That report lists "Files 0016–0019", but 0016–0018 are already in production and 7a's new Files migrations are 0019/0020. Group 2 set the expected maximum to 20, and staging applied 0020. - **Verdict:** production data will upgrade. One open owner question: the `Notes` folder ID changes on upgrade (#627), so old folder links to Notes break. ### Bugs found (not 7a regressions: the same code is in c4a61e8cf) 1. If you click Save in the Composer before the live parse returns, the edit fails with "Changes could not be saved" and no request is sent. In Log mode the send uses `liveParse`, which can still be null (`Composer.svelte:1069`). 2. Ticking the root checkbox of a File Task in the editor writes `- [x]` but leaves `status: todo`. The Task stays open in the API, Calendar and Reminders, because the sync only goes from frontmatter to checkbox. 3. Photos does not show a new upload live. `refreshBuckets()` returns early when the buckets are less than 30 s old (`BUCKETS_STALE_MS`), so an upload made soon after the page loads stays hidden until a reload. The SSE events arrive. 4. The media sandbox cannot start on staging: `bwrap: Creating new namespace failed: Resource temporarily unavailable` (the outer NPROC limit is 64). Every thumbnail gets `decoder-failed-v1` and nothing is logged (54 failure markers, the first from 2026-10-02 19:24 on c4a61e8cf). Check that production thumbnails work. 5. Search responses carry the item counts of the whole instance's Index. Fixed in `79effe5cf`, which is after `a0f94231b`. The macOS Calendar and Reminders check was skipped because the staging-imap job held the Mac lock. CalDAV PUT and GET for a Log VEVENT and a VTODO work with the Mac App Password. **Ready for production: yes.** This smoke found no 7a regression and the upgrade is proven. The round's own adversarial run on `a0f94231b` exited 1; its Search size finding is the leak fixed in `79effe5cf`.
Author
Owner

Started merge-round 7a verification on job/merge-round-7a at 61222f456d; origin/dev was 4a871b3838 at start. I am mapping the requested probes and will run them against one release server.

Started merge-round 7a verification on job/merge-round-7a at 61222f456d0783500e01d3f18a548403d31b83d0; origin/dev was 4a871b383864dad0d6c87bcd22b7f0d589e3506e at start. I am mapping the requested probes and will run them against one release server.
Author
Owner

Full adversarial run found a Files TUS concurrency regression in the Journal race probe: 2 of 10 conditional upload PATCH requests returned HTTP 500 (upload state failed) after index_and_change_feed completed; the failure was at crates/plugins/files/src/uploads.rs:1683. The destination had a competing committed revision. I changed the finalize check to classify a changed destination as HTTP 412 while keeping HTTP 500 and the retained install intent when live bytes match but the Index row is corrupt. Added a focused regression test; crate gates are pending after the single adversarial run finishes.

Full adversarial run found a Files TUS concurrency regression in the Journal race probe: 2 of 10 conditional upload PATCH requests returned HTTP 500 (`upload state failed`) after `index_and_change_feed` completed; the failure was at `crates/plugins/files/src/uploads.rs:1683`. The destination had a competing committed revision. I changed the finalize check to classify a changed destination as HTTP 412 while keeping HTTP 500 and the retained install intent when live bytes match but the Index row is corrupt. Added a focused regression test; crate gates are pending after the single adversarial run finishes.
Author
Owner

Verification finding: the Files crate suite timed out in the existing write/reconcile storm test. Exact output:

test tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm ... FAILED

thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:5260:14:
writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(())
thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:5234:45:
atomic write 628 failed: entry not found
test result: FAILED. 157 passed; 1 failed; 1 ignored; 0 measured; finished in 472.44s

The host load average at the end was 40.96, 37.81, 36.66. The new focused TUS regression test passed separately (1 passed). I did not change the existing test or its expectations. This storm timeout is outside the TUS code changed for #427 and remains for triage.

Verification finding: the Files crate suite timed out in the existing write/reconcile storm test. Exact output: ```text test tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm ... FAILED thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:5260:14: writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(()) thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:5234:45: atomic write 628 failed: entry not found test result: FAILED. 157 passed; 1 failed; 1 ignored; 0 measured; finished in 472.44s ``` The host load average at the end was 40.96, 37.81, 36.66. The new focused TUS regression test passed separately (1 passed). I did not change the existing test or its expectations. This storm timeout is outside the TUS code changed for #427 and remains for triage.
Author
Owner

verify-7a final report

Branch: job/merge-round-7a
Merged origin/dev once at 516faaa698570bdb468626cf6cd75d9c81b33ac2.
Head: c82b9aca1c5719205fc33260ce050e77737e1427.

Built

Fixed the Files TUS completion race. A destination changed by a competing committed upload now returns the existing 412 precondition response so the client can retry. If live bytes still match but the Index row is damaged, completion still returns 500 and retains the install intent for repair. Added a focused regression test. Files changed: crates/plugins/files/src/index.rs, crates/plugins/files/src/uploads.rs, crates/plugins/files/src/lib.rs.

Commit: c82b9aca1 fix(files): classify concurrent TUS destination changes.

Verification

Built the release server, CLI, and sync crates once. Output:

Finished `release` profile [optimized] target(s) in 38m 28s

The full tests/adversarial/run.sh probe reached its final authz matrix. The shell wrapper then failed to retain the runner status because zsh treats status as read-only; exact wrapper output: zsh:1: read-only variable: status. Findings and matrix output were retained in the run log. Static gates reported:

Cross-User classification gate: 342 operations classified
Generated entry point classification: 960 tools classified
Admin coverage: 39 reviewed operations; contract and Rust guards agree

#957 XUser and #959 editor matrix output:

Two-User OpenAPI matrix: 342 operations classified; 159 operations replayed; 733 A-ID vs missing-ID comparisons across B, C, D and anonymous; 25 identifier routes classified with no local fixture factory; median absolute timing delta 0.5 ms
Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures
PASS editor all areas seed=25608414 historySeeds=25608414

Search count leak commit 79effe5cf is an ancestor of this head (git merge-base --is-ancestor 79effe5cf HEAD exited 0); the XUser Search isolation probe passed. The final #957 authz matrix completed all 2,216 requests but had six no-response timeouts and no reported authorization mismatch:

Authorization matrix: 342 OpenAPI operations; 2216 requests across 4 base identities plus invalid/stale session probes and 18 App Password scope classes; valid/malformed session bodies on 14 privileged body routes; App Passwords use valid bodies; policy classes {'public': 19, 'public_link': 14, 'user': 270, 'admin': 39}
!! POST /api/v1/admin/search/integrity/check as admin/valid: server response -1: b'timed out'
!! POST /api/v1/admin/search/rebuild as app_password_mcp_write/valid: server response -1: b'timed out'
!! POST /api/v1/admin/search/rebuild as app_password_mcp_full/valid: server response -1: b'timed out'
!! GET /api/v1/admin/user-archives as app_password_api_upload_only/valid: server response -1: b'timed out'
!! POST /api/v1/notes/linked-notes as standard/valid: server response -1: b'timed out'
!! DELETE /api/v1/notifications/subscriptions/{installation_id} as standard/valid: server response -1: b'timed out'
authorization matrix found 6 failures

#964 production Files E2E passed, including type-to-select in Recent:

PASS keyboard Trash action in Recent and Undo
FILES E2E PASSED
CSP REPORTS files: 0 across 16 pages

Rust gate output:

$ cargo fmt --check
(exit 0; no output)
$ cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 25s

The Files crate suite had one timeout in the existing write/reconcile storm test; the focused TUS regression passed separately:

thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:5260:14:
writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(())
thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:5234:45:
atomic write 628 failed: entry not found
test result: FAILED. 157 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 472.44s
error: test failed, to rerun pass `-p calternal-plugin-files --lib`
running 1 test
test tests::tus_reports_conflict_when_another_upload_replaces_the_destination ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 158 filtered out; finished in 3.91s

cargo clippy -p calternal-server --all-targets -- -D warnings was started but stopped at the four-hour job cutoff before it reached calternal-server; cargo test -p calternal-server was not run. The merge round must run both server gates. cargo clean completed with:

Removed 18010 files, 6.5GiB total

The web build output was removed.

Known gaps and findings

The adversarial rounds found unresolved content scrub/GC problems: scrub stayed in phase=scanning at 90 and 180 seconds without repairing damaged CAS content; after restart, 32 orphan blobs and marker garbage remained. This is a data-integrity blocker (#972). The 50K-file deletion/purge probe did not complete within its bound (#78, #174). The authz matrix had the six timeouts above; the Notes IMAP valid APPEND timed out after continuation. Search upload/rename storm requests also timed out under load; the matrix reported no Search isolation or committed-hit failure. Server SIGTERM exceeded 60 seconds and the harness used SIGKILL (#963). These findings were recorded on the existing issues, including #956, #269, #190, #960, #454, #972, #78, #174, #963 and #965. Staging smoke was already reported as passed.

The Files storm test failed at its five-minute deadline under host load (load average 40.96, 37.81, 36.66); its assertion was not changed. The full adversarial wrapper status was not captured due the zsh variable collision described above.

Decision

DESIGN.md does not set the response for a TUS destination replaced by a newer concurrent upload. I used 412 Precondition Failed because the upload is stale and the sync client can retry against the current revision. A verified Index mismatch still uses the prior 500 recovery path.

7a READY FOR PRODUCTION: no

# verify-7a final report Branch: `job/merge-round-7a` Merged `origin/dev` once at `516faaa698570bdb468626cf6cd75d9c81b33ac2`. Head: `c82b9aca1c5719205fc33260ce050e77737e1427`. ## Built Fixed the Files TUS completion race. A destination changed by a competing committed upload now returns the existing 412 precondition response so the client can retry. If live bytes still match but the Index row is damaged, completion still returns 500 and retains the install intent for repair. Added a focused regression test. Files changed: `crates/plugins/files/src/index.rs`, `crates/plugins/files/src/uploads.rs`, `crates/plugins/files/src/lib.rs`. Commit: `c82b9aca1 fix(files): classify concurrent TUS destination changes`. ## Verification Built the release server, CLI, and sync crates once. Output: ```text Finished `release` profile [optimized] target(s) in 38m 28s ``` The full `tests/adversarial/run.sh` probe reached its final authz matrix. The shell wrapper then failed to retain the runner status because zsh treats `status` as read-only; exact wrapper output: `zsh:1: read-only variable: status`. Findings and matrix output were retained in the run log. Static gates reported: ```text Cross-User classification gate: 342 operations classified Generated entry point classification: 960 tools classified Admin coverage: 39 reviewed operations; contract and Rust guards agree ``` #957 XUser and #959 editor matrix output: ```text Two-User OpenAPI matrix: 342 operations classified; 159 operations replayed; 733 A-ID vs missing-ID comparisons across B, C, D and anonymous; 25 identifier routes classified with no local fixture factory; median absolute timing delta 0.5 ms Job/Mail/quota ownership checks: 97 comparisons; 0 denial failures PASS editor all areas seed=25608414 historySeeds=25608414 ``` Search count leak commit `79effe5cf` is an ancestor of this head (`git merge-base --is-ancestor 79effe5cf HEAD` exited 0); the XUser Search isolation probe passed. The final #957 authz matrix completed all 2,216 requests but had six no-response timeouts and no reported authorization mismatch: ```text Authorization matrix: 342 OpenAPI operations; 2216 requests across 4 base identities plus invalid/stale session probes and 18 App Password scope classes; valid/malformed session bodies on 14 privileged body routes; App Passwords use valid bodies; policy classes {'public': 19, 'public_link': 14, 'user': 270, 'admin': 39} !! POST /api/v1/admin/search/integrity/check as admin/valid: server response -1: b'timed out' !! POST /api/v1/admin/search/rebuild as app_password_mcp_write/valid: server response -1: b'timed out' !! POST /api/v1/admin/search/rebuild as app_password_mcp_full/valid: server response -1: b'timed out' !! GET /api/v1/admin/user-archives as app_password_api_upload_only/valid: server response -1: b'timed out' !! POST /api/v1/notes/linked-notes as standard/valid: server response -1: b'timed out' !! DELETE /api/v1/notifications/subscriptions/{installation_id} as standard/valid: server response -1: b'timed out' authorization matrix found 6 failures ``` #964 production Files E2E passed, including type-to-select in Recent: ```text PASS keyboard Trash action in Recent and Undo FILES E2E PASSED CSP REPORTS files: 0 across 16 pages ``` Rust gate output: ```text $ cargo fmt --check (exit 0; no output) $ cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 25s ``` The Files crate suite had one timeout in the existing write/reconcile storm test; the focused TUS regression passed separately: ```text thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:5260:14: writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(()) thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:5234:45: atomic write 628 failed: entry not found test result: FAILED. 157 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 472.44s error: test failed, to rerun pass `-p calternal-plugin-files --lib` ``` ```text running 1 test test tests::tus_reports_conflict_when_another_upload_replaces_the_destination ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 158 filtered out; finished in 3.91s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings` was started but stopped at the four-hour job cutoff before it reached `calternal-server`; `cargo test -p calternal-server` was not run. The merge round must run both server gates. `cargo clean` completed with: ```text Removed 18010 files, 6.5GiB total ``` The web build output was removed. ## Known gaps and findings The adversarial rounds found unresolved content scrub/GC problems: scrub stayed in `phase=scanning` at 90 and 180 seconds without repairing damaged CAS content; after restart, 32 orphan blobs and marker garbage remained. This is a data-integrity blocker (#972). The 50K-file deletion/purge probe did not complete within its bound (#78, #174). The authz matrix had the six timeouts above; the Notes IMAP valid APPEND timed out after continuation. Search upload/rename storm requests also timed out under load; the matrix reported no Search isolation or committed-hit failure. Server SIGTERM exceeded 60 seconds and the harness used SIGKILL (#963). These findings were recorded on the existing issues, including #956, #269, #190, #960, #454, #972, #78, #174, #963 and #965. Staging smoke was already reported as passed. The Files storm test failed at its five-minute deadline under host load (load average 40.96, 37.81, 36.66); its assertion was not changed. The full adversarial wrapper status was not captured due the zsh variable collision described above. ## Decision DESIGN.md does not set the response for a TUS destination replaced by a newer concurrent upload. I used 412 Precondition Failed because the upload is stale and the sync client can retry against the current revision. A verified Index mismatch still uses the prior 500 recovery path. 7a READY FOR PRODUCTION: no
Author
Owner

UX gaps closed: no UI code changed in this verification job. The #964 keyboard type-to-select behavior in Files Recent passed in the production E2E.

UX gaps left: none found in the #964 flow exercised by that E2E.

UX gaps closed: no UI code changed in this verification job. The #964 keyboard type-to-select behavior in Files Recent passed in the production E2E. UX gaps left: none found in the #964 flow exercised by that E2E.
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#427
No description provided.