SIDEBAR: one shared section-header component — Settings 'User'/'Admin' reuse it (size, hover Copy link), like Files' 'Pinned'/'Smart folders' #320

Closed
opened 2026-09-28 08:59:52 +00:00 by kayg · 8 comments
Owner

Owner (2026-09-28, screenshot of the Settings sidebar): 'i remember also asking for User/Admin here to behave like Section headers (the sizing that is? of course they should have other section header properties like the hover to copy link feature) basically reuse the code - what happened to that?' The orchestrator found no earlier issue for it (#192 created the User/Admin sections without this). Filed now.

Rule (reuse gate, one owner): every sidebar section header in every mode renders through ONE shared component: Settings (User, Admin), Files (Pinned, Smart folders), Photos (Library, Library folders), Notes, Calendar (Tags…), Admin pages, and Mail later. Same type role (the size/weight tokens from #287), the same spacing above and below, the same colour.
Behaviours the shared header gives everyone:

  • Hover (desktop) shows a Copy link affordance for that section (a deep link per DESIGN §33, e.g. /settings/admin, /files#pinned), plus 'Copy link' in its context menu, and on touch via long-press. It matches how note section headings expose their links (reuse that code path; do not write a second one).
  • Optional collapse chevron, where a section already collapses (reuse the existing collapse state and persistence).
  • Optional count badge (as Search's 'NAVIGATE 5').
  • Keyboard: focusable only when it has an action; an accessible name; headings are real headings (h2/h3) where the sidebar structure needs them.
    Steps: find every sidebar header implementation (post the list with file:line on the issue), extract or extend the shared one in packages/ui (reuse whatever the Files sidebar uses if it is already shared), migrate all of them, and delete the local copies. Keep the diff mechanical. Evidence: 2× before/after crops of every sidebar (Settings as admin and as a standard user, Files, Photos, Notes, Calendar), light and dark, plus a hover showing Copy link. Coordinate with #319 (single-pill buttons) and #287 (weights) if they touch the same files.
Owner (2026-09-28, screenshot of the Settings sidebar): 'i remember also asking for User/Admin here to behave like Section headers (the sizing that is? of course they should have other section header properties like the hover to copy link feature) basically reuse the code - what happened to that?' The orchestrator found no earlier issue for it (#192 created the User/Admin sections without this). Filed now. Rule (reuse gate, one owner): **every sidebar section header in every mode renders through ONE shared component**: Settings (User, Admin), Files (Pinned, Smart folders), Photos (Library, Library folders), Notes, Calendar (Tags…), Admin pages, and Mail later. Same type role (the size/weight tokens from #287), the same spacing above and below, the same colour. Behaviours the shared header gives everyone: - **Hover (desktop) shows a Copy link** affordance for that section (a deep link per DESIGN §33, e.g. `/settings/admin`, `/files#pinned`), plus 'Copy link' in its context menu, and on touch via long-press. It matches how note section headings expose their links (reuse that code path; do not write a second one). - Optional collapse chevron, where a section already collapses (reuse the existing collapse state and persistence). - Optional count badge (as Search's 'NAVIGATE 5'). - Keyboard: focusable only when it has an action; an accessible name; headings are real headings (`h2`/`h3`) where the sidebar structure needs them. Steps: find every sidebar header implementation (post the list with file:line on the issue), extract or extend the shared one in packages/ui (reuse whatever the Files sidebar uses if it is already shared), migrate all of them, and delete the local copies. Keep the diff mechanical. Evidence: 2× before/after crops of every sidebar (Settings as admin and as a standard user, Files, Photos, Notes, Calendar), light and dark, plus a hover showing Copy link. Coordinate with #319 (single-pill buttons) and #287 (weights) if they touch the same files.
Author
Owner

Sidebar header inventory before migration:

  • Settings: User and Admin headings are inline in apps/web/src/routes/settings/[...path]/+page.svelte:307,311.
  • Files: Pinned is inline in apps/web/src/lib/files/FilesSidebar.svelte:139; Smart folders is passed at FilesSidebar.svelte:186 and rendered by apps/web/src/lib/search/SavedSearchList.svelte:93.
  • Photos: Library is passed at apps/web/src/lib/photos/PhotosSidebar.svelte:46-48 and rendered by apps/web/src/lib/components/SidebarLinks.svelte:34; Library folders is inline in PhotosSidebar.svelte:53.
  • Notes: Notes is inline in apps/web/src/lib/notes/NotesExplorer.svelte:329.
  • Calendar: Tags is inline in apps/web/src/lib/components/app-sidebar.svelte:444; Saved searches is passed at app-sidebar.svelte:460 and rendered by SavedSearchList.svelte:93; Pinned is passed at app-sidebar.svelte:463 and rendered by SidebarLinks.svelte:34.
  • Analytics: Ranges is passed in apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte:40 and rendered by SidebarLinks.svelte:34.

The initial branch is job/sidebar-headers, based on dev at 80fa1fedd80b522367a68dba596679cde6b361b3.

Sidebar header inventory before migration: - Settings: User and Admin headings are inline in `apps/web/src/routes/settings/[...path]/+page.svelte:307,311`. - Files: Pinned is inline in `apps/web/src/lib/files/FilesSidebar.svelte:139`; Smart folders is passed at `FilesSidebar.svelte:186` and rendered by `apps/web/src/lib/search/SavedSearchList.svelte:93`. - Photos: Library is passed at `apps/web/src/lib/photos/PhotosSidebar.svelte:46-48` and rendered by `apps/web/src/lib/components/SidebarLinks.svelte:34`; Library folders is inline in `PhotosSidebar.svelte:53`. - Notes: Notes is inline in `apps/web/src/lib/notes/NotesExplorer.svelte:329`. - Calendar: Tags is inline in `apps/web/src/lib/components/app-sidebar.svelte:444`; Saved searches is passed at `app-sidebar.svelte:460` and rendered by `SavedSearchList.svelte:93`; Pinned is passed at `app-sidebar.svelte:463` and rendered by `SidebarLinks.svelte:34`. - Analytics: Ranges is passed in `apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte:40` and rendered by `SidebarLinks.svelte:34`. The initial branch is `job/sidebar-headers`, based on `dev` at `80fa1fedd80b522367a68dba596679cde6b361b3`.
Author
Owner

Inventory addendum: the wide Settings rail also renders its User/Admin labels as grouped section captions in packages/ui/src/components/FloatingSidebar.svelte:184-185, driven by the group values built in apps/web/src/routes/settings/[...path]/+page.svelte:67. I will migrate both this rail and the compact Settings list (the first inventory comment lists the latter).

Inventory addendum: the wide Settings rail also renders its User/Admin labels as grouped section captions in `packages/ui/src/components/FloatingSidebar.svelte:184-185`, driven by the group values built in `apps/web/src/routes/settings/[...path]/+page.svelte:67`. I will migrate both this rail and the compact Settings list (the first inventory comment lists the latter).
Author
Owner

Implementation check finding and fix:

  • The first bun run check rejected a Svelte class directive on the chevron component. I changed it to a class attribute. Svelte then warned about pointer and keyboard listeners on a static wrapper; I moved those bubbling listeners to the mounted section group so only its actual controls enter the tab order.
  • Current evidence: svelte-check found 0 errors and 0 warnings; the focused header test reports 3 passing tests.
Implementation check finding and fix: - The first `bun run check` rejected a Svelte class directive on the chevron component. I changed it to a class attribute. Svelte then warned about pointer and keyboard listeners on a static wrapper; I moved those bubbling listeners to the mounted section group so only its actual controls enter the tab order. - Current evidence: `svelte-check found 0 errors and 0 warnings`; the focused header test reports 3 passing tests.
Author
Owner

Finding during the required dev merge: five sidebar files conflicted only on duplicated legacy caption styles (.explorer-label, .sidebar-section-label, .master-label, and .section-label). Those rules were removed because SidebarSectionHeader now owns that typography; incoming --sidebar-row-weight changes remain in the migrated row styles. Resolution is committed in c86efc6f.

Finding during the required dev merge: five sidebar files conflicted only on duplicated legacy caption styles (`.explorer-label`, `.sidebar-section-label`, `.master-label`, and `.section-label`). Those rules were removed because `SidebarSectionHeader` now owns that typography; incoming `--sidebar-row-weight` changes remain in the migrated row styles. Resolution is committed in `c86efc6f`.
Author
Owner

The first review capture exposed a harness issue: the debug server reads RustEmbed assets from the live apps/web/build directory, so the preserved debug binary showed the migrated headers in both phases. The 12 before/after PNG pairs were byte-identical. I will rebuild the base production bundle and recapture the before images from it; the current after images are from the merged build.

The first review capture exposed a harness issue: the debug server reads RustEmbed assets from the live `apps/web/build` directory, so the preserved debug binary showed the migrated headers in both phases. The 12 before/after PNG pairs were byte-identical. I will rebuild the base production bundle and recapture the before images from it; the current after images are from the merged build.
Author
Owner

Production screenshots (2×)

Real local server, production frontend build, 1440×960 viewport at device scale factor 2.

View Base (80fa1fed) Migrated (c86efc6f)
calendar paper · Tokyo night paper · Tokyo night
files paper · Tokyo night paper · Tokyo night
notes paper · Tokyo night paper · Tokyo night
photos paper · Tokyo night paper · Tokyo night
settings-admin paper · Tokyo night paper · Tokyo night
settings-member paper · Tokyo night paper · Tokyo night
## Production screenshots (2×) Real local server, production frontend build, 1440×960 viewport at device scale factor 2. | View | Base (`80fa1fed`) | Migrated (`c86efc6f`) | |---|---|---| | calendar | [paper](https://git.kayg.org/attachments/3d9bdad2-2f51-499d-abb9-612a41e12928) · [Tokyo night](https://git.kayg.org/attachments/d00a8e56-405a-48e6-a5d2-29ef081d4712) | [paper](https://git.kayg.org/attachments/290a667e-f749-4609-a42e-490a0495cb05) · [Tokyo night](https://git.kayg.org/attachments/c9d5032c-5f69-4201-8ab3-331b1719e3a2) | | files | [paper](https://git.kayg.org/attachments/b34f8583-74ee-4272-ae8c-55ed84d62e8d) · [Tokyo night](https://git.kayg.org/attachments/ec1adb44-7900-489f-863e-900df8e9ada2) | [paper](https://git.kayg.org/attachments/60d8ca40-49b2-47ef-8e75-ce96ba5a28d3) · [Tokyo night](https://git.kayg.org/attachments/de53f321-94b9-43f8-a288-d84471ac5c5b) | | notes | [paper](https://git.kayg.org/attachments/4cb9ac76-247d-4fce-b767-c584e8e7e9b1) · [Tokyo night](https://git.kayg.org/attachments/8a694fe5-72ae-4f47-9f4a-87bfe62d6dc5) | [paper](https://git.kayg.org/attachments/95eb82f7-d5ca-4f6b-aa37-30470ca01762) · [Tokyo night](https://git.kayg.org/attachments/b33feeed-3e5c-4666-9860-0424e0cd4b91) | | photos | [paper](https://git.kayg.org/attachments/0d5fbf1e-58fc-4d33-bdce-f5ce0d3817dd) · [Tokyo night](https://git.kayg.org/attachments/40e024f0-8431-43e9-97c9-66b49171dd12) | [paper](https://git.kayg.org/attachments/cc0281a2-3860-4e27-a569-1351019ef05b) · [Tokyo night](https://git.kayg.org/attachments/ed9db14d-8f02-4d0a-8413-045e0d4713ed) | | settings-admin | [paper](https://git.kayg.org/attachments/4d6a0e74-3af9-4577-9b3d-a1e87e3b63b6) · [Tokyo night](https://git.kayg.org/attachments/eaf1c580-999c-4a3a-8801-441c52c6cb61) | [paper](https://git.kayg.org/attachments/67364ed2-acaf-4898-8fd2-0c18d5997466) · [Tokyo night](https://git.kayg.org/attachments/89ef098b-d820-4c5a-b663-67fe7aedc4a8) | | settings-member | [paper](https://git.kayg.org/attachments/23c0c523-f301-4037-b9c3-bdf305989fa4) · [Tokyo night](https://git.kayg.org/attachments/8cdc7db2-9514-4716-821e-08a4bad5a34c) | [paper](https://git.kayg.org/attachments/113ad1e3-76e7-40fb-aff7-ea8cd87ba9cf) · [Tokyo night](https://git.kayg.org/attachments/8e2a69e2-1ff0-4a14-85e6-e3230611b64c) |
Author
Owner

Adversarial probe update: the initial ADVERSARIAL_ROUND2_ONLY=1 selection skipped round 1 media fixtures. Its Photos shared-timeline result was therefore a probe setup gap (empty timeline, no seeded photo). It also reported 64 accepted SSE streams and 20 mkdirs taking 14.1 seconds with streams open; I will file this non-SLOW load finding separately. The zip stream exceeded 30 seconds and was explicitly marked SLOW. The probe then stopped at its CLI section because the supplied server binary skipped building target/debug/calternal. I am building that binary and will make one corrected fixture-enabled run.

Adversarial probe update: the initial `ADVERSARIAL_ROUND2_ONLY=1` selection skipped round 1 media fixtures. Its Photos shared-timeline result was therefore a probe setup gap (empty timeline, no seeded photo). It also reported 64 accepted SSE streams and 20 mkdirs taking 14.1 seconds with streams open; I will file this non-SLOW load finding separately. The zip stream exceeded 30 seconds and was explicitly marked SLOW. The probe then stopped at its CLI section because the supplied server binary skipped building `target/debug/calternal`. I am building that binary and will make one corrected fixture-enabled run.
Author
Owner

Final report

Implemented one shared SidebarSectionHeader across the sidebar headings in Settings, Files, Photos, Notes, Calendar, Analytics, saved searches, and the shared sidebar links. It reuses LinkedHeading's Copy link behavior. Section menus support pointer context menus, Shift+F10, and long press. Existing owners keep collapse state and persistence.

Files

  • packages/ui/src/components/SidebarSectionHeader.svelte, packages/ui/src/index.ts
  • apps/web/src/lib/components/SidebarSectionHeader.svelte.test.ts
  • apps/web/src/lib/components/SidebarLinks.svelte, apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte, apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/files/FilesSidebar.svelte, apps/web/src/lib/notes/NotesExplorer.svelte, apps/web/src/lib/photos/PhotosSidebar.svelte, apps/web/src/lib/search/SavedSearchList.svelte
  • apps/web/src/lib/styles/sidebar.css, apps/web/src/routes/settings/[...path]/+page.svelte, packages/ui/src/components/FloatingSidebar.svelte

Commits and screenshots

  • Feature: 3501f4597ca48d65ca4949d6027f8766749ba866 — Unify sidebar section headings across modes
  • Merged dev once: c86efc6fffe1a6edd4b8710e72d3a161063e5063 — current HEAD
  • Pushed job/sidebar-headers; push reported Everything up-to-date.
  • 12 baseline and 12 migrated production screenshots (2×, paper and Tokyo night) are attached in the preceding comment.

Gates

cargo fmt --check exited 0 with no stdout.

cargo clippy --all-targets -- -D warnings exited 0. Final output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 47m 06s

cargo test exited 0. Exact Cargo result lines:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 181.70s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.74s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.14s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 31.60s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 390.22s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.38s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.98s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.54s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.31s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.83s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 37.07s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 86.01s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.89s
test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.69s
test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.62s
test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 16.55s
test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 20.13s
test result: ok. 486 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.75s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.66s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.52s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.35s
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.40s
test result: ok. 48 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.58s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.20s
test result: ok. 120 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 268.50s
test result: ok. 100 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 125.06s
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s
test result: ok. 42 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 15.72s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s
test result: ok. 28 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 2.46s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.40s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 23.28s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.77s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 65 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 22.77s
test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.37s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.53s
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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.01s
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 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
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

Totals: 1,365 passed, 0 failed, 12 ignored. The ignored tests require local pinned model assets or are manual benchmarks/live-app runs.

bun run check:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/sidebar-headers/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

 Test Files  111 passed (111)
      Tests  712 passed (712)
   Start at  12:47:31
   Duration  166.29s (transform 61%, environment 16%, import 12%, tests 8%, setup 3%)

  Transform  |component| transforming modules took 294.96s · 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

bun run build also completed successfully for the production screenshot captures.

Adversarial results and gaps

The time-boxed adversarial probe did not complete. The first round-2-only invocation omitted round-1 media fixtures and the CLI binary; its empty Photos timeline was a probe setup gap. I built the CLI binary and started one corrected fixture-enabled pass. Its 20,000-item staged Search rebuild did not signal restart-ready-4 within the probe's 180-second wait. The harness then cleaned up the server; subsequent 502 responses were not independently validated.

Before the corrected pass, the probe reported three missing-hit checks for unicodenfcsentinel during staged Search rebuild. I filed this with the limits above in #336. The corrected pass was stopped by the harness timeout before reaching the remaining round-2 sections.

The probe also reported 20 mkdirs took 14.1s with open streams after accepting 64 SSE streams. I added this measured latency case to existing #330. A ZIP stream exceeding 30 seconds was explicitly marked SLOW and treated as shared-host load. No 5xx/crash from the header changes was observed.

Decisions

The design did not assign collapse persistence to the shared heading. The component therefore calls the caller's existing collapse callback and does not store state. Each heading gets a stable route/hash supplied by its owning sidebar. Context-menu, Shift+F10, and long-press all expose the shared Copy link action.

## Final report Implemented one shared `SidebarSectionHeader` across the sidebar headings in Settings, Files, Photos, Notes, Calendar, Analytics, saved searches, and the shared sidebar links. It reuses `LinkedHeading`'s Copy link behavior. Section menus support pointer context menus, Shift+F10, and long press. Existing owners keep collapse state and persistence. ### Files - `packages/ui/src/components/SidebarSectionHeader.svelte`, `packages/ui/src/index.ts` - `apps/web/src/lib/components/SidebarSectionHeader.svelte.test.ts` - `apps/web/src/lib/components/SidebarLinks.svelte`, `apps/web/src/lib/components/analytics/AnalyticsSidebar.svelte`, `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/files/FilesSidebar.svelte`, `apps/web/src/lib/notes/NotesExplorer.svelte`, `apps/web/src/lib/photos/PhotosSidebar.svelte`, `apps/web/src/lib/search/SavedSearchList.svelte` - `apps/web/src/lib/styles/sidebar.css`, `apps/web/src/routes/settings/[...path]/+page.svelte`, `packages/ui/src/components/FloatingSidebar.svelte` ### Commits and screenshots - Feature: `3501f4597ca48d65ca4949d6027f8766749ba866` — `Unify sidebar section headings across modes` - Merged `dev` once: `c86efc6fffe1a6edd4b8710e72d3a161063e5063` — current HEAD - Pushed `job/sidebar-headers`; push reported `Everything up-to-date`. - 12 baseline and 12 migrated production screenshots (2×, paper and Tokyo night) are attached in the preceding comment. ### Gates `cargo fmt --check` exited 0 with no stdout. `cargo clippy --all-targets -- -D warnings` exited 0. Final output: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 47m 06s ``` `cargo test` exited 0. Exact Cargo result lines: ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 181.70s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.74s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.14s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 31.60s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 390.22s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.38s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.98s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.54s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.31s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.83s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 37.07s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 86.01s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.89s test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.69s test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.62s test result: ok. 36 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 16.55s test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 20.13s test result: ok. 486 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.75s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.66s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.52s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.35s test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.40s test result: ok. 48 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.58s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.20s test result: ok. 120 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 268.50s test result: ok. 100 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 125.06s test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s test result: ok. 42 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 15.72s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s test result: ok. 28 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 2.46s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.40s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 23.28s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.77s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s test result: ok. 65 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 22.77s test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.37s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.53s test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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.01s test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 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 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` Totals: 1,365 passed, 0 failed, 12 ignored. The ignored tests require local pinned model assets or are manual benchmarks/live-app runs. `bun run check`: ```text $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/sidebar-headers/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 111 passed (111) Tests 712 passed (712) Start at 12:47:31 Duration 166.29s (transform 61%, environment 16%, import 12%, tests 8%, setup 3%) Transform |component| transforming modules took 294.96s · 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 ``` `bun run build` also completed successfully for the production screenshot captures. ### Adversarial results and gaps The time-boxed adversarial probe did not complete. The first round-2-only invocation omitted round-1 media fixtures and the CLI binary; its empty Photos timeline was a probe setup gap. I built the CLI binary and started one corrected fixture-enabled pass. Its 20,000-item staged Search rebuild did not signal `restart-ready-4` within the probe's 180-second wait. The harness then cleaned up the server; subsequent 502 responses were not independently validated. Before the corrected pass, the probe reported three missing-hit checks for `unicodenfcsentinel` during staged Search rebuild. I filed this with the limits above in [#336](https://git.kayg.org/kayg/calternal/issues/336). The corrected pass was stopped by the harness timeout before reaching the remaining round-2 sections. The probe also reported `20 mkdirs took 14.1s with open streams` after accepting 64 SSE streams. I added this measured latency case to existing [#330](https://git.kayg.org/kayg/calternal/issues/330). A ZIP stream exceeding 30 seconds was explicitly marked SLOW and treated as shared-host load. No 5xx/crash from the header changes was observed. ### Decisions The design did not assign collapse persistence to the shared heading. The component therefore calls the caller's existing collapse callback and does not store state. Each heading gets a stable route/hash supplied by its owning sidebar. Context-menu, Shift+F10, and long-press all expose the shared Copy link action.
kayg closed this issue 2026-09-28 13:05:36 +00:00
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#320
No description provided.