Calendar: photo/file activity cards overlap entries and don't scale; give them their own lane and size them to the view #589

Open
opened 2026-10-01 07:29:34 +00:00 by kayg · 17 comments
Owner

Owner report (2026-10-01, Calendar week view screenshot, narrow column)

"This is the wrong way for images to appear: they should take up their own row/spacing, and they should scale depending on the view and space available, so here they should be way smaller!"
In a narrow Week column, photo/file activity cards (a large square photo, and a large square file card) are drawn on top of the Log entries (they cover "…ym", "Working on…", "Croit Consulting…"). They are fixed large squares, regardless of the column width or the hour height.
Fix (packages/ui/src/components/calendar/ActivityStack.svelte, layout.ts, model.ts; shared layout, not per view):

  • Own space: activity (photos/files captured at a time) is a real layout participant. It gets its own lane/column in the time grid's overlap layout (as an event would), so it never overlaps Log entries, Events or Tasks. In Day view with room, the lane sits beside the entries. In narrow columns, it shares the overlap layout like any other item.
  • Scale with the view: the thumbnail size derives from the available lane width and the hour height (zoom). Clamp between a minimum (e.g. a 20–24 px chip) and a maximum (the lane width). When there is no room for a thumbnail, show a compact chip "📷 3" / "📄 2" (count + kind icon) that expands on hover/click into the existing stack preview.
  • Bursts: several photos at nearly the same time collapse into one stack with a count (no tower of squares).
  • Month/Year views: a small dot/chip only. Agenda: an inline thumbnail row under the entry.
  • Keyboard and screen reader: the chip is a button with "3 photos at 14:20".
    Tests:
  • a layout unit test: activity boxes never intersect item boxes, for 1/3/7-day views at 390/820/1440 and 3 zoom levels;
  • an e2e with a real photo burst (link #427/#585 fixtures): the screenshots show thumbnails sized to the lane.
    Screenshots (Week narrow + wide, Day, Month) in both themes. Web gates + calendar crate tests if touched.
## Owner report (2026-10-01, Calendar week view screenshot, narrow column) "This is the wrong way for images to appear: they should take up their own row/spacing, and they should scale depending on the view and space available, so here they should be way smaller!" In a narrow Week column, photo/file activity cards (a large square photo, and a large square file card) are drawn **on top of** the Log entries (they cover "…ym", "Working on…", "Croit Consulting…"). They are fixed large squares, regardless of the column width or the hour height. **Fix (`packages/ui/src/components/calendar/ActivityStack.svelte`, `layout.ts`, `model.ts`; shared layout, not per view):** - **Own space:** activity (photos/files captured at a time) is a real layout participant. It gets its own lane/column in the time grid's overlap layout (as an event would), so it never overlaps Log entries, Events or Tasks. In Day view with room, the lane sits beside the entries. In narrow columns, it shares the overlap layout like any other item. - **Scale with the view:** the thumbnail size derives from the available lane width and the hour height (zoom). Clamp between a minimum (e.g. a 20–24 px chip) and a maximum (the lane width). When there is no room for a thumbnail, show a compact chip "📷 3" / "📄 2" (count + kind icon) that expands on hover/click into the existing stack preview. - **Bursts:** several photos at nearly the same time collapse into one stack with a count (no tower of squares). - **Month/Year views:** a small dot/chip only. **Agenda:** an inline thumbnail row under the entry. - Keyboard and screen reader: the chip is a button with "3 photos at 14:20". **Tests:** - a layout unit test: activity boxes never intersect item boxes, for 1/3/7-day views at 390/820/1440 and 3 zoom levels; - an e2e with a real photo burst (link #427/#585 fixtures): the screenshots show thumbnails sized to the lane. Screenshots (Week narrow + wide, Day, Month) in both themes. Web gates + calendar crate tests if touched.
Author
Owner

Starting calimg-589 on branch job/calimg-589, based on cc25c441b7a974185622a1dee853cf38686d2b67. I will implement the shared Calendar activity layout changes from #589 and the related photo date/preference changes from #624 in this job.

Starting calimg-589 on branch `job/calimg-589`, based on `cc25c441b7a974185622a1dee853cf38686d2b67`. I will implement the shared Calendar activity layout changes from #589 and the related photo date/preference changes from #624 in this job.
Author
Owner

#624 API finding and fix: /api/v1/calendar/items previously chose either capture time or file modification time with photos=taken|added, so it could not place the same photo on both dates. The Calendar route now returns the capture row and upload-day row when their local dates differ, keeps only the captured row when both dates match, and uses a source byte in the cursor to keep equal-time rows page-safe. CalendarPreferences no longer exposes photo_time; GET lazily removes the old field from .calternal/settings.json, and PUT rejects it. Evidence: cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings passed; cargo test -p calternal-plugin-calendar passed (84 unit tests, 4 integration tests). The shared UI layout and preview work is still in progress.

#624 API finding and fix: `/api/v1/calendar/items` previously chose either capture time or file modification time with `photos=taken|added`, so it could not place the same photo on both dates. The Calendar route now returns the capture row and upload-day row when their local dates differ, keeps only the captured row when both dates match, and uses a source byte in the cursor to keep equal-time rows page-safe. `CalendarPreferences` no longer exposes `photo_time`; GET lazily removes the old field from `.calternal/settings.json`, and PUT rejects it. Evidence: `cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings` passed; `cargo test -p calternal-plugin-calendar` passed (84 unit tests, 4 integration tests). The shared UI layout and preview work is still in progress.
Author
Owner

Finding: Month/Year and Agenda consumed only the range and summary projections based on file modification time, so they missed the capture-date rows already available from the Calendar items API. The shared range loader now merges those rows for Agenda and Month, and Year reads them in bounded 42-day ranges. Regression coverage checks both capture and upload dates in Month/Agenda data and Year counts.

Finding: Month/Year and Agenda consumed only the range and summary projections based on file modification time, so they missed the capture-date rows already available from the Calendar items API. The shared range loader now merges those rows for Agenda and Month, and Year reads them in bounded 42-day ranges. Regression coverage checks both capture and upload dates in Month/Agenda data and Year counts.
Author
Owner

Production E2E finding for #589/#624: the Calendar suite reached the composer commit-freeze scenario and failed its existing snapshot assertion. The request body was 20:16 nFrozen snapshot; the extra n is the keyboard shortcut used to open the composer and was inserted into the text field after focus. This shows the animated keyboard open path can leak its trigger key into the newly focused editor. I am tracing the shortcut dispatch and will fix the interaction without changing the test expectation.

Production E2E finding for #589/#624: the Calendar suite reached the composer commit-freeze scenario and failed its existing snapshot assertion. The request body was `20:16 nFrozen snapshot`; the extra `n` is the keyboard shortcut used to open the composer and was inserted into the text field after focus. This shows the animated keyboard open path can leak its trigger key into the newly focused editor. I am tracing the shortcut dispatch and will fix the interaction without changing the test expectation.
Author
Owner

Correction to my previous E2E finding: a focused production probe showed the n shortcut is prevented on the keydown and does not enter a newly opened composer. The full suite had left the composer open after the “Discard clears the draft” assertion; its next openComposer() call typed n into that already-focused text field, producing 20:16 nFrozen snapshot. I will close the composer in the test setup before beginning the commit-freeze case. This is an E2E setup defect, not a Calendar shortcut behavior defect.

Correction to my previous E2E finding: a focused production probe showed the `n` shortcut is prevented on the keydown and does not enter a newly opened composer. The full suite had left the composer open after the “Discard clears the draft” assertion; its next `openComposer()` call typed `n` into that already-focused text field, producing `20:16 nFrozen snapshot`. I will close the composer in the test setup before beginning the commit-freeze case. This is an E2E setup defect, not a Calendar shortcut behavior defect.
Author
Owner

Production E2E follow-up: after the bottom-edge resize, the Calendar API read returned the expected 11:00 end, but the next immediate drag preview still used the rendered 10:30 end. The test began the next pointer action before the row reflected the successful write. I am adding a rendered-state wait before that next action; if the row does not update within the same bound, I will treat it as a UI reconciliation bug.

Production E2E follow-up: after the bottom-edge resize, the Calendar API read returned the expected `11:00` end, but the next immediate drag preview still used the rendered `10:30` end. The test began the next pointer action before the row reflected the successful write. I am adding a rendered-state wait before that next action; if the row does not update within the same bound, I will treat it as a UI reconciliation bug.
Author
Owner

The same production E2E race occurs after Undo: the Calendar Journal API returned the restored 09:00 start, but the next resize preview still read 10:00. The app starts a Calendar refresh after the write; the test can begin another drag before the displayed row reflects that read. I am checking the row's accessible label before the next pointer action so the probe can distinguish a delayed render from a persistent stale row.

The same production E2E race occurs after Undo: the Calendar Journal API returned the restored `09:00` start, but the next resize preview still read `10:00`. The app starts a Calendar refresh after the write; the test can begin another drag before the displayed row reflects that read. I am checking the row's accessible label before the next pointer action so the probe can distinguish a delayed render from a persistent stale row.
Author
Owner

Clarification: the accessible label had already updated to the restored 09:00 range; the failing drag preview came from the row's CSS geometry still transitioning after keyboard Undo. The new owner override correctly animates keyboard-triggered updates, so the test's next pointer action landed during that motion and selected the wrong resize region. I am changing the E2E to wait for the finite row transition before the next drag. This is test synchronization, not a product animation change.

Clarification: the accessible label had already updated to the restored `09:00` range; the failing drag preview came from the row's CSS geometry still transitioning after keyboard Undo. The new owner override correctly animates keyboard-triggered updates, so the test's next pointer action landed during that motion and selected the wrong resize region. I am changing the E2E to wait for the finite row transition before the next drag. This is test synchronization, not a product animation change.
Author
Owner

Finding for #589/#624 E2E: editing the 2026-09-30 Journal item “Morning review of the plan” sent a PATCH with date 2026-10-01, even though the edit had no date token. The parser resolves a time-only line to today; Calendar edit code used that raw inferred date. Normal Log creation already applies landingDate to preserve the selected day for this case. I aligned Calendar edits with that shared rule; an explicit different date still moves the item. Web check and full web tests pass after the fix. The E2E run is next to verify the edit and continue the requested photo screenshots.

Finding for #589/#624 E2E: editing the 2026-09-30 Journal item “Morning review of the plan” sent a PATCH with date 2026-10-01, even though the edit had no date token. The parser resolves a time-only line to today; Calendar edit code used that raw inferred date. Normal Log creation already applies `landingDate` to preserve the selected day for this case. I aligned Calendar edits with that shared rule; an explicit different date still moves the item. Web check and full web tests pass after the fix. The E2E run is next to verify the edit and continue the requested photo screenshots.
Author
Owner

Real #589/#624 finding from the production E2E: Calendar showed the 12-photo capture stack, but no preview opened on hover for the upload-day Added deck. The root pointer resolver only maps Log, Event and Task keys. TimeGrid already passes typed activity previews to its onhover callback, but the Calendar route did not connect that callback. I have wired the existing callback through the shared hover card path; I am rebuilding and rerunning the photo scenario now.

Real #589/#624 finding from the production E2E: Calendar showed the 12-photo capture stack, but no preview opened on hover for the upload-day Added deck. The root pointer resolver only maps Log, Event and Task keys. TimeGrid already passes typed activity previews to its `onhover` callback, but the Calendar route did not connect that callback. I have wired the existing callback through the shared hover card path; I am rebuilding and rerunning the photo scenario now.
Author
Owner

Adversarial round update (real local server, 2026-10-01)

The API-only run completed with the server alive. It reported 285 total findings, including load-classified SLOW results. Non-SLOW findings recorded for owner triage:

  • Tags: rename across sources returned 409 (expected 200); source and XMP repair reconciliation and rebuild returned 409 (expected 204), and rebuild found no source files.
  • DAV: a well-formed Journal alarm returned 204 (probe expected 201); a cross-date move and the following delete returned 412.
  • Calendar: the far-future items cursor returned 400 with "cursor is not valid" (probe expected 200).
  • Under concurrent writes, Calendar duplicate returned six IDs and timed out on the remaining 14 requests; Saved Search rename timed out on eight requests; template creation produced only 3/16 unique Note IDs. The bookmark search did not return its captured Note while reporting "timed_out: true" after indexing reached 100%.
  • The combined API-only invocation omitted the setup flag that creates MCP credentials. I reran MCP by itself with the fixture enabled; MCP Inspector then failed its exact tool-set assertion because it listed tools outside the probe's expected set.

The run also emitted many SLOW findings during the deliberate 120-photo upload and concurrency bursts. Per the owner rule, I classified those as load. I did not change unrelated Tags, DAV, Search or MCP contracts in this Calendar feature job.

Adversarial round update (real local server, 2026-10-01) The API-only run completed with the server alive. It reported 285 total findings, including load-classified SLOW results. Non-SLOW findings recorded for owner triage: - Tags: rename across sources returned 409 (expected 200); source and XMP repair reconciliation and rebuild returned 409 (expected 204), and rebuild found no source files. - DAV: a well-formed Journal alarm returned 204 (probe expected 201); a cross-date move and the following delete returned 412. - Calendar: the far-future items cursor returned 400 with "cursor is not valid" (probe expected 200). - Under concurrent writes, Calendar duplicate returned six IDs and timed out on the remaining 14 requests; Saved Search rename timed out on eight requests; template creation produced only 3/16 unique Note IDs. The bookmark search did not return its captured Note while reporting "timed_out: true" after indexing reached 100%. - The combined API-only invocation omitted the setup flag that creates MCP credentials. I reran MCP by itself with the fixture enabled; MCP Inspector then failed its exact tool-set assertion because it listed tools outside the probe's expected set. The run also emitted many SLOW findings during the deliberate 120-photo upload and concurrency bursts. Per the owner rule, I classified those as load. I did not change unrelated Tags, DAV, Search or MCP contracts in this Calendar feature job.
Author
Owner

Final report — #589 and #624

Head: eecd7b6b59 on job/calimg-589. No push, deploy or merge was made.

Built

  • Photo and file activity now occupies the shared Calendar overlap layout with Events, Logs and Tasks. Thumbnail size follows the lane width and hour zoom. Bursts pack into decks; narrow lanes use accessible count chips. Month/Year stay compact and Agenda keeps its inline row.
  • Photos appear on capture and upload dates. The same-day row keeps the capture role. Upload dates show an Added deck and a bounded, keyboard-scrollable carousel; selecting a photo opens Quick Look over the Calendar.
  • Removed the Photo date preference from Calendar settings, browser preference writes, the API contract and generated client. Existing saved settings are cleaned on read and update.
  • Added the shared layout tests, the real 12-photo E2E path, screenshot matrix and a hot-path bench profile.

Files

  • apps/web/e2e/calendar-doc-stack.mjs
  • apps/web/e2e/calendar-perf.mjs
  • apps/web/e2e/calendar-view-switcher.mjs
  • apps/web/e2e/calendar.mjs
  • apps/web/e2e/layout-sweep.mjs
  • 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/data.test.ts
  • apps/web/src/lib/calendar/data.ts
  • apps/web/src/lib/calendar/model.test.ts
  • apps/web/src/lib/calendar/prefs.test.ts
  • apps/web/src/lib/calendar/prefs.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/files/FilesSidebar.svelte
  • apps/web/src/lib/notifications/InboxPanel.svelte
  • 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/calendars/CalendarsSection.svelte
  • bench/calendar-activity-589.mjs
  • bench/run.sh
  • contracts/openapi.json
  • crates/plugins/calendar/src/items.rs
  • docs/perf/baseline.json
  • packages/api-client/src/generated.ts
  • packages/ui/src/components/FloatingSidebar.svelte
  • packages/ui/src/components/OverlaySurface.svelte
  • packages/ui/src/components/SegmentedControl.svelte
  • packages/ui/src/components/TabBar.svelte
  • packages/ui/src/components/calendar/ActivityStack.svelte
  • packages/ui/src/components/calendar/GridColumn.svelte
  • packages/ui/src/components/calendar/ItemPreview.svelte
  • packages/ui/src/components/calendar/TimeGrid.svelte
  • packages/ui/src/components/calendar/layout.ts
  • packages/ui/src/components/calendar/model.ts
  • packages/ui/src/components/menu/FloatingSurface.svelte
  • packages/ui/src/motion.ts
  • packages/ui/src/tokens.css
  • tests/adversarial/attack.py

Visual and performance evidence

The focused production E2E passed and all 24 screenshots are attached to this issue: Week, Day, Month and Added carousel at 390, 820 and 1440 px, in light and dark themes.

Exact focused E2E output:

calendar photo activity e2e: capture/upload dates, carousel, Quick Look, themes and screenshots passed

Local benchmark (the perf VM was not used; local host load average was 40.52, 40.78, 36.44):

  • Average case: 36 participants, 3,000 repetitions; p50 22.56 µs, p95 52.88 µs, CPU 0.1692 s, RSS 37,597,184 B before and 55,377,920 B peak.
  • Dense case: 61 participants including a 100-photo burst deck, 1,500 repetitions; p50 31.03 µs, p95 56.61 µs, CPU 0.0621 s, RSS 54,751,232 B before and 56,193,024 B peak.
  • There was no like-for-like activity-lane baseline. The existing 49.9 ms Calendar frame baseline measures a different path.

Gates

Rust gates ran after the last Rust change. Empty-output gates exited 0:

cargo fmt --check
cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings
cargo clippy -p calternal-server --all-targets -- -D warnings

Cargo test output excerpts:

test result: ok. 84 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out

Web check output:

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/calimg-589/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Web unit test output:

Test Files  148 passed (148)
     Tests  1018 passed (1018)
   Start at  21:45:52
   Duration  188.58s (transform 53%, environment 19%, import 14%, tests 10%, setup 3%)

bun run --cwd apps/web build exited 0. It printed the existing Rolldown “use client” directive warnings. E2E script and benchmark syntax checks plus git diff --check passed. cargo clean output: Removed 16123 files, 9.3GiB total; web build output was deleted.

Known gaps

  • The broader Calendar E2E still stops at the existing Daily Note repair fixture. The focused #589/#624 photo E2E passes.
  • The one local-server adversarial round ended with the server alive but reported non-SLOW Tags, DAV, Calendar cursor, concurrency and bookmark-search findings. They are recorded in the preceding issue comment for owner triage. The focused MCP probe also fails its exact tool-set assertion. The 120-photo and other SLOW-only burst results were classified as load.

Decisions where the design was silent

  • Set the smallest activity thumbnail to 20 CSS px; below that fit, render the count chip. Added decks require their label room inside the same lane.
  • Pack one upload-day Photo deck per day and navigate the set in date order in Quick Look. Capture role wins when capture and upload dates match.
  • Remove the legacy preference through a bounded settings-file cleanup during the normal preference read/update path; no SQL migration applies to this file-backed value.
Final report — #589 and #624 Head: eecd7b6b59e509084fe2b3530baa9bface3440b0 on job/calimg-589. No push, deploy or merge was made. ## Built - Photo and file activity now occupies the shared Calendar overlap layout with Events, Logs and Tasks. Thumbnail size follows the lane width and hour zoom. Bursts pack into decks; narrow lanes use accessible count chips. Month/Year stay compact and Agenda keeps its inline row. - Photos appear on capture and upload dates. The same-day row keeps the capture role. Upload dates show an Added deck and a bounded, keyboard-scrollable carousel; selecting a photo opens Quick Look over the Calendar. - Removed the Photo date preference from Calendar settings, browser preference writes, the API contract and generated client. Existing saved settings are cleaned on read and update. - Added the shared layout tests, the real 12-photo E2E path, screenshot matrix and a hot-path bench profile. ## Files - `apps/web/e2e/calendar-doc-stack.mjs` - `apps/web/e2e/calendar-perf.mjs` - `apps/web/e2e/calendar-view-switcher.mjs` - `apps/web/e2e/calendar.mjs` - `apps/web/e2e/layout-sweep.mjs` - `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/data.test.ts` - `apps/web/src/lib/calendar/data.ts` - `apps/web/src/lib/calendar/model.test.ts` - `apps/web/src/lib/calendar/prefs.test.ts` - `apps/web/src/lib/calendar/prefs.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/files/FilesSidebar.svelte` - `apps/web/src/lib/notifications/InboxPanel.svelte` - `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/calendars/CalendarsSection.svelte` - `bench/calendar-activity-589.mjs` - `bench/run.sh` - `contracts/openapi.json` - `crates/plugins/calendar/src/items.rs` - `docs/perf/baseline.json` - `packages/api-client/src/generated.ts` - `packages/ui/src/components/FloatingSidebar.svelte` - `packages/ui/src/components/OverlaySurface.svelte` - `packages/ui/src/components/SegmentedControl.svelte` - `packages/ui/src/components/TabBar.svelte` - `packages/ui/src/components/calendar/ActivityStack.svelte` - `packages/ui/src/components/calendar/GridColumn.svelte` - `packages/ui/src/components/calendar/ItemPreview.svelte` - `packages/ui/src/components/calendar/TimeGrid.svelte` - `packages/ui/src/components/calendar/layout.ts` - `packages/ui/src/components/calendar/model.ts` - `packages/ui/src/components/menu/FloatingSurface.svelte` - `packages/ui/src/motion.ts` - `packages/ui/src/tokens.css` - `tests/adversarial/attack.py` ## Visual and performance evidence The focused production E2E passed and all 24 screenshots are attached to this issue: Week, Day, Month and Added carousel at 390, 820 and 1440 px, in light and dark themes. Exact focused E2E output: ```text calendar photo activity e2e: capture/upload dates, carousel, Quick Look, themes and screenshots passed ``` Local benchmark (the perf VM was not used; local host load average was 40.52, 40.78, 36.44): - Average case: 36 participants, 3,000 repetitions; p50 22.56 µs, p95 52.88 µs, CPU 0.1692 s, RSS 37,597,184 B before and 55,377,920 B peak. - Dense case: 61 participants including a 100-photo burst deck, 1,500 repetitions; p50 31.03 µs, p95 56.61 µs, CPU 0.0621 s, RSS 54,751,232 B before and 56,193,024 B peak. - There was no like-for-like activity-lane baseline. The existing 49.9 ms Calendar frame baseline measures a different path. ## Gates Rust gates ran after the last Rust change. Empty-output gates exited 0: ```text cargo fmt --check cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings cargo clippy -p calternal-server --all-targets -- -D warnings ``` Cargo test output excerpts: ```text test result: ok. 84 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out ``` Web check output: ```text 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/calimg-589/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Web unit test output: ```text Test Files 148 passed (148) Tests 1018 passed (1018) Start at 21:45:52 Duration 188.58s (transform 53%, environment 19%, import 14%, tests 10%, setup 3%) ``` `bun run --cwd apps/web build` exited 0. It printed the existing Rolldown “use client” directive warnings. E2E script and benchmark syntax checks plus `git diff --check` passed. `cargo clean` output: `Removed 16123 files, 9.3GiB total`; web build output was deleted. ## Known gaps - The broader Calendar E2E still stops at the existing Daily Note repair fixture. The focused #589/#624 photo E2E passes. - The one local-server adversarial round ended with the server alive but reported non-SLOW Tags, DAV, Calendar cursor, concurrency and bookmark-search findings. They are recorded in the preceding issue comment for owner triage. The focused MCP probe also fails its exact tool-set assertion. The 120-photo and other SLOW-only burst results were classified as load. ## Decisions where the design was silent - Set the smallest activity thumbnail to 20 CSS px; below that fit, render the count chip. Added decks require their label room inside the same lane. - Pack one upload-day Photo deck per day and navigate the set in date order in Quick Look. Capture role wins when capture and upload dates match. - Remove the legacy preference through a bounded settings-file cleanup during the normal preference read/update path; no SQL migration applies to this file-backed value.
Author
Owner

Starting round 2 on branch job/calimg-589. Base SHA: 3f258302a0. I am checking the shared Activity layout, photo deck/card actions, and the #662 owned-path and text-card paths before editing.

Starting round 2 on branch job/calimg-589. Base SHA: 3f258302a0f2d6418ff60c9ce22cbb33e008ca99. I am checking the shared Activity layout, photo deck/card actions, and the #662 owned-path and text-card paths before editing.
Author
Owner

Round 2 finding: the supplied Day/Week screenshots show the grid thumbnail still tied to the current hour height (about 56 px in Day and 48 px in Week), so it never fills a wide lane. The Added label is placed in a lane-clipped button, and opens at the same time as the anchored preview. I will size the deck from its packed lane and free vertical span, keep the label intrinsic, and remove the redundant tooltip while the hover card owns the interaction.

Round 2 finding: the supplied Day/Week screenshots show the grid thumbnail still tied to the current hour height (about 56 px in Day and 48 px in Week), so it never fills a wide lane. The Added label is placed in a lane-clipped button, and opens at the same time as the anchored preview. I will size the deck from its packed lane and free vertical span, keep the label intrinsic, and remove the redundant tooltip while the hover card owns the interaction.
Author
Owner

Notes gate finding (#662)

cargo test -p calternal-plugin-notes completed with 166 passed and 1 failed. The existing tests::daily_and_composer_preserve_unrelated_bytes test received 404 instead of 200 from GET /journal/2026-09-24 at crates/plugins/notes/src/lib.rs:9423. This test constructs NotesPlugin.router directly, so it does not receive the new OwnedPathRegistry layer. No test expectation was changed. I am continuing the remaining gates and will report this result.

Notes gate finding (#662) `cargo test -p calternal-plugin-notes` completed with 166 passed and 1 failed. The existing `tests::daily_and_composer_preserve_unrelated_bytes` test received 404 instead of 200 from `GET /journal/2026-09-24` at `crates/plugins/notes/src/lib.rs:9423`. This test constructs `NotesPlugin.router` directly, so it does not receive the new `OwnedPathRegistry` layer. No test expectation was changed. I am continuing the remaining gates and will report this result.
Author
Owner

Finding: the existing full Calendar E2E has a stale Activity label expectation. GridColumn.svelte builds a 100-photo accessible label as 100 photos at 08:05, while apps/web/e2e/calendar.mjs still asserts 100 photos saved at 08:05 in its busy-day probe. I left that existing expectation unchanged per the owner rule. The focused Round 2 production proof passed, but it does not reach that busy-day assertion. This mismatch remains for the orchestrator to resolve.

Finding: the existing full Calendar E2E has a stale Activity label expectation. `GridColumn.svelte` builds a 100-photo accessible label as `100 photos at 08:05`, while `apps/web/e2e/calendar.mjs` still asserts `100 photos saved at 08:05` in its busy-day probe. I left that existing expectation unchanged per the owner rule. The focused Round 2 production proof passed, but it does not reach that busy-day assertion. This mismatch remains for the orchestrator to resolve.
Author
Owner

Final report — calimg-589 (#589, #624, #662)

Head SHA: 421dd6373. Work is committed on job/calimg-589. No push, deploy or merge was performed.

Built

  • Calendar Activity decks now size from their packed lane and free time, preserve the square image treatment, and stop at 160 px. Photo Added chips show the short count; the control keeps the full accessible name and hover card text.
  • The carousel counter spans the full track. Copy link, Open in Photos and Quick Look share the card action row. Open in Photos uses the stable Photo ID route. Keyboard arrows, Quick Look action, clicking a carousel Photo, and closing the overlay were exercised.
  • The #624 capture/upload date behavior is retained: a Photo appears on both dates, once if the dates are the same; the retired photo-time preference stays removed.
  • The #662 Calendar routes use the shared Plugin-owned path registry and Notes/Tasks indexes. Task source files, Daily notes, and registered Plugin Markdown do not become generic file Activity. The shared text-card renderer removes YAML frontmatter; an incomplete leading frontmatter block at the 8 KiB read limit stays hidden.
  • The production E2E proof creates and edits a Task and Note, writes Markdown under Money, waits for Files indexing, and checks typed Calendar rows.

UX gaps closed

  • Images scale to the lane and available time while Events, Tasks and Logs still use the shared overlap layout.
  • “12 Added” clipping is removed; a count chip fits its text.
  • The hover card has no visible detached duplicate warm tooltip.
  • Copy link, Open in Photos, Quick Look, keyboard carousel navigation and clicking a Photo were checked. The shared PillGroup supplies the 44 px coarse-pointer hit area for card actions. Dense time-grid targets keep the documented essential-presentation exception and have the Agenda row alternative.
  • The screenshot matrix covers Week, Day, Month and carousel at 390, 820 and 1440 px in both themes; all 24 current screenshots are attached below.

UX gaps left / known gaps

  • cargo test -p calternal-plugin-notes has one existing failure: daily_and_composer_preserve_unrelated_bytes got HTTP 404 where it expects 200 for GET /journal/2026-09-24 when the Notes router runs without the PluginRegistry extension. The expectation was not changed. This finding is also in the issue thread.
  • The full Calendar E2E has a pre-existing mismatch: its busy-day assertion expects 100 photos saved at 08:05, while GridColumn.svelte returns 100 photos at 08:05. I left that expectation unchanged and reported it. The focused production proof passed; it does not reach that assertion.
  • No like-for-like performance baseline exists for the combined lane-packing plus thumbnail-sizing profile; the stored baseline measures the packer alone.

Files

  • crates/calternal-plugin/src/lib.rs
  • crates/calternal-server/src/system_plugin.rs
  • crates/plugins/calendar/src/items.rs
  • crates/plugins/calendar/src/view.rs
  • crates/plugins/files/src/thumbnails.rs
  • crates/plugins/money/src/lib.rs
  • crates/plugins/notes/src/lib.rs
  • crates/plugins/photos/src/lib.rs
  • apps/web/e2e/calendar.mjs
  • apps/web/src/lib/calendar/ItemPreview.svelte.test.ts
  • apps/web/src/lib/calendar/data.test.ts
  • apps/web/src/lib/calendar/data.ts
  • apps/web/src/lib/calendar/model.test.ts
  • apps/web/src/routes/calendar/[view]/[date]/+page.svelte
  • packages/ui/src/components/calendar/GridColumn.svelte
  • packages/ui/src/components/calendar/ItemPreview.svelte
  • packages/ui/src/components/calendar/layout.ts
  • tests/adversarial/calendar_event_tags.mjs
  • bench/calendar-activity-589.mjs

Gate output

cargo fmt --check
git diff --check origin/dev...HEAD
# both exited 0 with no output

cargo clippy -p calternal-plugin --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.91s
cargo test -p calternal-plugin
test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.61s

cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 34s
cargo test -p calternal-plugin-calendar
84 passed; 0 failed
1 passed; 0 failed
3 passed; 0 failed

cargo clippy -p calternal-plugin-files --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 40s
cargo test -p calternal-plugin-files
test result: ok. 148 passed; 0 failed; 1 ignored

cargo clippy -p calternal-plugin-money --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 24.25s
cargo test -p calternal-plugin-money
24 passed; 0 failed

cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 35s
cargo test -p calternal-plugin-notes
166 passed; 1 failed
daily_and_composer_preserve_unrelated_bytes: GET /api/v1/notes/journal/2026-09-24 returned 404; expected 200

cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 30s
cargo test -p calternal-plugin-photos
test result: ok. 46 passed; 0 failed; 2 ignored

cargo clippy -p calternal-server --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 55s
cargo test -p calternal-server
test result: ok. 107 passed; 0 failed; 3 ignored

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

bun run test
Test Files  153 passed (153)
Tests  1062 passed (1062)
Duration  78.59s

bun run test -- src/lib/calendar/model.test.ts
Test Files  1 passed (1)
Tests  33 passed (33)

bun run test -- src/lib/calendar/ItemPreview.svelte.test.ts
Test Files  1 passed (1)
Tests  11 passed (11)

bun e2e/calendar.mjs
calendar activity e2e: responsive Photo decks and typed Markdown passed

Live adversarial probe output:

Journal reconcile burst: n=20 p50=94.8 ms p95=100.0 ms
Calendar probe: Event tag Unicode/bidi and 65536-byte input; #536 drag preferences and timed Task hostile/auth/concurrent writes; Journal burst and 24 parallel tag/range/search reads; Calendar/Notes/Files Server-Timing passed

The probe also attacked Activity item dates, Unicode dates, timezone, limits, cursors and a 24-request valid/invalid burst; it returned no 5xx and left the server healthy.

Production build output ended with:

Wrote site to "build"
✔ done

cargo clean output:

Removed 20420 files, 12.5GiB total

Local Activity benchmark

This local run profiles the packer plus the new sizing helpers at source SHA e5e1aaacc; later commits only updated tests and comments. The #589 baseline in docs/perf/baseline.json profiles the packer alone, so treat the comparison as scope-changed, not a regression measurement.

Profile Baseline p50 / p95 This run p50 / p95 Baseline CPU This run CPU Baseline RSS growth This run RSS growth
Average (36 participants) 22.56 / 52.88 µs 34.03 / 81.59 µs 0.1692 s 0.2203 s 17,780,736 B 17,485,824 B
Burst (61 participants, 100-photo stack) 31.03 / 56.61 µs 49.24 / 81.38 µs 0.0621 s 0.0911 s 1,441,792 B 1,310,720 B

Host load average stayed at 7.19, 12.83, 17.63 before and after. The baseline run's host load was 40.52, 40.78, 36.44. Peak RSS this run was 56,385,536 B average and 57,208,832 B burst.

Decisions not specified by DESIGN

  • Used a 160 px maximum image edge. Added Photos reserve 38 px for the count chip when a 56 px lane can show it; use the compact count form when the lane is below 56 px or the rendered image is below 44 px.
  • For Plugin-owned Markdown detection, combine the registry path with MIME text/markdown or the .md / .markdown extension. Declared roots are .calternal/, Money/, Tasks/ and Photos/.
  • Hide the whole thumbnail excerpt when the 8 KiB read begins a YAML frontmatter block without its closing fence. This avoids exposing partial metadata.
  • The retired Photo time choice remains removed as decided in #624; Calendar uses one request zone through each range and its item pages.

Screenshot matrix attached to #589

View 390 light 390 dark 820 light 820 dark 1440 light 1440 dark
week PNG PNG PNG PNG PNG PNG
day PNG PNG PNG PNG PNG PNG
month PNG PNG PNG PNG PNG PNG
carousel PNG PNG PNG PNG PNG PNG
## Final report — calimg-589 (#589, #624, #662) Head SHA: `421dd6373`. Work is committed on `job/calimg-589`. No push, deploy or merge was performed. ### Built - Calendar Activity decks now size from their packed lane and free time, preserve the square image treatment, and stop at 160 px. Photo Added chips show the short count; the control keeps the full accessible name and hover card text. - The carousel counter spans the full track. Copy link, Open in Photos and Quick Look share the card action row. Open in Photos uses the stable Photo ID route. Keyboard arrows, Quick Look action, clicking a carousel Photo, and closing the overlay were exercised. - The #624 capture/upload date behavior is retained: a Photo appears on both dates, once if the dates are the same; the retired photo-time preference stays removed. - The #662 Calendar routes use the shared Plugin-owned path registry and Notes/Tasks indexes. Task source files, Daily notes, and registered Plugin Markdown do not become generic file Activity. The shared text-card renderer removes YAML frontmatter; an incomplete leading frontmatter block at the 8 KiB read limit stays hidden. - The production E2E proof creates and edits a Task and Note, writes Markdown under Money, waits for Files indexing, and checks typed Calendar rows. ### UX gaps closed - Images scale to the lane and available time while Events, Tasks and Logs still use the shared overlap layout. - “12 Added” clipping is removed; a count chip fits its text. - The hover card has no visible detached duplicate warm tooltip. - Copy link, Open in Photos, Quick Look, keyboard carousel navigation and clicking a Photo were checked. The shared PillGroup supplies the 44 px coarse-pointer hit area for card actions. Dense time-grid targets keep the documented essential-presentation exception and have the Agenda row alternative. - The screenshot matrix covers Week, Day, Month and carousel at 390, 820 and 1440 px in both themes; all 24 current screenshots are attached below. ### UX gaps left / known gaps - `cargo test -p calternal-plugin-notes` has one existing failure: `daily_and_composer_preserve_unrelated_bytes` got HTTP 404 where it expects 200 for GET `/journal/2026-09-24` when the Notes router runs without the PluginRegistry extension. The expectation was not changed. This finding is also in the issue thread. - The full Calendar E2E has a pre-existing mismatch: its busy-day assertion expects `100 photos saved at 08:05`, while `GridColumn.svelte` returns `100 photos at 08:05`. I left that expectation unchanged and reported it. The focused production proof passed; it does not reach that assertion. - No like-for-like performance baseline exists for the combined lane-packing plus thumbnail-sizing profile; the stored baseline measures the packer alone. ### Files - `crates/calternal-plugin/src/lib.rs` - `crates/calternal-server/src/system_plugin.rs` - `crates/plugins/calendar/src/items.rs` - `crates/plugins/calendar/src/view.rs` - `crates/plugins/files/src/thumbnails.rs` - `crates/plugins/money/src/lib.rs` - `crates/plugins/notes/src/lib.rs` - `crates/plugins/photos/src/lib.rs` - `apps/web/e2e/calendar.mjs` - `apps/web/src/lib/calendar/ItemPreview.svelte.test.ts` - `apps/web/src/lib/calendar/data.test.ts` - `apps/web/src/lib/calendar/data.ts` - `apps/web/src/lib/calendar/model.test.ts` - `apps/web/src/routes/calendar/[view]/[date]/+page.svelte` - `packages/ui/src/components/calendar/GridColumn.svelte` - `packages/ui/src/components/calendar/ItemPreview.svelte` - `packages/ui/src/components/calendar/layout.ts` - `tests/adversarial/calendar_event_tags.mjs` - `bench/calendar-activity-589.mjs` ### Gate output ``` cargo fmt --check git diff --check origin/dev...HEAD # both exited 0 with no output cargo clippy -p calternal-plugin --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.91s cargo test -p calternal-plugin test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.61s cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 34s cargo test -p calternal-plugin-calendar 84 passed; 0 failed 1 passed; 0 failed 3 passed; 0 failed cargo clippy -p calternal-plugin-files --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 40s cargo test -p calternal-plugin-files test result: ok. 148 passed; 0 failed; 1 ignored cargo clippy -p calternal-plugin-money --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 24.25s cargo test -p calternal-plugin-money 24 passed; 0 failed cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 35s cargo test -p calternal-plugin-notes 166 passed; 1 failed daily_and_composer_preserve_unrelated_bytes: GET /api/v1/notes/journal/2026-09-24 returned 404; expected 200 cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 30s cargo test -p calternal-plugin-photos test result: ok. 46 passed; 0 failed; 2 ignored cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 55s cargo test -p calternal-server test result: ok. 107 passed; 0 failed; 3 ignored bun run check svelte-check found 0 errors and 0 warnings bun run test Test Files 153 passed (153) Tests 1062 passed (1062) Duration 78.59s bun run test -- src/lib/calendar/model.test.ts Test Files 1 passed (1) Tests 33 passed (33) bun run test -- src/lib/calendar/ItemPreview.svelte.test.ts Test Files 1 passed (1) Tests 11 passed (11) bun e2e/calendar.mjs calendar activity e2e: responsive Photo decks and typed Markdown passed ``` Live adversarial probe output: ``` Journal reconcile burst: n=20 p50=94.8 ms p95=100.0 ms Calendar probe: Event tag Unicode/bidi and 65536-byte input; #536 drag preferences and timed Task hostile/auth/concurrent writes; Journal burst and 24 parallel tag/range/search reads; Calendar/Notes/Files Server-Timing passed ``` The probe also attacked Activity item dates, Unicode dates, timezone, limits, cursors and a 24-request valid/invalid burst; it returned no 5xx and left the server healthy. Production build output ended with: ``` Wrote site to "build" ✔ done ``` `cargo clean` output: ``` Removed 20420 files, 12.5GiB total ``` ### Local Activity benchmark This local run profiles the packer plus the new sizing helpers at source SHA `e5e1aaacc`; later commits only updated tests and comments. The #589 baseline in `docs/perf/baseline.json` profiles the packer alone, so treat the comparison as scope-changed, not a regression measurement. | Profile | Baseline p50 / p95 | This run p50 / p95 | Baseline CPU | This run CPU | Baseline RSS growth | This run RSS growth | |---|---:|---:|---:|---:|---:|---:| | Average (36 participants) | 22.56 / 52.88 µs | 34.03 / 81.59 µs | 0.1692 s | 0.2203 s | 17,780,736 B | 17,485,824 B | | Burst (61 participants, 100-photo stack) | 31.03 / 56.61 µs | 49.24 / 81.38 µs | 0.0621 s | 0.0911 s | 1,441,792 B | 1,310,720 B | Host load average stayed at 7.19, 12.83, 17.63 before and after. The baseline run's host load was 40.52, 40.78, 36.44. Peak RSS this run was 56,385,536 B average and 57,208,832 B burst. ### Decisions not specified by DESIGN - Used a 160 px maximum image edge. Added Photos reserve 38 px for the count chip when a 56 px lane can show it; use the compact count form when the lane is below 56 px or the rendered image is below 44 px. - For Plugin-owned Markdown detection, combine the registry path with MIME `text/markdown` or the `.md` / `.markdown` extension. Declared roots are `.calternal/`, `Money/`, `Tasks/` and `Photos/`. - Hide the whole thumbnail excerpt when the 8 KiB read begins a YAML frontmatter block without its closing fence. This avoids exposing partial metadata. - The retired Photo time choice remains removed as decided in #624; Calendar uses one request zone through each range and its item pages. ### Screenshot matrix attached to #589 | View | 390 light | 390 dark | 820 light | 820 dark | 1440 light | 1440 dark | |---|---|---|---|---|---|---| | week | [PNG](https://git.kayg.org/attachments/94c36969-0c75-4189-b8fa-cee45c6968b7) | [PNG](https://git.kayg.org/attachments/b83029bf-0784-4f4d-a375-1f995ce13e30) | [PNG](https://git.kayg.org/attachments/e627969d-4cbd-4dd6-8bf5-4a465ccf79f5) | [PNG](https://git.kayg.org/attachments/76af405b-ce45-49a3-9262-f56641182e7c) | [PNG](https://git.kayg.org/attachments/82174fbd-2c53-4896-abcf-6cef32b0178e) | [PNG](https://git.kayg.org/attachments/b09ab2c1-0f5a-458c-bc2d-980370a4308d) | | day | [PNG](https://git.kayg.org/attachments/3c7ec6b7-602c-4feb-b640-f72d930d8383) | [PNG](https://git.kayg.org/attachments/a6595056-0e79-4e62-8832-56bbd8609b8e) | [PNG](https://git.kayg.org/attachments/e6381ffd-8db6-4f6a-a068-76bbb71912ae) | [PNG](https://git.kayg.org/attachments/7e0d934a-399e-4a79-9cd3-cb42aae3817f) | [PNG](https://git.kayg.org/attachments/ffb97e61-b80f-427f-85b5-b55727309b97) | [PNG](https://git.kayg.org/attachments/d739869b-82f9-4029-b081-1f2dfcf76c22) | | month | [PNG](https://git.kayg.org/attachments/1a9e141d-c461-4d54-8bcf-2efc59dc0ecf) | [PNG](https://git.kayg.org/attachments/fce13498-2444-40ce-bcdc-a95a89cdf8a1) | [PNG](https://git.kayg.org/attachments/34a2f929-c011-401e-8096-5bbe6b73eae6) | [PNG](https://git.kayg.org/attachments/41bd226e-1168-4c56-8483-b95c07a642ac) | [PNG](https://git.kayg.org/attachments/13bd61e6-7fbb-486e-a0b3-6196fd8b1f23) | [PNG](https://git.kayg.org/attachments/9f7f7e96-c198-45e9-8897-233d932b737a) | | carousel | [PNG](https://git.kayg.org/attachments/96a0fb28-f775-4d3d-b141-973072ce8869) | [PNG](https://git.kayg.org/attachments/fa81f459-1533-433f-a70e-97d49b9068b8) | [PNG](https://git.kayg.org/attachments/b2a0e304-a1f2-42ab-91c4-686b259f4699) | [PNG](https://git.kayg.org/attachments/6e8390fe-3df0-4874-a7cf-794fb491c65a) | [PNG](https://git.kayg.org/attachments/9e48362a-457e-46da-b9c3-53586798646f) | [PNG](https://git.kayg.org/attachments/83dbe3f8-fa96-46d2-911a-f4e6257b2f60) |
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#589
No description provided.