Photos: saved tags are missing from the item API #1370

Closed
opened 2026-10-07 14:14:28 +00:00 by kayg · 21 comments
Owner

Steps:

  1. Open a photo in Photos.
  2. Open Tags, add #paris and trip/lisbon 2026, then save.
  3. Read /api/v1/tags/items/<photo path>.

Expected: The item endpoint returns both saved tags.

Actual: The production Playwright flow reads an empty tags array immediately after Save. The UI uses a drop-policy toast for the successful background result, so the normal toast assertion does not provide confirmation.

Evidence: artifacts/bash-files/photos/photos-tags-not-persisted-actual.png.

Severity: major.

Steps: 1. Open a photo in Photos. 2. Open Tags, add `#paris` and `trip/lisbon 2026`, then save. 3. Read `/api/v1/tags/items/<photo path>`. Expected: The item endpoint returns both saved tags. Actual: The production Playwright flow reads an empty `tags` array immediately after Save. The UI uses a drop-policy toast for the successful background result, so the normal toast assertion does not provide confirmation. Evidence: `artifacts/bash-files/photos/photos-tags-not-persisted-actual.png`. Severity: major.
Author
Owner

Round 3 reproduction of existing #1370 in photos-round3.mjs (real local production server, 45 seeded photos over 11 days, macOS-emulated Playwright): open the Photos viewer, set #paris and trip/lisbon 2026, then Save. The UI did not show the expected saved confirmation IMG_1003.JPG now has #paris #trip/lisbon-2026 within 30 seconds. This is the already-filed photo tag persistence/API issue; no duplicate issue opened. Evidence: artifacts/bash3-filesphotos/photos-tags-save-known-failure.png.

Round 3 reproduction of existing #1370 in `photos-round3.mjs` (real local production server, 45 seeded photos over 11 days, macOS-emulated Playwright): open the Photos viewer, set `#paris` and `trip/lisbon 2026`, then Save. The UI did not show the expected saved confirmation `IMG_1003.JPG now has #paris #trip/lisbon-2026` within 30 seconds. This is the already-filed photo tag persistence/API issue; no duplicate issue opened. Evidence: `artifacts/bash3-filesphotos/photos-tags-save-known-failure.png`.
Author
Owner

Starting #1370 work.

Branch: job/phototags-1370
Worktree HEAD at start: d395b6e25da20d8c67b8b9fc1a593b813939c8a7
origin/dev observed at start: af202e1514955ddf0f8b4b3d2503c780d807ed84 (the worktree is 15 commits behind; I will merge origin/dev once before final gates).

Starting #1370 work. Branch: `job/phototags-1370` Worktree HEAD at start: `d395b6e25da20d8c67b8b9fc1a593b813939c8a7` `origin/dev` observed at start: `af202e1514955ddf0f8b4b3d2503c780d807ed84` (the worktree is 15 commits behind; I will merge `origin/dev` once before final gates).
Author
Owner

Finding from source trace: GET /api/v1/photos/items/{item_id} serializes PhotoItem without Tags (crates/plugins/photos/src/routes.rs), the Photos Inspector loads Tags through a separate path request (apps/web/src/lib/files/InfoPanel.svelte), and Search preview loads only the Files item (apps/web/src/lib/search/SearchPreview.svelte). The Photos item route has both the stored owner and current viewer, so the shared response can return Tags only when those identities match. I will use that response in the Inspector and Photo Search preview; a Share recipient will receive an empty Tag list.

Finding from source trace: `GET /api/v1/photos/items/{item_id}` serializes `PhotoItem` without Tags (`crates/plugins/photos/src/routes.rs`), the Photos Inspector loads Tags through a separate path request (`apps/web/src/lib/files/InfoPanel.svelte`), and Search preview loads only the Files item (`apps/web/src/lib/search/SearchPreview.svelte`). The Photos item route has both the stored owner and current viewer, so the shared response can return Tags only when those identities match. I will use that response in the Inspector and Photo Search preview; a Share recipient will receive an empty Tag list.
Author
Owner

Performance review finding: scripts/perf-lint --check initially rejected the existing InfoPanel list pins because the source file hash changed. The new Photo item query uses the existing (owner_id, item_kind, item_id) Tags index. The source analyzer also found unresolved SQLx call sites in the new handler; I am keeping the query owner-scoped and indexed, and the focused profile will measure two Tags and the Assign Tags API's 200-value limit. Search preview's first draft added a per-Tag template loop without a surface contract, so I replaced it with the existing detail-text treatment that displays all Tags without another unbounded render loop. I am refreshing the exact live pins and rerunning the guard.

