UI: every lone icon button is a pill-in-a-pill (shared single PillGroup) — sidebar toggle first, then hunt all instances #319

Open
opened 2026-09-28 08:53:12 +00:00 by kayg · 15 comments
Owner

Owner (2026-09-28, screenshots: the sidebar collapse/expand button vs the ⋯ menu button): 'this single sidebar button should be a pill within a pill like most single buttons (like the 3 dot menu button) please hunt for instances like these.'

Rule (one owner, reuse): a standalone icon button that is not already inside a pill group renders through the shared single-button pill: the glass island with the inner button (PillGroup variant="single" in packages/ui, as the ⋯ menu, Fab and PrimaryPill do), with the same size tokens (--island-hit, --hit, icon size), the warm tooltip with its shortcut, the magnetic hover and the focus ring. No bare icon buttons with their own border or background.

  1. Sidebar collapse/expand toggle (FloatingSidebar / app-sidebar.svelte oncollapse, and the expand button that shows when the sidebar is collapsed in ModeHeader): convert it to the single pill.
  2. Hunt: search the whole web app and packages/ui for lone icon buttons: <button with only an icon child outside a PillGroup/SelectionBar/menu row/toolbar; classes like icon-btn, ghost, close, tool; close buttons on sheets, popovers, dialogs and viewers (the photo viewer, the inspector popover, the search palette's esc, Settings back); the Calendar nav arrows; the Files view toggles; the note header buttons; empty-state buttons. List every instance (file:line) in the issue before changing. Exclude, with a reason in the list: buttons inside an existing group, inline text-editor controls, list-row trailing actions that follow the row's own style, and form controls.
  3. Convert each instance to the shared single pill (or into an existing neighbouring group when two lone buttons sit side by side: two lone pills next to each other become one group). No new primitive; if the single variant lacks a needed size (e.g. compact inside a dense header), add a size option to PillGroup.
  4. Add a guard to the consistency hunt (#237 / docs/audits): a grep-based check listing lone icon buttons so new ones get flagged.
    Use role tokens only (#287/#299). Keep diffs mechanical so parallel jobs merge easily. Evidence: 2× before/after crops of every changed spot (light + dark; desktop + phone), zoom-checking icon centring inside the inner pill.
Owner (2026-09-28, screenshots: the sidebar collapse/expand button vs the ⋯ menu button): 'this single sidebar button should be a pill within a pill like most single buttons (like the 3 dot menu button) please hunt for instances like these.' Rule (one owner, reuse): **a standalone icon button that is not already inside a pill group renders through the shared single-button pill**: the glass island with the inner button (`PillGroup variant="single"` in packages/ui, as the ⋯ menu, Fab and PrimaryPill do), with the same size tokens (`--island-hit`, `--hit`, icon size), the warm tooltip with its shortcut, the magnetic hover and the focus ring. No bare icon buttons with their own border or background. 1. **Sidebar collapse/expand toggle** (FloatingSidebar / app-sidebar.svelte `oncollapse`, and the expand button that shows when the sidebar is collapsed in ModeHeader): convert it to the single pill. 2. **Hunt**: search the whole web app and packages/ui for lone icon buttons: `<button` with only an icon child outside a PillGroup/SelectionBar/menu row/toolbar; classes like `icon-btn`, `ghost`, `close`, `tool`; close buttons on sheets, popovers, dialogs and viewers (the photo viewer, the inspector popover, the search palette's esc, Settings back); the Calendar nav arrows; the Files view toggles; the note header buttons; empty-state buttons. List every instance (file:line) in the issue before changing. Exclude, with a reason in the list: buttons inside an existing group, inline text-editor controls, list-row trailing actions that follow the row's own style, and form controls. 3. Convert each instance to the shared single pill (or into an existing neighbouring group when two lone buttons sit side by side: two lone pills next to each other become one group). **No new primitive**; if the single variant lacks a needed size (e.g. compact inside a dense header), add a size option to PillGroup. 4. Add a guard to the consistency hunt (#237 / docs/audits): a grep-based check listing lone icon buttons so new ones get flagged. Use role tokens only (#287/#299). Keep diffs mechanical so parallel jobs merge easily. Evidence: 2× before/after crops of every changed spot (light + dark; desktop + phone), zoom-checking icon centring inside the inner pill.
Author
Owner

Starting #319 on branch job/single-pills. Base dev SHA: 49a795f159. I am completing the required lone-icon inventory before the first code change.

Starting #319 on branch job/single-pills. Base dev SHA: 49a795f1598706b16d9753b9db9e98f2ffe14797. I am completing the required lone-icon inventory before the first code change.
Author
Owner

#319 inventory before source changes

Branch: job/single-pills; base dev SHA 49a795f1598706b16d9753b9db9e98f2ffe14797. I scanned apps/web/src and packages/ui/src and checked the component parents and call sites.

Convert

  • apps/web/src/routes/+layout.svelte:586 — bare Hide sidebar button; first conversion.
  • packages/ui/src/components/ModeHeader.svelte:383 — Show sidebar; already in a one-button PillGroup, but still uses the toolbar variant.
  • packages/ui/src/components/ModeHeader.svelte:371 — page Back; one-button PillGroup is compact toolbar on desktop and single on phone. Use the single variant with compact desktop sizing.
  • apps/web/src/routes/settings/[...path]/+page.svelte:292 — desktop Back to settings; one-button PillGroup uses the toolbar variant. The phone control at :230 already uses single.
  • packages/ui/src/components/Inspector.svelte:76 — bare inspector Close.
  • apps/web/src/lib/components/KeyboardShortcutsCard.svelte:81 — bare shortcuts-card Close.
  • apps/web/src/lib/components/TagEditor.svelte:104 — bare sheet Close.
  • apps/web/src/lib/files/ShareDialog.svelte:296 — bare share-sheet Close.
  • apps/web/src/lib/files/UploadToast.svelte:104,110,120 — pause/resume, expand/collapse and dismiss controls in the summary; group the adjacent controls.
  • apps/web/src/lib/notes/NotesExplorer.svelte:330 — icon-only New note action in the sidebar section header.
  • apps/web/src/lib/components/analytics/RangePicker.svelte:318,330 — previous/next range controls; each sits on a different side of the title, so each gets a single PillGroup.
  • packages/ui/src/components/calendar/MiniMonth.svelte:45-46 — adjacent previous/next month controls; combine in one PillGroup.
  • packages/ui/src/components/calendar/AgendaList.svelte:331,335 — adjacent Log and day-actions controls; combine in one PillGroup.
  • apps/web/src/lib/search/SearchPreview.svelte:321,325,328 — Open/Run, Quick Look and Copy link actions; group the adjacent preview actions.
  • apps/web/src/lib/composer/Composer.svelte:1541 — bare Composer Send action; when “Send all” is present, keep the neighboring send actions in one group.
  • packages/ui/src/components/LinkedHeading.svelte:132 — standalone Copy link icon beside section headings; use the shared single PillGroup.
  • apps/web/src/lib/photos/PhotoTimeline.svelte:586,616 — standalone Copy link actions on month and day headings.

Excluded, with reason

  • Existing action groups: packages/ui/src/components/ChromeActions.svelte:72-109, ModeHeader.svelte:498-527, calendar/CalendarTools.svelte:83-91, calendar/ItemPreview.svelte:210-225,245-279,332-343,387-392, viewer/QuickLook.svelte:205-208,260-284, apps/web/src/lib/files/InfoPanel.svelte:182-191, apps/web/src/lib/components/search-dialog.svelte:631-682, apps/web/src/lib/composer/Composer.svelte:1516-1536, and packages/ui/src/components/SelectionBar.svelte:65-105 already use PillGroup or SelectionBar.
  • Row or tile actions keep their row style: apps/web/src/lib/components/app-sidebar.svelte:389 (tree disclosure), apps/web/src/lib/composer/Composer.svelte:1458,1464 (attachment tile), apps/web/src/lib/files/FilesSidebar.svelte:172-174 (pinned row), apps/web/src/lib/files/ShareDialog.svelte:311,359-360 (share/link rows), apps/web/src/lib/files/InfoPanel.svelte:259 (Version row), apps/web/src/lib/files/PublicLinkPage.svelte:685 (file row), apps/web/src/lib/notifications/InboxPanel.svelte:359 (notification row), packages/ui/src/components/calendar/AgendaList.svelte:300 (item row menu), apps/web/src/lib/search/SavedSearchList.svelte:152 (saved-search row), and apps/web/src/lib/files/UploadToast.svelte:137-144 (per-upload rows).
  • Form or inline editor controls: apps/web/src/lib/components/TagEditor.svelte:112 (tag-chip removal), apps/web/src/lib/search/SearchField.svelte:252,258,282 (filter-chip removal), and apps/web/src/lib/photos/PhotoTimeline.svelte:603 (day checkbox).
  • CopyLink variant="pill" is row/field chrome at apps/web/src/lib/components/NoteList.svelte:39, apps/web/src/routes/tag/[tag]/+page.svelte:132, apps/web/src/routes/tags/+page.svelte:41, apps/web/src/routes/settings/ai/TurnHistory.svelte:92, apps/web/src/lib/ai/TurnPanel.svelte:203, and apps/web/src/routes/settings/admin/InvitationsGroup.svelte:115; these are row actions or a copy control beside an input. The heading and timeline uses are listed above for conversion.
  • Text-bearing controls are not icon-only: packages/ui/src/components/NoticeBanner.svelte:62 (dismiss text), apps/web/src/lib/auth/components/RecoveryKeyWords.svelte:70,77 (Copy words and Download), and packages/ui/src/components/TodayButton.svelte:35 (Today label).

The Search palette Escape behavior is keyboard-only and has no icon button. Files view selection is already in the shared segmented control.

#319 inventory before source changes Branch: `job/single-pills`; base `dev` SHA `49a795f1598706b16d9753b9db9e98f2ffe14797`. I scanned `apps/web/src` and `packages/ui/src` and checked the component parents and call sites. ## Convert - `apps/web/src/routes/+layout.svelte:586` — bare Hide sidebar button; first conversion. - `packages/ui/src/components/ModeHeader.svelte:383` — Show sidebar; already in a one-button PillGroup, but still uses the toolbar variant. - `packages/ui/src/components/ModeHeader.svelte:371` — page Back; one-button PillGroup is compact toolbar on desktop and single on phone. Use the single variant with compact desktop sizing. - `apps/web/src/routes/settings/[...path]/+page.svelte:292` — desktop Back to settings; one-button PillGroup uses the toolbar variant. The phone control at `:230` already uses `single`. - `packages/ui/src/components/Inspector.svelte:76` — bare inspector Close. - `apps/web/src/lib/components/KeyboardShortcutsCard.svelte:81` — bare shortcuts-card Close. - `apps/web/src/lib/components/TagEditor.svelte:104` — bare sheet Close. - `apps/web/src/lib/files/ShareDialog.svelte:296` — bare share-sheet Close. - `apps/web/src/lib/files/UploadToast.svelte:104,110,120` — pause/resume, expand/collapse and dismiss controls in the summary; group the adjacent controls. - `apps/web/src/lib/notes/NotesExplorer.svelte:330` — icon-only New note action in the sidebar section header. - `apps/web/src/lib/components/analytics/RangePicker.svelte:318,330` — previous/next range controls; each sits on a different side of the title, so each gets a single PillGroup. - `packages/ui/src/components/calendar/MiniMonth.svelte:45-46` — adjacent previous/next month controls; combine in one PillGroup. - `packages/ui/src/components/calendar/AgendaList.svelte:331,335` — adjacent Log and day-actions controls; combine in one PillGroup. - `apps/web/src/lib/search/SearchPreview.svelte:321,325,328` — Open/Run, Quick Look and Copy link actions; group the adjacent preview actions. - `apps/web/src/lib/composer/Composer.svelte:1541` — bare Composer Send action; when “Send all” is present, keep the neighboring send actions in one group. - `packages/ui/src/components/LinkedHeading.svelte:132` — standalone Copy link icon beside section headings; use the shared single PillGroup. - `apps/web/src/lib/photos/PhotoTimeline.svelte:586,616` — standalone Copy link actions on month and day headings. ## Excluded, with reason - Existing action groups: `packages/ui/src/components/ChromeActions.svelte:72-109`, `ModeHeader.svelte:498-527`, `calendar/CalendarTools.svelte:83-91`, `calendar/ItemPreview.svelte:210-225,245-279,332-343,387-392`, `viewer/QuickLook.svelte:205-208,260-284`, `apps/web/src/lib/files/InfoPanel.svelte:182-191`, `apps/web/src/lib/components/search-dialog.svelte:631-682`, `apps/web/src/lib/composer/Composer.svelte:1516-1536`, and `packages/ui/src/components/SelectionBar.svelte:65-105` already use PillGroup or SelectionBar. - Row or tile actions keep their row style: `apps/web/src/lib/components/app-sidebar.svelte:389` (tree disclosure), `apps/web/src/lib/composer/Composer.svelte:1458,1464` (attachment tile), `apps/web/src/lib/files/FilesSidebar.svelte:172-174` (pinned row), `apps/web/src/lib/files/ShareDialog.svelte:311,359-360` (share/link rows), `apps/web/src/lib/files/InfoPanel.svelte:259` (Version row), `apps/web/src/lib/files/PublicLinkPage.svelte:685` (file row), `apps/web/src/lib/notifications/InboxPanel.svelte:359` (notification row), `packages/ui/src/components/calendar/AgendaList.svelte:300` (item row menu), `apps/web/src/lib/search/SavedSearchList.svelte:152` (saved-search row), and `apps/web/src/lib/files/UploadToast.svelte:137-144` (per-upload rows). - Form or inline editor controls: `apps/web/src/lib/components/TagEditor.svelte:112` (tag-chip removal), `apps/web/src/lib/search/SearchField.svelte:252,258,282` (filter-chip removal), and `apps/web/src/lib/photos/PhotoTimeline.svelte:603` (day checkbox). - CopyLink `variant="pill"` is row/field chrome at `apps/web/src/lib/components/NoteList.svelte:39`, `apps/web/src/routes/tag/[tag]/+page.svelte:132`, `apps/web/src/routes/tags/+page.svelte:41`, `apps/web/src/routes/settings/ai/TurnHistory.svelte:92`, `apps/web/src/lib/ai/TurnPanel.svelte:203`, and `apps/web/src/routes/settings/admin/InvitationsGroup.svelte:115`; these are row actions or a copy control beside an input. The heading and timeline uses are listed above for conversion. - Text-bearing controls are not icon-only: `packages/ui/src/components/NoticeBanner.svelte:62` (dismiss text), `apps/web/src/lib/auth/components/RecoveryKeyWords.svelte:70,77` (Copy words and Download), and `packages/ui/src/components/TodayButton.svelte:35` (Today label). The Search palette Escape behavior is keyboard-only and has no icon button. Files view selection is already in the shared segmented control.
Author
Owner

Built

Converted every item in the pre-edit lone-icon inventory above to the shared PillGroup, starting with the sidebar collapse/expand controls. Paired neighboring actions now share a toolbar group. Extended the shared group for inline linked-heading actions, added a PillGroup test, and added scripts/check-lone-icon-buttons.sh to the consistency audit. The numeric Composer weight found by the full test gate now uses var(--weight-semibold) (commit 8be72b93).

The complete pre-edit inventory was posted before changes. The branch is pushed to origin/job/single-pills.

Files

  • apps/web/src/lib/components/KeyboardShortcutsCard.svelte
  • apps/web/src/lib/components/PillGroup.svelte.test.ts
  • apps/web/src/lib/components/TagEditor.svelte
  • apps/web/src/lib/components/analytics/RangePicker.svelte
  • apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/composer/Composer.svelte
  • apps/web/src/lib/files/ShareDialog.svelte
  • apps/web/src/lib/files/UploadToast.svelte
  • apps/web/src/lib/notes/NotesExplorer.svelte
  • apps/web/src/lib/photos/PhotoTimeline.svelte
  • apps/web/src/lib/search/SearchPreview.svelte
  • apps/web/src/routes/+layout.svelte
  • apps/web/src/routes/layout.css
  • apps/web/src/routes/settings/[...path]/+page.svelte
  • docs/audits/consistency-2026-09-28.md
  • packages/ui/src/components/Inspector.svelte
  • packages/ui/src/components/LinkedHeading.svelte
  • packages/ui/src/components/ModeHeader.svelte
  • packages/ui/src/components/PillGroup.svelte
  • packages/ui/src/components/calendar/AgendaList.svelte
  • packages/ui/src/components/calendar/MiniMonth.svelte
  • scripts/check-lone-icon-buttons.sh

2× review crops

Decisions not covered by DESIGN

A lone action uses variant="single"; neighboring related actions use variant="toolbar". Composer switches to the toolbar group only while “Send all” is available. This applies #319’s grouping rule. No open DESIGN item was implemented.

Gates

cargo fmt --check completed with no output and exit code 0. git diff --check dev...HEAD also completed with no output.

cargo clippy --all-targets -- -D warnings first stopped because the shared sccache server used a missing temp directory from another worktree. Retried with RUSTC_WRAPPER unset for this command only. Final output:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 25m 37s

cargo test stopped in the Files crate. Exact failure summary:

test tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm ... FAILED

thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:3829:14:
writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(())

thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:3803:45:
atomic write 828 failed: entry not found

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

error: test failed, to rerun pass `-p calternal-plugin-files --lib`

The failure is filed as #337. The test was not rerun; later workspace test binaries did not run.

Final bun run check output:

Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/single-pills/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

The first full bun run test found the new Composer font-weight: 600 violation. After replacing it with the shared token, the guard passed (3 tests) and the full suite passed. Final summary:

Test Files  111 passed (111)
      Tests  711 passed (711)
   Start at  14:58:33
   Duration  137.66s (transform 56%, environment 16%, import 15%, tests 10%, setup 3%)

Adversarial round and gaps

One run was stopped at the 45-minute cap, at the Journal probes. Later probes in attack.py, plus media_uploads.py, hostile_bytes.mjs, attack2.py, and the restart probe did not run. The shared host had concurrent Cargo and adversarial jobs. Findings were recorded on existing issues: staged-search misses in #325, editor undo/redo text loss in #314, API timeouts in #269, and calendar write timeouts in #267. The concurrent delete/edit lost edit is filed as #332. Shared-host cause is not established. SLOW-only cases returned expected statuses and were treated as load.

Head

8be72b93657ba6c69a2125a0155eb9d957e7b119 (job/single-pills)

## Built Converted every item in the pre-edit lone-icon inventory above to the shared `PillGroup`, starting with the sidebar collapse/expand controls. Paired neighboring actions now share a toolbar group. Extended the shared group for inline linked-heading actions, added a PillGroup test, and added `scripts/check-lone-icon-buttons.sh` to the consistency audit. The numeric Composer weight found by the full test gate now uses `var(--weight-semibold)` (commit `8be72b93`). The complete pre-edit inventory was posted before changes. The branch is pushed to `origin/job/single-pills`. ## Files - `apps/web/src/lib/components/KeyboardShortcutsCard.svelte` - `apps/web/src/lib/components/PillGroup.svelte.test.ts` - `apps/web/src/lib/components/TagEditor.svelte` - `apps/web/src/lib/components/analytics/RangePicker.svelte` - `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/composer/Composer.svelte` - `apps/web/src/lib/files/ShareDialog.svelte` - `apps/web/src/lib/files/UploadToast.svelte` - `apps/web/src/lib/notes/NotesExplorer.svelte` - `apps/web/src/lib/photos/PhotoTimeline.svelte` - `apps/web/src/lib/search/SearchPreview.svelte` - `apps/web/src/routes/+layout.svelte` - `apps/web/src/routes/layout.css` - `apps/web/src/routes/settings/[...path]/+page.svelte` - `docs/audits/consistency-2026-09-28.md` - `packages/ui/src/components/Inspector.svelte` - `packages/ui/src/components/LinkedHeading.svelte` - `packages/ui/src/components/ModeHeader.svelte` - `packages/ui/src/components/PillGroup.svelte` - `packages/ui/src/components/calendar/AgendaList.svelte` - `packages/ui/src/components/calendar/MiniMonth.svelte` - `scripts/check-lone-icon-buttons.sh` ## 2× review crops - [Sidebar and navigation](https://git.kayg.org/attachments/03a5e7e1-6de5-45f8-97a3-6ecc0647b0aa) - [Overlay close controls](https://git.kayg.org/attachments/7ce646be-6057-41bf-ac24-25606c0fe5b7) - [Calendar and Notes](https://git.kayg.org/attachments/43d9723e-75d9-44f8-9aa5-2d6ec588e315) - [Search, Photos, and Composer](https://git.kayg.org/attachments/2024d200-52eb-4d12-bccd-00a227f2fda8) - [Linked heading](https://git.kayg.org/attachments/82d241b3-f0b7-4b5f-b4e6-bfb37338bcc3) - [Compact Settings Back button](https://git.kayg.org/attachments/ad99fb90-5b3a-40db-9db0-16aa3afdfcec) ## Decisions not covered by DESIGN A lone action uses `variant="single"`; neighboring related actions use `variant="toolbar"`. Composer switches to the toolbar group only while “Send all” is available. This applies #319’s grouping rule. No open DESIGN item was implemented. ## Gates `cargo fmt --check` completed with no output and exit code 0. `git diff --check dev...HEAD` also completed with no output. `cargo clippy --all-targets -- -D warnings` first stopped because the shared sccache server used a missing temp directory from another worktree. Retried with `RUSTC_WRAPPER` unset for this command only. Final output: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 25m 37s ``` `cargo test` stopped in the Files crate. Exact failure summary: ```text test tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm ... FAILED thread 'tests::internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm' panicked at crates/plugins/files/src/lib.rs:3829:14: writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(()) thread 'tokio-rt-worker' panicked at crates/plugins/files/src/lib.rs:3803:45: atomic write 828 failed: entry not found test result: FAILED. 119 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 322.25s error: test failed, to rerun pass `-p calternal-plugin-files --lib` ``` The failure is filed as [#337](https://git.kayg.org/kayg/calternal/issues/337). The test was not rerun; later workspace test binaries did not run. Final `bun run check` output: ```text Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/single-pills/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` The first full `bun run test` found the new Composer `font-weight: 600` violation. After replacing it with the shared token, the guard passed (3 tests) and the full suite passed. Final summary: ```text Test Files 111 passed (111) Tests 711 passed (711) Start at 14:58:33 Duration 137.66s (transform 56%, environment 16%, import 15%, tests 10%, setup 3%) ``` ## Adversarial round and gaps One run was stopped at the 45-minute cap, at the Journal probes. Later probes in `attack.py`, plus `media_uploads.py`, `hostile_bytes.mjs`, `attack2.py`, and the restart probe did not run. The shared host had concurrent Cargo and adversarial jobs. Findings were recorded on existing issues: staged-search misses in [#325](https://git.kayg.org/kayg/calternal/issues/325), editor undo/redo text loss in [#314](https://git.kayg.org/kayg/calternal/issues/314), API timeouts in [#269](https://git.kayg.org/kayg/calternal/issues/269), and calendar write timeouts in [#267](https://git.kayg.org/kayg/calternal/issues/267). The concurrent delete/edit lost edit is filed as [#332](https://git.kayg.org/kayg/calternal/issues/332). Shared-host cause is not established. SLOW-only cases returned expected statuses and were treated as load. ## Head `8be72b93657ba6c69a2125a0155eb9d957e7b119` (`job/single-pills`)
Author
Owner

Orchestrator review: the converted controls look right (sidebar toggle, share/inspector/shortcuts/tag close, photo back: consistent single pills with the glass ring, centred glyphs). Two evidence problems before merge:

  1. tag-editor-close, Desktop · Paper: the crop shows a half-clipped button ('<' cut off at the left edge, a vertical bar at the right). Either the crop is mis-framed or the button is really clipped inside the tag editor. Check it; if it is clipped, fix it.
  2. settings-back 'Not rendered at this width' at all four sizes: the Settings back button (large < on phone, small on desktop; owner decision) exists, so the capture selector is wrong. Capture it at 390/820/1440.
  3. Add the 820 px column (the rule: 390/820/1440).
    Then merge dev once, push, and report.
Orchestrator review: the converted controls look right (sidebar toggle, share/inspector/shortcuts/tag close, photo back: consistent single pills with the glass ring, centred glyphs). Two evidence problems before merge: 1. **tag-editor-close, Desktop · Paper:** the crop shows a half-clipped button ('<' cut off at the left edge, a vertical bar at the right). Either the crop is mis-framed or the button is really clipped inside the tag editor. Check it; if it is clipped, fix it. 2. **settings-back 'Not rendered at this width' at all four sizes**: the Settings back button (large < on phone, small on desktop; owner decision) exists, so the capture selector is wrong. Capture it at 390/820/1440. 3. Add the 820 px column (the rule: 390/820/1440). Then merge dev once, push, and report.
Author
Owner

Owner report (2026-09-28, screenshot): the Inspector popover's 'Info' title sits on the panel's rounded corner, across the border, instead of inside the content padding (Inspector.svelte: the surface has 6 px padding, and the header/title gets no inner padding of its own, while the body uses 0.6rem). You are editing Inspector.svelte already, so fix it here:

  • The header (title + close pill) uses the same inner inset as the body (a token), so the title's left edge aligns with the body text and clears the corner radius (inset ≥ radius × 0.3, the usual optical rule).
  • Title and close pill share one vertical centre line (cap-centred, #253 rules).
  • Check it both as the desktop popover and as the phone sheet, where the title may use the large sheet title; the same rule holds there.
  • Also check every other PopoverSurface consumer with a header (share dialog, tag editor, link dialog) for the same inset issue; fix it in PopoverSurface once, not per consumer.
    Evidence: 2× crops of the Inspector (and one other header popover) at 390/820/1440, light and dark.
Owner report (2026-09-28, screenshot): the Inspector popover's **'Info' title sits on the panel's rounded corner**, across the border, instead of inside the content padding (Inspector.svelte: the surface has 6 px padding, and the header/title gets no inner padding of its own, while the body uses 0.6rem). You are editing Inspector.svelte already, so fix it here: - The header (title + close pill) uses the same inner inset as the body (a token), so the title's left edge aligns with the body text and clears the corner radius (inset ≥ radius × 0.3, the usual optical rule). - Title and close pill share one vertical centre line (cap-centred, #253 rules). - Check it both as the desktop popover and as the phone sheet, where the title may use the large sheet title; the same rule holds there. - Also check every other PopoverSurface consumer with a header (share dialog, tag editor, link dialog) for the same inset issue; fix it in PopoverSurface once, not per consumer. Evidence: 2× crops of the Inspector (and one other header popover) at 390/820/1440, light and dark.
Author
Owner

Follow-up started on job/single-pills at HEAD 8be72b93657ba6c69a2125a0155eb9d957e7b119. Current dev SHA: 442b4f000b7e851023e6c0ae792e89c52e01969e; current merge base: b64fee55c16dc31e400f76934084fda6b29acad2. I am checking the tag-editor crop, Settings Back capture, and the shared PopoverSurface header inset, then I will capture the requested viewport/theme evidence.

Follow-up started on `job/single-pills` at HEAD `8be72b93657ba6c69a2125a0155eb9d957e7b119`. Current `dev` SHA: `442b4f000b7e851023e6c0ae792e89c52e01969e`; current merge base: `b64fee55c16dc31e400f76934084fda6b29acad2`. I am checking the tag-editor crop, Settings Back capture, and the shared PopoverSurface header inset, then I will capture the requested viewport/theme evidence.
Author
Owner

Inspector inset finding

The production E2E reproduced the owner report before the CSS change: at 1440 px the Inspector title started at x=544.72 px while its body text started at x=554.32 px (9.6 px apart). I added the shared --popover-content-inset to the PopoverSurface header and used that token for the Inspector body. The same real-server alignment assertion now passes at 1440, 820 and 390 px.

The broader test:e2e:popovers run proceeds through the Inspector checks, then fails at its Search glass-class assertion (Search uses the shared clear glass paint); no existing expectation was changed. I am investigating whether the viewport sequence affects that test and will keep its current expectation.

## Inspector inset finding The production E2E reproduced the owner report before the CSS change: at 1440 px the Inspector title started at x=544.72 px while its body text started at x=554.32 px (9.6 px apart). I added the shared `--popover-content-inset` to the PopoverSurface header and used that token for the Inspector body. The same real-server alignment assertion now passes at 1440, 820 and 390 px. The broader `test:e2e:popovers` run proceeds through the Inspector checks, then fails at its Search glass-class assertion (`Search uses the shared clear glass paint`); no existing expectation was changed. I am investigating whether the viewport sequence affects that test and will keep its current expectation.
Author
Owner

Follow-up visual finding

The first alignment assertion compared the title with .inspector-body padding, not the visible Details labels. Reviewing the production crops showed that InfoPanel .info adds another 18 px of horizontal padding, leaving the title 18 px left of the visible labels even after the first shared inset change. I am tightening the E2E assertion to compare against the real Details label and will remove the duplicate inner padding so the shared Inspector inset controls both.

## Follow-up visual finding The first alignment assertion compared the title with `.inspector-body` padding, not the visible Details labels. Reviewing the production crops showed that `InfoPanel .info` adds another 18 px of horizontal padding, leaving the title 18 px left of the visible labels even after the first shared inset change. I am tightening the E2E assertion to compare against the real Details label and will remove the duplicate inner padding so the shared Inspector inset controls both.
Author
Owner

The shared Inspector inset change is committed as 2f955787c2ec3274b4b92695fabf11e048223a63. A real production build verified exact title-to-Details-label alignment at 390, 820, and 1440 px; the TagEditor close pill also remains inside its surface at all three widths. The production screenshot matrix has zero page errors. I am attaching the final light/dark crops next.

The targeted test:e2e:popovers reaches PASS Files Inspector selection and Escape (including the new three-width alignment assertions), then stops at the existing Search uses the shared clear glass paint assertion. I left that expectation unchanged.

The shared Inspector inset change is committed as `2f955787c2ec3274b4b92695fabf11e048223a63`. A real production build verified exact title-to-Details-label alignment at 390, 820, and 1440 px; the TagEditor close pill also remains inside its surface at all three widths. The production screenshot matrix has zero page errors. I am attaching the final light/dark crops next. The targeted `test:e2e:popovers` reaches `PASS Files Inspector selection and Escape` (including the new three-width alignment assertions), then stops at the existing `Search uses the shared clear glass paint` assertion. I left that expectation unchanged.
Author
Owner
### Inspector header and Details - [inspector-1440-dark](https://git.kayg.org/attachments/9bcdec1c-cf02-4292-a7c5-494ae119a20a) - [inspector-1440-light](https://git.kayg.org/attachments/ad4778bf-b1f9-4632-a009-5dd567e2904f) - [inspector-390-dark](https://git.kayg.org/attachments/28f1536b-22d0-456e-beda-a0a2d2268b3f) - [inspector-390-light](https://git.kayg.org/attachments/106d8f9b-f52b-4703-a581-a1c6e9a2f083) - [inspector-820-dark](https://git.kayg.org/attachments/b9030ebf-b98e-4b56-8d52-de94d1ee9395) - [inspector-820-light](https://git.kayg.org/attachments/8e4afc9c-254c-40fe-af80-8ad6696f42dd) ### TagEditor and close pill - [tag-editor-1440-dark](https://git.kayg.org/attachments/15edd0a3-7e24-478f-8cd7-74e118a50951) - [tag-editor-1440-light](https://git.kayg.org/attachments/fecd5c29-25d8-4a97-a2ec-a5b5b5f6ae21) - [tag-editor-390-dark](https://git.kayg.org/attachments/ef007a2e-a517-46a7-a598-a409b4eb2e4f) - [tag-editor-390-light](https://git.kayg.org/attachments/e75cb20c-8302-427e-bff1-e580ac1a8275) - [tag-editor-820-dark](https://git.kayg.org/attachments/dc9a6d4c-798c-4a03-b8cc-dd90ad9234d7) - [tag-editor-820-light](https://git.kayg.org/attachments/22ef7053-3fed-47ff-afb2-31f1ca5d9c0a) - [tag-editor-close-1440-dark](https://git.kayg.org/attachments/eb4faadd-f1ff-4ebe-b700-d8d5bb01780a) - [tag-editor-close-1440-light](https://git.kayg.org/attachments/d51e7348-4deb-4a4e-ad72-e959db610807) - [tag-editor-close-390-dark](https://git.kayg.org/attachments/1ec6e872-8e0d-48e1-872b-84e45c7bd41d) - [tag-editor-close-390-light](https://git.kayg.org/attachments/de398843-72d9-4ab8-838a-5806ddf4b0ee) - [tag-editor-close-820-dark](https://git.kayg.org/attachments/9bdaebe8-507d-45bc-8a72-2c0c0de15910) - [tag-editor-close-820-light](https://git.kayg.org/attachments/750d575e-88d7-4afa-a4c5-b9c2c4b813f5) ### Settings Back at 390 and 820 px - [settings-back-390-dark](https://git.kayg.org/attachments/b4d40a3b-4c64-4d69-b008-7794f53a88e7) - [settings-back-390-light](https://git.kayg.org/attachments/9fd4262c-7378-4962-ba46-47c424ebf363) - [settings-back-820-dark](https://git.kayg.org/attachments/63d315eb-be20-4668-bd1d-3f3f113c80dd) - [settings-back-820-light](https://git.kayg.org/attachments/c0ce3d45-fe6d-4894-be09-92dbcfad61c6) ### Settings heading at 1440 px - [settings-wide-heading-1440-dark](https://git.kayg.org/attachments/6ba0ec6e-14cc-4c6c-be8e-552932bc4bba) - [settings-wide-heading-1440-light](https://git.kayg.org/attachments/4897e63a-b5ef-4abd-904e-40ae82a10628)
Author
Owner
### Inspector header and Details - [inspector-1440-dark](https://git.kayg.org/attachments/9bcdec1c-cf02-4292-a7c5-494ae119a20a) - [inspector-1440-light](https://git.kayg.org/attachments/ad4778bf-b1f9-4632-a009-5dd567e2904f) - [inspector-390-dark](https://git.kayg.org/attachments/28f1536b-22d0-456e-beda-a0a2d2268b3f) - [inspector-390-light](https://git.kayg.org/attachments/106d8f9b-f52b-4703-a581-a1c6e9a2f083) - [inspector-820-dark](https://git.kayg.org/attachments/b9030ebf-b98e-4b56-8d52-de94d1ee9395) - [inspector-820-light](https://git.kayg.org/attachments/8e4afc9c-254c-40fe-af80-8ad6696f42dd) ### TagEditor and close pill - [tag-editor-1440-dark](https://git.kayg.org/attachments/15edd0a3-7e24-478f-8cd7-74e118a50951) - [tag-editor-1440-light](https://git.kayg.org/attachments/fecd5c29-25d8-4a97-a2ec-a5b5b5f6ae21) - [tag-editor-390-dark](https://git.kayg.org/attachments/ef007a2e-a517-46a7-a598-a409b4eb2e4f) - [tag-editor-390-light](https://git.kayg.org/attachments/e75cb20c-8302-427e-bff1-e580ac1a8275) - [tag-editor-820-dark](https://git.kayg.org/attachments/dc9a6d4c-798c-4a03-b8cc-dd90ad9234d7) - [tag-editor-820-light](https://git.kayg.org/attachments/22ef7053-3fed-47ff-afb2-31f1ca5d9c0a) - [tag-editor-close-1440-dark](https://git.kayg.org/attachments/eb4faadd-f1ff-4ebe-b700-d8d5bb01780a) - [tag-editor-close-1440-light](https://git.kayg.org/attachments/d51e7348-4deb-4a4e-ad72-e959db610807) - [tag-editor-close-390-dark](https://git.kayg.org/attachments/1ec6e872-8e0d-48e1-872b-84e45c7bd41d) - [tag-editor-close-390-light](https://git.kayg.org/attachments/de398843-72d9-4ab8-838a-5806ddf4b0ee) - [tag-editor-close-820-dark](https://git.kayg.org/attachments/9bdaebe8-507d-45bc-8a72-2c0c0de15910) - [tag-editor-close-820-light](https://git.kayg.org/attachments/750d575e-88d7-4afa-a4c5-b9c2c4b813f5) ### Settings Back at 390 and 820 px - [settings-back-390-dark](https://git.kayg.org/attachments/b4d40a3b-4c64-4d69-b008-7794f53a88e7) - [settings-back-390-light](https://git.kayg.org/attachments/9fd4262c-7378-4962-ba46-47c424ebf363) - [settings-back-820-dark](https://git.kayg.org/attachments/63d315eb-be20-4668-bd1d-3f3f113c80dd) - [settings-back-820-light](https://git.kayg.org/attachments/c0ce3d45-fe6d-4894-be09-92dbcfad61c6) ### Settings heading at 1440 px - [settings-wide-heading-1440-dark](https://git.kayg.org/attachments/6ba0ec6e-14cc-4c6c-be8e-552932bc4bba) - [settings-wide-heading-1440-light](https://git.kayg.org/attachments/4897e63a-b5ef-4abd-904e-40ae82a10628)
Author
Owner
Final post-merge screenshot evidence (2× crops; 390, 820, and 1440 px where the view is present) ### Inspector header and Details - [inspector-1440-dark](https://git.kayg.org/attachments/10dbd038-5131-45eb-a869-fe581aa869a8) - [inspector-1440-light](https://git.kayg.org/attachments/48db6ac7-7dc3-45a0-8329-a005aae0e0f8) - [inspector-390-dark](https://git.kayg.org/attachments/7ae1fb1f-7bf9-46ab-a6b3-8001fef425eb) - [inspector-390-light](https://git.kayg.org/attachments/3438f881-c2b2-498d-94ba-cc8487830154) - [inspector-820-dark](https://git.kayg.org/attachments/fbcc76cc-447a-4c7a-abb3-0bebd8bd6e78) - [inspector-820-light](https://git.kayg.org/attachments/c786efdc-e0c8-4f2e-9c89-a5a2493ea43e) ### TagEditor and close pill - [tag-editor-1440-dark](https://git.kayg.org/attachments/c9412d7d-5b20-4145-8c8b-991377901964) - [tag-editor-1440-light](https://git.kayg.org/attachments/4793e430-00f6-49cb-bff4-5f33fde93ce0) - [tag-editor-390-dark](https://git.kayg.org/attachments/9d425645-97bf-4fbe-8671-548a616db3c7) - [tag-editor-390-light](https://git.kayg.org/attachments/78f2eb58-1189-46c4-ab1a-b07a9bb82b03) - [tag-editor-820-dark](https://git.kayg.org/attachments/13c0d00a-caba-479b-97ba-f0652439c947) - [tag-editor-820-light](https://git.kayg.org/attachments/048016c9-9b10-456c-bae3-bfa8691ddd5a) - [tag-editor-close-1440-dark](https://git.kayg.org/attachments/2daeccb4-b87c-4a46-8a34-fe99c7f63061) - [tag-editor-close-1440-light](https://git.kayg.org/attachments/28c162c8-09ef-46bc-8949-36ce93f699d9) - [tag-editor-close-390-dark](https://git.kayg.org/attachments/afa8b4cd-3a20-477a-81f2-ec089e251d24) - [tag-editor-close-390-light](https://git.kayg.org/attachments/7690f25d-3e1e-44b0-a61f-0fc6facf783b) - [tag-editor-close-820-dark](https://git.kayg.org/attachments/7a08371f-27fb-40ef-a630-5b9eac851e89) - [tag-editor-close-820-light](https://git.kayg.org/attachments/7aec699a-f1b0-4a46-b739-1ae815d28689) ### Settings Back at 390 and 820 px - [settings-back-390-dark](https://git.kayg.org/attachments/44170049-c8d5-4742-9201-77025e11182a) - [settings-back-390-light](https://git.kayg.org/attachments/ae9c9f1e-44f3-4ddd-b758-64c3f41d3e77) - [settings-back-820-dark](https://git.kayg.org/attachments/0880687a-a58c-4fb9-ad1f-ba6587122544) - [settings-back-820-light](https://git.kayg.org/attachments/0cf95565-0913-4e50-a92e-b6cf2642a07b) ### Settings heading at 1440 px - [settings-wide-heading-1440-dark](https://git.kayg.org/attachments/09e7801a-a667-4cba-bf92-75e0063e900a) - [settings-wide-heading-1440-light](https://git.kayg.org/attachments/9f1f8244-329e-49e3-8d07-7c3f91c70a67)
Author
Owner

Finished Forgejo #319 on job/single-pills.

Head: 3bf4078ad91e711e0ae75bd531307e82e6a90343 (merge commit with dev). git push origin job/single-pills returned Everything up-to-date; git ls-remote confirms this SHA is the remote branch tip.

Built

  • Added the shared --popover-content-inset token and used it for PopoverSurface header content and Inspector details content. Removed InfoPanel's duplicate horizontal inset so its Details labels align with the header title.
  • Added a browser regression assertion for Inspector title and Details alignment at 390, 820, and 1440 px.
  • Resolved the NotesExplorer.svelte merge conflict by keeping both Dev's SidebarSectionHeader/Vault Health action and the job branch's grouped Notes actions.
  • Files: packages/ui/src/tokens.css, packages/ui/src/components/PopoverSurface.svelte, packages/ui/src/components/Inspector.svelte, apps/web/src/lib/files/InfoPanel.svelte, apps/web/e2e/popovers.mjs, and apps/web/src/lib/notes/NotesExplorer.svelte.

The final post-merge 2× crops are attached in the preceding comment: Inspector and TagEditor at 390/820/1440 in light/dark; Settings Back at 390/820 and the wide Settings heading at 1440 in light/dark. The title and Details left edges measured equal at all three Inspector widths. The TagEditor close pill remained within its surface at every width.

Gates

All gates ran after the one git merge dev.

  • cargo fmt --check: exit 0, no output.

  • cargo clippy --all-targets -- -D warnings:

    Finished 'dev' profile [unoptimized + debuginfo] target(s) in 14m 08s
    
  • cargo test: exit 0. The saved output contained 73 passing suite results: 1,378 passed, 0 failed, 12 ignored. The final suite printed:

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    
  • bun run check:

    svelte-check found 0 errors and 0 warnings
    
  • bun run test:

     Test Files  113 passed (113)
          Tests  728 passed (728)
    

Known gap

The targeted apps/web/e2e/popovers.mjs run passed the Inspector selection/Escape check, then failed at the existing Search uses the shared clear glass paint assertion. I left that existing expectation unchanged. This assertion failure is separate from the passing gates above.

Decision

The design doc does not set this popover inset. I used one shared 0.6rem token for the header and Inspector details so both align, and removed the duplicate app-level inset.

Finished Forgejo #319 on `job/single-pills`. Head: `3bf4078ad91e711e0ae75bd531307e82e6a90343` (merge commit with `dev`). `git push origin job/single-pills` returned `Everything up-to-date`; `git ls-remote` confirms this SHA is the remote branch tip. ## Built - Added the shared `--popover-content-inset` token and used it for PopoverSurface header content and Inspector details content. Removed InfoPanel's duplicate horizontal inset so its Details labels align with the header title. - Added a browser regression assertion for Inspector title and Details alignment at 390, 820, and 1440 px. - Resolved the `NotesExplorer.svelte` merge conflict by keeping both Dev's SidebarSectionHeader/Vault Health action and the job branch's grouped Notes actions. - Files: `packages/ui/src/tokens.css`, `packages/ui/src/components/PopoverSurface.svelte`, `packages/ui/src/components/Inspector.svelte`, `apps/web/src/lib/files/InfoPanel.svelte`, `apps/web/e2e/popovers.mjs`, and `apps/web/src/lib/notes/NotesExplorer.svelte`. The final post-merge 2× crops are attached in the preceding comment: Inspector and TagEditor at 390/820/1440 in light/dark; Settings Back at 390/820 and the wide Settings heading at 1440 in light/dark. The title and Details left edges measured equal at all three Inspector widths. The TagEditor close pill remained within its surface at every width. ## Gates All gates ran after the one `git merge dev`. - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: ``` Finished 'dev' profile [unoptimized + debuginfo] target(s) in 14m 08s ``` - `cargo test`: exit 0. The saved output contained 73 passing suite results: 1,378 passed, 0 failed, 12 ignored. The final suite printed: ``` test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` - `bun run check`: ``` svelte-check found 0 errors and 0 warnings ``` - `bun run test`: ``` Test Files 113 passed (113) Tests 728 passed (728) ``` ## Known gap The targeted `apps/web/e2e/popovers.mjs` run passed the Inspector selection/Escape check, then failed at the existing `Search uses the shared clear glass paint` assertion. I left that existing expectation unchanged. This assertion failure is separate from the passing gates above. ## Decision The design doc does not set this popover inset. I used one shared `0.6rem` token for the header and Inspector details so both align, and removed the duplicate app-level inset.
Author
Owner

Consistency-hunt update from current dev at dfb5964a2fcf13dc8b9a50a319eee09bc386f322 (2026-09-30). A static Svelte AST scan found 59 icon-only <button> candidates outside ancestors named PillGroup, SelectionBar or MenuItem. This is a candidate list: it still includes row actions, chip controls, toolbar controls and other cases that §319 says to classify as exclusions. Clear surfaces to review include the sidebar toggle (apps/web/src/routes/+layout.svelte:613), Settings RangePicker arrows (apps/web/src/lib/components/analytics/RangePicker.svelte:318,330), inspector close (packages/ui/src/components/Inspector.svelte:77), Quick Look controls (packages/ui/src/components/viewer/QuickLook.svelte:273-284), MiniMonth arrows (packages/ui/src/components/calendar/MiniMonth.svelte:137-138) and the Notes actions button (apps/web/src/lib/notes/NotesExplorer.svelte:393,396).

Rule: every standalone icon action uses the shared single PillGroup; adjacent actions form one group. Fix owner: packages/ui/src/components/PillGroup.svelte. The audit keeps the full candidate set so each row action, inline control and form control can be marked with its exclusion reason before conversion.

Consistency-hunt update from current `dev` at `dfb5964a2fcf13dc8b9a50a319eee09bc386f322` (2026-09-30). A static Svelte AST scan found 59 icon-only `<button>` candidates outside ancestors named `PillGroup`, `SelectionBar` or `MenuItem`. This is a candidate list: it still includes row actions, chip controls, toolbar controls and other cases that §319 says to classify as exclusions. Clear surfaces to review include the sidebar toggle (`apps/web/src/routes/+layout.svelte:613`), Settings RangePicker arrows (`apps/web/src/lib/components/analytics/RangePicker.svelte:318,330`), inspector close (`packages/ui/src/components/Inspector.svelte:77`), Quick Look controls (`packages/ui/src/components/viewer/QuickLook.svelte:273-284`), MiniMonth arrows (`packages/ui/src/components/calendar/MiniMonth.svelte:137-138`) and the Notes actions button (`apps/web/src/lib/notes/NotesExplorer.svelte:393,396`). Rule: every standalone icon action uses the shared single `PillGroup`; adjacent actions form one group. Fix owner: `packages/ui/src/components/PillGroup.svelte`. The audit keeps the full candidate set so each row action, inline control and form control can be marked with its exclusion reason before conversion.
Author
Owner

Hygiene review: the current-dev scan still has 59 icon/label candidates to classify. Keeping #319 open until that sweep is complete.

Hygiene review: the current-dev scan still has 59 icon/label candidates to classify. Keeping #319 open until that sweep is complete.
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#319
No description provided.