Notes: text formatting toolbar lost its glass and got narrower #1210

Open
opened 2026-10-06 16:30:39 +00:00 by kayg · 9 comments
Owner

Owner report (2026-10-06, production ff53d7b8b): the Notes text-formatting toolbar (EditorFormatBubble → shared PillGroup frosted scroll) became narrower and lost its blurred glass surface. It now reads as a flat dark pill; the "Heading 1" segment pokes out as its own lighter box, and the "Text style: Heading 1" tooltip overlaps the note card.

The toolbar source files (EditorFormatBubble, FormatCommandRow, FormatButton) and PillGroup.svelte have not changed since 10-03, so the cause is elsewhere: a shared token, theme CSS, a backdrop-filter ancestor (a parent with transform/filter/will-change/contain breaks backdrop-filter for fixed children), portalToBody target, or the scroll mode's width clamp.

Do:

  1. Reproduce in a production build: select text in a note, screenshot the bubble at 1x and 2x next to the block-selection toolbar (the size the owner prefers).
  2. Find the regressing commit (bisect between a known-good build and ff53d7b8b; check git log -S on glass/backdrop tokens and the portal root). Report the commit.
  3. Fix at the source (shared primitive or token), never by restyling the bubble locally. The bubble must be the shared frosted glass with real backdrop blur, the same size as the block toolbar, with the active "Heading 1" segment inside the pill and no separate box.
  4. Add an e2e that asserts computed backdrop-filter is not none on the bubble surface and that its height/width matches the block toolbar within 1 px.
  5. Screenshots at actual size in the issue.

Related: #1189 (block action capsule glass, fixed on branch, not deployed yet), #1209.

Owner report (2026-10-06, production ff53d7b8b): the Notes text-formatting toolbar (EditorFormatBubble → shared `PillGroup frosted scroll`) became **narrower** and **lost its blurred glass surface**. It now reads as a flat dark pill; the "Heading 1" segment pokes out as its own lighter box, and the "Text style: Heading 1" tooltip overlaps the note card. The toolbar source files (EditorFormatBubble, FormatCommandRow, FormatButton) and PillGroup.svelte have not changed since 10-03, so the cause is elsewhere: a shared token, theme CSS, a backdrop-filter ancestor (a parent with `transform`/`filter`/`will-change`/`contain` breaks backdrop-filter for fixed children), `portalToBody` target, or the `scroll` mode's width clamp. Do: 1. Reproduce in a production build: select text in a note, screenshot the bubble at 1x and 2x next to the block-selection toolbar (the size the owner prefers). 2. Find the regressing commit (bisect between a known-good build and ff53d7b8b; check `git log -S` on glass/backdrop tokens and the portal root). Report the commit. 3. Fix at the source (shared primitive or token), never by restyling the bubble locally. The bubble must be the shared frosted glass with real backdrop blur, the same size as the block toolbar, with the active "Heading 1" segment inside the pill and no separate box. 4. Add an e2e that asserts computed `backdrop-filter` is not `none` on the bubble surface and that its height/width matches the block toolbar within 1 px. 5. Screenshots at actual size in the issue. Related: #1189 (block action capsule glass, fixed on branch, not deployed yet), #1209.
Author
Owner

Starting #1210 on branch job/fmtbar-1210, based on origin/dev at ff53d7b8b4da5a21f862d57dcc3496f8c019f647. I am reproducing the Notes format bubble in a production build and tracing shared surface/portal changes before fixing the shared source.

Starting #1210 on branch `job/fmtbar-1210`, based on `origin/dev` at `ff53d7b8b4da5a21f862d57dcc3496f8c019f647`. I am reproducing the Notes format bubble in a production build and tracing shared surface/portal changes before fixing the shared source.
Author
Owner

History finding for #1210: between the Oct 3 production baseline 4012c07da7009662785435c679414bc7d11351c6 and ff53d7b8b, EditorFormatBubble.svelte, FormatCommandRow.svelte, FormatButton.svelte, PillGroup.svelte, and actions/portal.ts have no changes. The only matching blur-suspension addition in that range is 6f560927e; its selector excludes the direct body portal layer, so it should not affect the format capsule. I am checking the live production DOM next to identify the actual paint and sizing cause before choosing a fix.