Performance review finding: `scripts/perf-lint --check` initially rejected the existing InfoPanel list pins because the source file hash changed. The new Photo item query uses the existing `(owner_id, item_kind, item_id)` Tags index. The source analyzer also found unresolved SQLx call sites in the new handler; I am keeping the query owner-scoped and indexed, and the focused profile will measure two Tags and the Assign Tags API's 200-value limit. Search preview's first draft added a per-Tag template loop without a surface contract, so I replaced it with the existing detail-text treatment that displays all Tags without another unbounded render loop. I am refreshing the exact live pins and rerunning the guard.
Author
Owner

The production browser pass reached the Quick Look Tag editor and closed it after Save, but timed out waiting for the exact success-toast text IMG_1370.JPG now has #paris #trip/lisbon-2026 (30 s). The failure screenshot shows the real Photo still open. I am changing the browser assertion to wait on the persisted Tags API response, then will verify the Inspector and cold reload against that state. This isolates persistence from transient toast copy.

The production browser pass reached the Quick Look Tag editor and closed it after Save, but timed out waiting for the exact success-toast text `IMG_1370.JPG now has #paris #trip/lisbon-2026` (30 s). The failure screenshot shows the real Photo still open. I am changing the browser assertion to wait on the persisted Tags API response, then will verify the Inspector and cold reload against that state. This isolates persistence from transient toast copy.
Author
Owner

The browser flow now verifies saved Tags through the API, and reached the screenshot matrix. It exposed a second test sequencing issue: Escape closes the whole Quick Look surface (as QuickLook.svelte specifies), so the following i had no viewer to open. I will close only the Inspector with its existing Show info control, then invoke i while Quick Look remains open.

The browser flow now verifies saved Tags through the API, and reached the screenshot matrix. It exposed a second test sequencing issue: Escape closes the whole Quick Look surface (as `QuickLook.svelte` specifies), so the following `i` had no viewer to open. I will close only the Inspector with its existing Show info control, then invoke `i` while Quick Look remains open.
Author
Owner

The matrix confirmed the Inspector scrim intercepts the Show info button while it is open, so the pointer close cannot run. The earlier Escape sequence closes Quick Look itself. I will use the viewer's existing i shortcut to close and reopen the Inspector, then capture the reopened state. This preserves the intended pointer and keyboard evidence without bypassing the UI.

The matrix confirmed the Inspector scrim intercepts the Show info button while it is open, so the pointer close cannot run. The earlier Escape sequence closes Quick Look itself. I will use the viewer's existing `i` shortcut to close and reopen the Inspector, then capture the reopened state. This preserves the intended pointer and keyboard evidence without bypassing the UI.
Author
Owner

The Inspector owns focus while it is open, so sending i does not reach Quick Look's shortcut handler. The underlying Show info button is also behind the Inspector scrim. I will close through the Inspector's own “Close Info” control, which is inside the surface, then test the i opening shortcut from the restored Quick Look focus.

The Inspector owns focus while it is open, so sending `i` does not reach Quick Look's shortcut handler. The underlying Show info button is also behind the Inspector scrim. I will close through the Inspector's own “Close Info” control, which is inside the surface, then test the `i` opening shortcut from the restored Quick Look focus.
Author
Owner

The actual close control is named Close IMG_1370.JPG: the shared Inspector uses the inspected item name as its dialog title and “Info” as its dialog label. The “Close Info” locator therefore never matched. I will target the accessible item-specific close name, then continue the keyboard-opening check.

The actual close control is named `Close IMG_1370.JPG`: the shared Inspector uses the inspected item name as its dialog title and “Info” as its dialog label. The “Close Info” locator therefore never matched. I will target the accessible item-specific close name, then continue the keyboard-opening check.
Author
Owner

The latest run captured both Inspector states in both themes at 1440 px and 820 px. At 390 px, the Inspector is a nested phone sheet; closing it does not restore focus to the Quick Look Info button, so i is not delivered to the viewer. I will quietly restore focus to that shared button before the shortcut, using focus({ focusVisible: false }), then capture the phone keyboard state.

The latest run captured both Inspector states in both themes at 1440 px and 820 px. At 390 px, the Inspector is a nested phone sheet; closing it does not restore focus to the Quick Look Info button, so `i` is not delivered to the viewer. I will quietly restore focus to that shared button before the shortcut, using `focus({ focusVisible: false })`, then capture the phone keyboard state.
Author
Owner

The production browser flow now completes the Inspector matrix, but the cold-reload assertion found inconsistent API Tags. Immediately after the Tag editor saves paris and trip/lisbon-2026, the Tags API and Photo item API both return those two Tags. After reloading the deep link, the item API returns an additional trip prefix Tag. I am tracing the write/index/read path and will keep the regression assertion.

The production browser flow now completes the Inspector matrix, but the cold-reload assertion found inconsistent API Tags. Immediately after the Tag editor saves `paris` and `trip/lisbon-2026`, the Tags API and Photo item API both return those two Tags. After reloading the deep link, the item API returns an additional `trip` prefix Tag. I am tracing the write/index/read path and will keep the regression assertion.
Author
Owner

The extra trip is a hierarchical parent Tag from the Photo's XMP hierarchicalSubject source; calternal-tags deliberately indexes hierarchy-only parent Tags during reconciliation. The immediate assignment response and immediate item response precede that source rescan. I will keep exact checks for the requested assignment, then after reload compare the item API with the Tags API and assert both requested Tags remain present.

The extra `trip` is a hierarchical parent Tag from the Photo's XMP `hierarchicalSubject` source; `calternal-tags` deliberately indexes hierarchy-only parent Tags during reconciliation. The immediate assignment response and immediate item response precede that source rescan. I will keep exact checks for the requested assignment, then after reload compare the item API with the Tags API and assert both requested Tags remain present.
Author
Owner

The 390 px run still cannot open the Inspector with i. In Quick Look, the phone action bar is a sibling of .ql, but the shortcut handler is attached only to .ql; desktop and tablet actions sit inside .ql. I will extend the shared Quick Look key handler to dispatch shortcuts from focused controls inside its own dialog while leaving nested Inspector/Share dialogs in control. This keeps the advertised shortcut working on the phone sheet as well.

The 390 px run still cannot open the Inspector with `i`. In Quick Look, the phone action bar is a sibling of `.ql`, but the shortcut handler is attached only to `.ql`; desktop and tablet actions sit inside `.ql`. I will extend the shared Quick Look key handler to dispatch shortcuts from focused controls inside its own dialog while leaving nested Inspector/Share dialogs in control. This keeps the advertised shortcut working on the phone sheet as well.
Author
Owner

All 12 Inspector screenshots now capture, including the phone keyboard shortcut after the shared Quick Look fix. Search's real API result includes the Photo, but the production Search Preview says the Photo is no longer in the Photos library. I am checking the Search provider identity against the Photo item endpoint before changing the preview path.

All 12 Inspector screenshots now capture, including the phone keyboard shortcut after the shared Quick Look fix. Search's real API result includes the Photo, but the production Search Preview says the Photo is no longer in the Photos library. I am checking the Search provider identity against the Photo item endpoint before changing the preview path.
Author
Owner

The Search hit is from the Files provider: it has kind photo, an href under /files?path=…, and an ID that is a User-scoped path, not a Photos item ID. The Photos item API correctly returns 404 for that path string. SearchPreview must resolve the Files hit with the indexed Files stat endpoint, then load PhotoItem by its stable item_id; direct Photos provider and /p/<id> links keep their current fast path. I will cover both identities in the focused test and browser flow.

The Search hit is from the Files provider: it has kind `photo`, an href under `/files?path=…`, and an ID that is a User-scoped path, not a Photos item ID. The Photos item API correctly returns 404 for that path string. SearchPreview must resolve the Files hit with the indexed Files stat endpoint, then load PhotoItem by its stable `item_id`; direct Photos provider and `/p/<id>` links keep their current fast path. I will cover both identities in the focused test and browser flow.
Author
Owner

The production Search Preview now receives PhotoItem (HTTP 200) and visibly shows #paris, #trip, and #trip/lisbon-2026. The result row is image-only and exposes its title through aria-label; the browser test filtered by visible text and therefore missed the rendered option. I will select the result by its accessible name and rerun the captures.

The production Search Preview now receives PhotoItem (HTTP 200) and visibly shows `#paris`, `#trip`, and `#trip/lisbon-2026`. The result row is image-only and exposes its title through `aria-label`; the browser test filtered by visible text and therefore missed the rendered option. I will select the result by its accessible name and rerun the captures.
Author
Owner