History finding for #1210: between the Oct 3 production baseline `4012c07da7009662785435c679414bc7d11351c6` and `ff53d7b8b`, `EditorFormatBubble.svelte`, `FormatCommandRow.svelte`, `FormatButton.svelte`, `PillGroup.svelte`, and `actions/portal.ts` have no changes. The only matching blur-suspension addition in that range is `6f560927e`; its selector excludes the direct body portal layer, so it should not affect the format capsule. I am checking the live production DOM next to identify the actual paint and sizing cause before choosing a fix.
Author
Owner

Production-DOM finding on the local production build: the selected Heading 1 bubble is 330 × 34 CSS px, is a direct child of the shared body overlay layer, and computes backdrop-filter: blur(16px) saturate(1.8) brightness(1.05). Its only ancestor is the display: contents overlay layer, with no transform/filter/contain; the new Calendar suspension rule is not suppressing it. The Heading 1 tooltip currently sits at y=150–179 while the Note card spans y=80–1527, so it overlaps the card. I am capturing the requested light/dark and 1×/2× renders and measuring the block-selection capsule next.

Production-DOM finding on the local production build: the selected Heading 1 bubble is 330 × 34 CSS px, is a direct child of the shared body overlay layer, and computes `backdrop-filter: blur(16px) saturate(1.8) brightness(1.05)`. Its only ancestor is the `display: contents` overlay layer, with no transform/filter/contain; the new Calendar suspension rule is not suppressing it. The Heading 1 tooltip currently sits at y=150–179 while the Note card spans y=80–1527, so it overlaps the card. I am capturing the requested light/dark and 1×/2× renders and measuring the block-selection capsule next.
Author
Owner

Result

Restored the Notes text-formatting capsule through the shared PillGroup and tooltip layer. The active Heading 1 segment stays inside the capsule, and its tooltip no longer overlaps the Note card. The production E2E checks real computed backdrop blur and compares the format capsule with the block-selection toolbar within 1 px.

Expected cost: showing the tooltip performs one obstacle selector lookup and reads one Note-card rectangle. The capsule uses its existing horizontal scroll row. There is no per-item observer or obstacle-list scan.

Regression investigation

Reviewed history from 4012c07da7009662785435c679414bc7d11351c6 through production ff53d7b8b4da5a21f862d57dcc3496f8c019f647. EditorFormatBubble.svelte, FormatCommandRow.svelte, FormatButton.svelte and PillGroup.svelte were unchanged in that range. git log -S 'backdrop-filter' found 6f560927e; its calendar-travel rule excludes the body overlay layer used by this capsule. I could not identify a commit that caused the reported blur loss. In the final production build, the format capsule reports blur(16px) saturate(1.8) brightness(1.05) and the block toolbar reports blur(16px) saturate(1.5).

UX gaps closed

  • Format and block capsules both measure 94 × 34 px at fine pointer size; the coarse-pointer format capsule is 142 × 44 px.
  • Heading 1 stays within the shared pill row. Its compact label keeps its accessible name.
  • The E2E covers 390, 820 and 1440 px in light and dark themes, plus Mac-emulated 1× and 2× crops.
  • The tooltip stays clear of both the Note card and selected text.

UX gaps left: the commit behind the reported blur loss remains unknown because that state does not reproduce in the current production build.

Screenshots

The following crops are at their actual 1× and 2× pixel sizes.

Theme / scale Format toolbar Block-selection toolbar
Light, 1× Format capsule, light 1x Block capsule, light 1x
Light, 2× Format capsule, light 2x Block capsule, light 2x
Dark, 1× Format capsule, dark 1x Block capsule, dark 1x
Dark, 2× Format capsule, dark 2x Block capsule, dark 2x

Responsive production screenshots: 390 px light, 390 px dark, 820 px light, 820 px dark, 1440 px light, 1440 px dark.