The tablet run got a PhotoItem 200 response but searched for a phone Preview sheet. Search uses a split preview pane from 768 px (the tablet is 820 px), so this was the wrong interaction path. I will hover the tablet result and inspect the shared preview pane; only the 390 px phone uses the touch Preview sheet.

The tablet run got a PhotoItem 200 response but searched for a phone Preview sheet. Search uses a split preview pane from 768 px (the tablet is 820 px), so this was the wrong interaction path. I will hover the tablet result and inspect the shared preview pane; only the 390 px phone uses the touch Preview sheet.
Author
Owner

Browser evidence: the Photo Tags route test verifies the two authored Tags before reload. After reload, XMP hierarchy reconciliation also indexes the parent trip, so the real item API and Tags API return the same three Tags. The browser benchmark initially assumed the count stayed at two and failed at the first profile request. I updated the average profile to validate and report the actual post-reload count, while retaining the explicit two-authored-Tag assertion before reload.

Browser evidence: the Photo Tags route test verifies the two authored Tags before reload. After reload, XMP hierarchy reconciliation also indexes the parent `trip`, so the real item API and Tags API return the same three Tags. The browser benchmark initially assumed the count stayed at two and failed at the first profile request. I updated the average profile to validate and report the actual post-reload count, while retaining the explicit two-authored-Tag assertion before reload.
Author
Owner

Completed

Head: 6806304f89cc681ffbab596b460f74ea2f8eb0d3 on job/phototags-1370 (not pushed).

The Photos item route now returns Tags from the shared Tag Index for the owner. A Share recipient receives no Tags. The Photos Inspector passes its loaded PhotoItem Tags to the existing shared InfoPanel/TagEditor. Search Photo previews load the same PhotoItem response; Files-provider results resolve their stable item ID through indexed Files stat() first. Deep-link reloads therefore use the same owner-scoped data.

The route test covers owner Tags and Share-recipient privacy. The real-browser flow uploads and tags a Photo, reloads it, checks the Inspector and item API, and checks Search previews at 1440, 820 and 390 px. The Quick Look keyboard handler now also receives shortcuts from its phone action bar while nested Inspector and Share surfaces retain their keys.

Decisions not covered by DESIGN.md

  • Search shows Photo Tags as a text line in the existing preview surface. It uses the shared ItemCard and does not add a new tag style.
  • A Files-provider Photo result uses one indexed Files stat() plus one Photos item request per preview. A Photos-provider result uses one Photos item request. Replaced previews abort their requests; no Home scan runs.
  • XMP hierarchy reconciliation can add a parent Tag after reload. The test checks the two authored Tags before reload, then checks that the item API and Tags API agree afterward. The measured average item has three Tags.

Verification

$ cargo fmt --check
(no output; exit 0)
$ cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 9m 28s
$ cargo test -p calternal-plugin-photos
test result: ok. 59 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 8.01s
$ cargo clippy -p calternal-server --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 42s
$ cargo test -p calternal-server
test result: ok. 272 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 211.10s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 58.05s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
$ bun run check
perf-lint: PASS; 0 violations; 22557 scoped exceptions
svelte-check found 0 errors and 4 warnings in 2 files
$ bunx vitest run src/lib/search/SearchPreview.svelte.test.ts src/lib/files/InfoPanel.svelte.test.ts --maxWorkers=2
 Test Files  2 passed (2)
      Tests  23 passed (23)
$ CALTERNAL_E2E_ASSET_OVERRIDE=1 bun e2e/photos.mjs --tags-only --screenshots <artifacts>
PHOTOS TAGS REGRESSION PASSED
CSP REPORTS photos: 0 across 3 pages

The four Svelte warnings are in the existing Mail and Money styles. The full web suite also passed earlier in this job: Test Files 297 passed (297) and Tests 2121 passed (2121). Per the current verification policy, the merge round should run the full suite on the combined branch.

Local performance profile

No comparable feature baseline existed. The local production-browser profile ran on calternal-dev with load average 18.75/18/20.84; treat it as a reference only. docs/perf/baseline.json records the measurements.

  • Average: 40 serial requests, 3 Tags per item; p50 22.3 ms, p95 41.2 ms; mean server CPU 32.02%, mean RSS 241,596,514 bytes.
  • Worst-case burst: 24 concurrent requests, 200 Tags per item; p50 215.1 ms, p95 322.3 ms; mean server CPU 73.33%, peak RSS 242,941,952 bytes.

Screenshots