Decisions

DESIGN does not set a narrow-width size for this shared scroll capsule. I used 94 × 34 px for fine pointers to match the block toolbar, and 142 × 44 px for coarse pointers to preserve a 44 px touch target while keeping the commands in the existing scroll row. The shared PillGroup owns the capsule styling; the feature does not add a local surface.

Files

  • apps/web/e2e/notes.mjs
  • apps/web/src/lib/components/PillGroup.svelte.test.ts
  • apps/web/src/lib/editor/format/EditorFormatBubble.svelte
  • apps/web/src/lib/editor/format/FormatButton.svelte
  • apps/web/src/lib/editor/format/FormatButton.svelte.test.ts
  • apps/web/src/lib/editor/format/FormatCommandRow.svelte
  • packages/ui/src/components/PillGroup.svelte
  • packages/ui/src/components/tooltip/TooltipLayer.svelte
  • contracts/perf/exceptions.json
  • contracts/perf/registry.json

Verification

cargo fmt --check: passed; no output. No Rust files changed, so Rust clippy and crate tests were not run.

cd apps/web && bun run check output:

perf-lint: PASS; 0 violations; 22615 scoped exceptions
PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
svelte-check found 0 errors and 2 warnings in 2 files

The two Svelte warnings are the existing empty CSS rules in AttachmentDeck.svelte and AgendaList.svelte.

cd apps/web && bun run test summary:

Ran 136 tests in 0.086s
OK
8 pass
0 fail
16 expect() calls
Test Files  283 passed (283)
     Tests  1981 passed (1981)

Focused Vitest: 2 files and 5 tests passed.

Production E2E output:

notes #1210 format capsule e2e: real backdrop, matching capsule geometry and responsive screenshots passed
CSP REPORTS notes: 0 across 3 pages