18 actual-size Mac-emulated PNGs are attached: 12 Inspector captures (mouse and keyboard, three widths, two themes) and 6 Search Preview captures (three widths, two themes). No screenshots are committed.

Inspector: 1440 paper mouse, 1440 dark mouse, 820 paper mouse, 820 dark mouse, 390 paper mouse, 390 dark mouse; 1440 paper keyboard, 1440 dark keyboard, 820 paper keyboard, 820 dark keyboard, 390 paper keyboard, 390 dark keyboard.

Search Preview: 1440 paper, 1440 dark, 820 paper, 820 dark, 390 paper, 390 dark.

UX gaps closed: Tags remain visible after reload, Search previews read the shared item API, and the phone Quick Look action bar routes shortcuts through the viewer handler. UX gaps left: none known.

Files changed: crates/plugins/photos/src/routes.rs, contracts/openapi.json, packages/api-client/src/generated.ts, apps/web/src/lib/files/InfoPanel.svelte, apps/web/src/lib/files/api.ts, apps/web/src/lib/photos/PhotoViewer.svelte, apps/web/src/lib/photos/api.ts, apps/web/src/lib/search/SearchPreview.svelte, apps/web/src/lib/search/SearchPreview.svelte.test.ts, apps/web/e2e/photos.mjs, packages/ui/src/components/viewer/QuickLook.svelte, bench/photo-item-tags-1370.sh, docs/perf/baseline.json, contracts/perf/adoption-1058.json, contracts/perf/exceptions.json, contracts/perf/ratchet.json.