Head: d8cca81dde1512a3e656aed5e7c5ecec7f410de1 (perf(ui): bound tooltip obstacle lookup (#1210)).

## Result Restored the Notes text-formatting capsule through the shared `PillGroup` and tooltip layer. The active Heading 1 segment stays inside the capsule, and its tooltip no longer overlaps the Note card. The production E2E checks real computed backdrop blur and compares the format capsule with the block-selection toolbar within 1 px. Expected cost: showing the tooltip performs one obstacle selector lookup and reads one Note-card rectangle. The capsule uses its existing horizontal scroll row. There is no per-item observer or obstacle-list scan. ### Regression investigation Reviewed history from `4012c07da7009662785435c679414bc7d11351c6` through production `ff53d7b8b4da5a21f862d57dcc3496f8c019f647`. `EditorFormatBubble.svelte`, `FormatCommandRow.svelte`, `FormatButton.svelte` and `PillGroup.svelte` were unchanged in that range. `git log -S 'backdrop-filter'` found `6f560927e`; its calendar-travel rule excludes the body overlay layer used by this capsule. I could not identify a commit that caused the reported blur loss. In the final production build, the format capsule reports `blur(16px) saturate(1.8) brightness(1.05)` and the block toolbar reports `blur(16px) saturate(1.5)`. ### UX gaps closed - Format and block capsules both measure 94 × 34 px at fine pointer size; the coarse-pointer format capsule is 142 × 44 px. - Heading 1 stays within the shared pill row. Its compact label keeps its accessible name. - The E2E covers 390, 820 and 1440 px in light and dark themes, plus Mac-emulated 1× and 2× crops. - The tooltip stays clear of both the Note card and selected text. UX gaps left: the commit behind the reported blur loss remains unknown because that state does not reproduce in the current production build. ### Screenshots The following crops are at their actual 1× and 2× pixel sizes. | Theme / scale | Format toolbar | Block-selection toolbar | | --- | --- | --- | | Light, 1× | ![Format capsule, light 1x](https://git.kayg.org/attachments/728dfe09-9b13-4219-97d7-d086320b33f5) | ![Block capsule, light 1x](https://git.kayg.org/attachments/d2b5298c-66f0-4f0e-ab9a-22d63c3702e3) | | Light, 2× | ![Format capsule, light 2x](https://git.kayg.org/attachments/9941790f-5481-4eda-9d94-d2ac1e7d652e) | ![Block capsule, light 2x](https://git.kayg.org/attachments/11cb596a-3230-4ee2-ae0c-f939ab34cdcd) | | Dark, 1× | ![Format capsule, dark 1x](https://git.kayg.org/attachments/0048479c-ea0c-494f-bef4-e7adc6813ead) | ![Block capsule, dark 1x](https://git.kayg.org/attachments/ead5d8cd-e487-486d-907b-07f7f4ceecf5) | | Dark, 2× | ![Format capsule, dark 2x](https://git.kayg.org/attachments/633055b6-d6a2-4686-b5a6-491f7dfef80d) | ![Block capsule, dark 2x](https://git.kayg.org/attachments/83a26b0c-2817-4858-9bd8-5400df9946b1) | Responsive production screenshots: [390 px light](https://git.kayg.org/attachments/ca2af230-abe9-4ae0-9778-ffa76aa72a23), [390 px dark](https://git.kayg.org/attachments/b6e6f918-d9b3-42a4-824d-19632e43da94), [820 px light](https://git.kayg.org/attachments/e5245f9d-f208-4b98-a525-da42db55afb4), [820 px dark](https://git.kayg.org/attachments/a2c9f77e-6cd0-49eb-a9fc-8ebefcb88c8a), [1440 px light](https://git.kayg.org/attachments/7761c1d5-5aea-428e-a288-45576c4a7035), [1440 px dark](https://git.kayg.org/attachments/c9151018-0eea-422e-a288-45576c4a7035). ### Decisions DESIGN does not set a narrow-width size for this shared scroll capsule. I used 94 × 34 px for fine pointers to match the block toolbar, and 142 × 44 px for coarse pointers to preserve a 44 px touch target while keeping the commands in the existing scroll row. The shared `PillGroup` owns the capsule styling; the feature does not add a local surface. ### Files - `apps/web/e2e/notes.mjs` - `apps/web/src/lib/components/PillGroup.svelte.test.ts` - `apps/web/src/lib/editor/format/EditorFormatBubble.svelte` - `apps/web/src/lib/editor/format/FormatButton.svelte` - `apps/web/src/lib/editor/format/FormatButton.svelte.test.ts` - `apps/web/src/lib/editor/format/FormatCommandRow.svelte` - `packages/ui/src/components/PillGroup.svelte` - `packages/ui/src/components/tooltip/TooltipLayer.svelte` - `contracts/perf/exceptions.json` - `contracts/perf/registry.json` ### Verification `cargo fmt --check`: passed; no output. No Rust files changed, so Rust clippy and crate tests were not run. `cd apps/web && bun run check` output: ```text perf-lint: PASS; 0 violations; 22615 scoped exceptions PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages svelte-check found 0 errors and 2 warnings in 2 files ``` The two Svelte warnings are the existing empty CSS rules in `AttachmentDeck.svelte` and `AgendaList.svelte`. `cd apps/web && bun run test` summary: ```text Ran 136 tests in 0.086s OK 8 pass 0 fail 16 expect() calls Test Files 283 passed (283) Tests 1981 passed (1981) ``` Focused Vitest: 2 files and 5 tests passed. Production E2E output: ```text notes #1210 format capsule e2e: real backdrop, matching capsule geometry and responsive screenshots passed CSP REPORTS notes: 0 across 3 pages ``` Head: `d8cca81dde1512a3e656aed5e7c5ecec7f410de1` (`perf(ui): bound tooltip obstacle lookup (#1210)`).
Author
Owner

Starting the correction on branch job/fmtbar-1210. Base is ff53d7b8b4; current head is d8cca81dde. I am removing the fixed-width PillGroup sizing, restoring all format commands at tablet and desktop widths, updating the e2e geometry/accessibility checks, and capturing the requested screenshot matrix.

Starting the correction on branch job/fmtbar-1210. Base is ff53d7b8b4da5a21f862d57dcc3496f8c019f647; current head is d8cca81dde1512a3e656aed5e7c5ecec7f410de1. I am removing the fixed-width PillGroup sizing, restoring all format commands at tablet and desktop widths, updating the e2e geometry/accessibility checks, and capturing the requested screenshot matrix.
Author
Owner

Finding: commit 0fbe7d162 introduced in , setting both width and max-width to (94px at fine pointer and 142px at coarse pointer). opts into that path, so the full command row is clipped into a three-action frame; the matching unit test currently locks in this fixed-width behavior. I will remove that API and assertion while retaining the behavior and the existing Note-card tooltip obstacle selector.

Finding: commit 0fbe7d162 introduced in , setting both width and max-width to (94px at fine pointer and 142px at coarse pointer). opts into that path, so the full command row is clipped into a three-action frame; the matching unit test currently locks in this fixed-width behavior. I will remove that API and assertion while retaining the behavior and the existing Note-card tooltip obstacle selector.
Author
Owner

Correction to my previous finding comment: the shell removed the backticked identifiers before posting. The finding is that commit 0fbe7d162 introduced the scrollWidth="action-capsule" path in packages/ui/src/components/PillGroup.svelte. That path sets both width and max-width to --pill-group-action-capsule-width (94px at fine pointer and 142px at coarse pointer). EditorFormatBubble opts into it, clipping the full command row into a three-action frame. The matching unit test currently locks in the fixed-width behavior. I will remove that sizing API and assertion while retaining the .pg-scroll behavior and the existing Note-card tooltip obstacle selector.

Correction to my previous finding comment: the shell removed the backticked identifiers before posting. The finding is that commit 0fbe7d162 introduced the `scrollWidth="action-capsule"` path in `packages/ui/src/components/PillGroup.svelte`. That path sets both width and max-width to `--pill-group-action-capsule-width` (94px at fine pointer and 142px at coarse pointer). `EditorFormatBubble` opts into it, clipping the full command row into a three-action frame. The matching unit test currently locks in the fixed-width behavior. I will remove that sizing API and assertion while retaining the `.pg-scroll` behavior and the existing Note-card tooltip obstacle selector.
Author
Owner

Finding: FormatButton.svelte applies the shared accent-soft fill to every .active button, including the wide Text style menu trigger while its menu is open. That paints an extra box inside the frosted capsule. I changed only the shared wide-control state to keep the accent ink while leaving its background transparent; the E2E now asserts the active trigger remains inside the capsule and has no filled background.

Finding: `FormatButton.svelte` applies the shared `accent-soft` fill to every `.active` button, including the wide Text style menu trigger while its menu is open. That paints an extra box inside the frosted capsule. I changed only the shared wide-control state to keep the accent ink while leaving its background transparent; the E2E now asserts the active trigger remains inside the capsule and has no filled background.
Author
Owner

Correction complete on job/fmtbar-1210.

The format capsule is content-sized. It uses the shared frosted material; its height, icon-button size and icons match BlockHoverActions. The Text style control keeps its natural label width and matching height. All seven formatting commands and the Text style menu are visible at 820px and 1440px. At 390px, the existing More formatting menu keeps Underline, Strikethrough, Highlight and Code reachable. The active Text style uses accent ink without a separate fill. The warm tooltip stays clear of both the Note card and selected text.

UX gaps closed: restored every requested command; checked accessible names, phone overflow actions, matching toolbar geometry, glass blur and tooltip collision at production build. The macOS screenshot platform is emulated.

UX gaps left: none in this issue's scope.

Decisions: keep the capsule width intrinsic to its controls and cap it at the viewport width minus 16px. Keep the existing 390px overflow behavior. Use the accessible command name Code. The row has eight controls and adds no Note-content scan or observer.

Comparison screenshots: each image shows the format capsule beside the block toolbar.

Gates (verbatim summary output)

bun run check

perf-lint: PASS; 0 violations; 22615 scoped exceptions
svelte-check found 0 errors and 2 warnings in 2 files

The two warnings are existing empty CSS rulesets in packages/ui/src/components/calendar/AttachmentDeck.svelte:1055 and packages/ui/src/components/calendar/AgendaList.svelte:1277.

bun run test

 Test Files  283 passed (283)
      Tests  1981 passed (1981)
   Start at  01:52:31
   Duration  547.55s (transform 29%, environment 27%, import 25%, tests 13%, setup 6%)

Focused production E2E: notes #1210 format capsule e2e: real backdrop, accessible commands, matching control geometry and 1x/2x responsive screenshots passed.

Head SHA: 8f8c58afb125b7915ebe11686a0940f06a977580.

**Correction complete** on `job/fmtbar-1210`. The format capsule is content-sized. It uses the shared frosted material; its height, icon-button size and icons match `BlockHoverActions`. The Text style control keeps its natural label width and matching height. All seven formatting commands and the Text style menu are visible at 820px and 1440px. At 390px, the existing More formatting menu keeps Underline, Strikethrough, Highlight and Code reachable. The active Text style uses accent ink without a separate fill. The warm tooltip stays clear of both the Note card and selected text. **UX gaps closed:** restored every requested command; checked accessible names, phone overflow actions, matching toolbar geometry, glass blur and tooltip collision at production build. The macOS screenshot platform is emulated. **UX gaps left:** none in this issue's scope. **Decisions:** keep the capsule width intrinsic to its controls and cap it at the viewport width minus 16px. Keep the existing 390px overflow behavior. Use the accessible command name `Code`. The row has eight controls and adds no Note-content scan or observer. **Comparison screenshots:** each image shows the format capsule beside the block toolbar. - 390px: [mono 1x](https://git.kayg.org/attachments/510d7a61-7b34-4fce-9da7-a38d34c25030) · [mono 2x](https://git.kayg.org/attachments/42243999-f1b1-43b5-9235-9d5f81ce0b4b) · [noir 1x](https://git.kayg.org/attachments/1799df63-acb6-4e39-8666-191b34f05cdb) · [noir 2x](https://git.kayg.org/attachments/5ef4e24c-c4ea-477b-8a1e-eebda13bb380) - 820px: [mono 1x](https://git.kayg.org/attachments/7431b09f-c138-4f49-b7e2-49e266d550b0) · [mono 2x](https://git.kayg.org/attachments/4ce6e88b-e258-468c-9b06-1fea6ee358db) · [noir 1x](https://git.kayg.org/attachments/d7ec8c17-bf85-4e92-9545-c320815fbcc8) · [noir 2x](https://git.kayg.org/attachments/83317910-5c82-4df2-9d85-3d2b72f8de7b) - 1440px: [mono 1x](https://git.kayg.org/attachments/abf9eb42-ae32-4433-8c0c-e9d284ec31b7) · [mono 2x](https://git.kayg.org/attachments/c1dae272-40ac-44be-882d-ec77bc8ad5aa) · [noir 1x](https://git.kayg.org/attachments/594a701a-5159-40e4-a325-47baaa1f0c19) · [noir 2x](https://git.kayg.org/attachments/eccf9966-ab73-4a60-ac70-ee4051d3da4c) **Gates (verbatim summary output)** `bun run check` ```text perf-lint: PASS; 0 violations; 22615 scoped exceptions svelte-check found 0 errors and 2 warnings in 2 files ``` The two warnings are existing empty CSS rulesets in `packages/ui/src/components/calendar/AttachmentDeck.svelte:1055` and `packages/ui/src/components/calendar/AgendaList.svelte:1277`. `bun run test` ```text Test Files 283 passed (283) Tests 1981 passed (1981) Start at 01:52:31 Duration 547.55s (transform 29%, environment 27%, import 25%, tests 13%, setup 6%) ``` Focused production E2E: `notes #1210 format capsule e2e: real backdrop, accessible commands, matching control geometry and 1x/2x responsive screenshots passed`. **Head SHA:** `8f8c58afb125b7915ebe11686a0940f06a977580`.
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#1210
No description provided.