## Completed Head: `6806304f89cc681ffbab596b460f74ea2f8eb0d3` on `job/phototags-1370` (not pushed). The Photos item route now returns Tags from the shared Tag Index for the owner. A Share recipient receives no Tags. The Photos Inspector passes its loaded PhotoItem Tags to the existing shared InfoPanel/TagEditor. Search Photo previews load the same PhotoItem response; Files-provider results resolve their stable item ID through indexed Files `stat()` first. Deep-link reloads therefore use the same owner-scoped data. The route test covers owner Tags and Share-recipient privacy. The real-browser flow uploads and tags a Photo, reloads it, checks the Inspector and item API, and checks Search previews at 1440, 820 and 390 px. The Quick Look keyboard handler now also receives shortcuts from its phone action bar while nested Inspector and Share surfaces retain their keys. ## Decisions not covered by DESIGN.md - Search shows Photo Tags as a text line in the existing preview surface. It uses the shared ItemCard and does not add a new tag style. - A Files-provider Photo result uses one indexed Files `stat()` plus one Photos item request per preview. A Photos-provider result uses one Photos item request. Replaced previews abort their requests; no Home scan runs. - XMP hierarchy reconciliation can add a parent Tag after reload. The test checks the two authored Tags before reload, then checks that the item API and Tags API agree afterward. The measured average item has three Tags. ## Verification ```text $ cargo fmt --check (no output; exit 0) $ cargo clippy -p calternal-plugin-photos --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 9m 28s $ cargo test -p calternal-plugin-photos test result: ok. 59 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 8.01s $ cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 42s $ cargo test -p calternal-server test result: ok. 272 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 211.10s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 58.05s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s $ bun run check perf-lint: PASS; 0 violations; 22557 scoped exceptions svelte-check found 0 errors and 4 warnings in 2 files $ bunx vitest run src/lib/search/SearchPreview.svelte.test.ts src/lib/files/InfoPanel.svelte.test.ts --maxWorkers=2 Test Files 2 passed (2) Tests 23 passed (23) $ CALTERNAL_E2E_ASSET_OVERRIDE=1 bun e2e/photos.mjs --tags-only --screenshots <artifacts> PHOTOS TAGS REGRESSION PASSED CSP REPORTS photos: 0 across 3 pages ``` The four Svelte warnings are in the existing Mail and Money styles. The full web suite also passed earlier in this job: `Test Files 297 passed (297)` and `Tests 2121 passed (2121)`. Per the current verification policy, the merge round should run the full suite on the combined branch. ## Local performance profile No comparable feature baseline existed. The local production-browser profile ran on `calternal-dev` with load average 18.75/18/20.84; treat it as a reference only. `docs/perf/baseline.json` records the measurements. - Average: 40 serial requests, 3 Tags per item; p50 22.3 ms, p95 41.2 ms; mean server CPU 32.02%, mean RSS 241,596,514 bytes. - Worst-case burst: 24 concurrent requests, 200 Tags per item; p50 215.1 ms, p95 322.3 ms; mean server CPU 73.33%, peak RSS 242,941,952 bytes. ## Screenshots 18 actual-size Mac-emulated PNGs are attached: 12 Inspector captures (mouse and keyboard, three widths, two themes) and 6 Search Preview captures (three widths, two themes). No screenshots are committed. Inspector: [1440 paper mouse](https://git.kayg.org/attachments/44fb9afe-7893-44f5-9a3e-71ea764ddf86), [1440 dark mouse](https://git.kayg.org/attachments/5cb8724f-5844-43cf-8fb5-564451be15cd), [820 paper mouse](https://git.kayg.org/attachments/8179bc51-80bc-4868-9851-1517f1c1fc29), [820 dark mouse](https://git.kayg.org/attachments/8db2c215-504a-4c35-8df2-29f02f038e4a), [390 paper mouse](https://git.kayg.org/attachments/790d3528-8e96-4836-950f-1c70b15d5f88), [390 dark mouse](https://git.kayg.org/attachments/cecd6a96-aa6c-411a-b174-a9e9b0e0d8db); [1440 paper keyboard](https://git.kayg.org/attachments/fc0abfac-78fd-493f-850a-4486edb6bcdd), [1440 dark keyboard](https://git.kayg.org/attachments/137feca7-5ea0-49bf-910c-355ef8b96e3b), [820 paper keyboard](https://git.kayg.org/attachments/8f2526ba-3baf-4d52-bf3e-def56348f0ae), [820 dark keyboard](https://git.kayg.org/attachments/3cd605f4-c6b4-43b3-8e71-ac3c9964ad2d), [390 paper keyboard](https://git.kayg.org/attachments/cce41b08-a84c-4602-8b2c-6754c528e7e0), [390 dark keyboard](https://git.kayg.org/attachments/77bb3e45-57c2-4102-9d8d-c68020ceb35f). Search Preview: [1440 paper](https://git.kayg.org/attachments/0f84e5cd-0acd-45d3-b4d8-49795c1223fa), [1440 dark](https://git.kayg.org/attachments/3f6331a0-50cc-4b42-a776-32f082c34fcf), [820 paper](https://git.kayg.org/attachments/b918f27e-bd99-420e-bc68-e447a768e4b4), [820 dark](https://git.kayg.org/attachments/a80b9003-d703-4b1b-bb10-0405d004b6d7), [390 paper](https://git.kayg.org/attachments/d0956459-e6f6-4ac0-9d8d-1de7906d605b), [390 dark](https://git.kayg.org/attachments/4d46f5ca-812f-472a-a358-24902040d911). UX gaps closed: Tags remain visible after reload, Search previews read the shared item API, and the phone Quick Look action bar routes shortcuts through the viewer handler. UX gaps left: none known. Files changed: `crates/plugins/photos/src/routes.rs`, `contracts/openapi.json`, `packages/api-client/src/generated.ts`, `apps/web/src/lib/files/InfoPanel.svelte`, `apps/web/src/lib/files/api.ts`, `apps/web/src/lib/photos/PhotoViewer.svelte`, `apps/web/src/lib/photos/api.ts`, `apps/web/src/lib/search/SearchPreview.svelte`, `apps/web/src/lib/search/SearchPreview.svelte.test.ts`, `apps/web/e2e/photos.mjs`, `packages/ui/src/components/viewer/QuickLook.svelte`, `bench/photo-item-tags-1370.sh`, `docs/perf/baseline.json`, `contracts/perf/adoption-1058.json`, `contracts/perf/exceptions.json`, `contracts/perf/ratchet.json`.
Author
Owner

Head update: cdc7922150dc330de6f29869eeaf17a5e5027091. This is a documentation-only change to the browser profile evidence callbacks after the final report; all verification results and screenshot attachments in the previous comment are unchanged.

Head update: `cdc7922150dc330de6f29869eeaf17a5e5027091`. This is a documentation-only change to the browser profile evidence callbacks after the final report; all verification results and screenshot attachments in the previous comment are unchanged.
Author
Owner

Deployed to calternal.cloud in merge round 35b (build 9dc65437e, live 2026-10-09 ~11:07 IST). Closing.

Deployed to calternal.cloud in merge round 35b (build 9dc65437e, live 2026-10-09 ~11:07 IST). Closing.
kayg closed this issue 2026-10-09 05:36:47 +00:00
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1370
No description provided.