Restyle the selection toolbar and place it above the selection #349
- MA
## Why The selection toolbar used unstyled mark labels, covered prose below a selection, and needed a clearer layout on phones.
## What changed - Style each mark by its action, show platform-aware shortcut hints, and use a named block menu. - Place the toolbar above the selection when there is room, while keeping its full surface within the viewport. Hide it when its selection scrolls mostly out of view. - Keep one 52px row on coarse pointers; move less-used marks into the block menu. Respect reduced-motion settings. - Prevent toolbar pointer movement from taking ownership of a comment hover preview. - Apply the reviewed design-contract class updates for the new toolbar markup.
## Verification The 63 focused toolbar tests, `bun run types`, and `bun run ci` passed locally on the rebased commits. Required GitHub checks will run on the final published head.
## Visuals Phone toolbar: 
Phone block menu: 
Desktop toolbar: 
Desktop block menu: 
- MA
**Review:** Changes needed
Looked at it in a real browser (headless Chromium, desktop 1440x900 and phone 390x844) against main, plus the CI run. The restyle itself is good; there are two e2e regressions and a few things to fix before merge.
**Looks right** - B/I/S/U read as a format bar, CodeIcon is cleaner than `<>`, "Text ⌄" is clear. Active marks show clearly (filled cell), and the toolbar matches other menus. - Enter animation is ~120 ms, ease-out, opacity plus 4px rise plus scale, finishing cleanly (frame sampled every rAF). Reposition is not animated. - No jitter: extending with Shift+Arrow and while dragging, the bubble follows the range one frame behind with a constant y; no flicker or re-run of the enter animation. - Tooltip with `Italic ⌘I` shows on hover and is hidden after click (the native `title` is suppressed by the tooltip layer, so no double tooltip). - Placement: above in the middle of the pane; flips below at the top of the pane and inside a table cell near the top; bottom of the pane stays inside the viewport. No conflict with #298: that PR touches other lines of `bubble.tsx` (link button, Escape handler, `hidden` prop), so the rebase should be mechanical (the `placeSurface(..., 8, "above")` call and the button class lines are the only nearby edits).
**Changes needed** 1. `e2e/toolbar.e2e.ts:565` (touch targets) and `:615` (toolbar follows range while editor scrolls) fail in CI (`gh pr checks 349`: e2e fail, 371 passed, 4 failed counting retries). Both are in the toolbar spec this PR edits. The touch test asserts every bubble button is at least 44x44; the scroll test's follow check (`movedWithSelection`, distance not growing) fails now that the bubble sits above the selection. Update `expectSurfaceToFollowEditorScroll` for an above-placed surface if that is all it is, and fix the 44px case below. 2. `packages/editor/src/toolbar/bubble.tsx:386` block trigger on touch: `:root[data-plan-coarse-pointer] .plan-menu-cell` forces `width: 44px`, so "Text" plus the chevron are squeezed into 44px and on the phone the chevron is clipped to a dot (see phone screenshot). Give the trigger its own coarse-pointer sizing (`min-width: 44px; width: auto`) or drop the chevron on touch. 3. Shortcut hints are hardcoded to the Mac glyphs (`⌘B`, `⌘I`, `⌘⇧X`, `⌘U`, `⌘E` at `bubble.tsx:143-157`, also `⌘K` in the link title). On Windows and Linux they should read `Ctrl+B` and so on. Please derive the modifier once (for example from `navigator.userAgentData?.platform ?? navigator.platform`) and format the hints from it; update the e2e assertion (`"Bold ⌘B"`) to match, or make it platform-neutral. 4. Phone: above placement collides with the native selection callout. iOS and Android show their Copy / Look Up / Share bar above the selection, so a bubble placed above stacks on, or hides behind, it. Headless cannot show the callout, but main (below) did not have this. Suggest `prefer: "auto"` (below) when `(pointer: coarse)`, keep above for mouse.
**Minor** - Above placement covers the line above the selection (screenshot: "Middle paragraph number 2" is hidden under the bubble). That is the intended trade for not covering the line below, but worth a conscious call; the 8px gap is tight for 36px chrome. - Phone: bubble is 350px wide with horizontal scroll and the Comment button is clipped off the right edge. Pre-existing on main, but "Text" is one wider than the old "T" so it is now one button worse. - The block menu still shows `T H1 H2 H3 > UL OL` while the trigger now says "Text", so the vocabulary is inconsistent; consider the same short names. - If a selection is scrolled mostly out of the pane, the bubble clamps to a sliver (maxHeight 8px, seen while dragging). Same clamp as before, but worth hiding the bubble when `above` and `below` are both smaller than the surface height.
**CI** (`gh pr checks 349`): format, lint, types, tests: fail, only the design-contract exception for `bubble.tsx` (expected, listed in the PR body, hash not edited); container: pass; e2e: fail (the two tests above).
Screenshots: `.design-orchestrator/audit-2026-10-07/shots/review-349/`
MaggieAppletonhead-ref-force-pushed 8e3c40f5e853last month on GitHub
- MA
Addressed the review in 8e3c40f5 and the follow-up e2e fix: (1) the two e2e failures came from measuring during the 120ms enter scale/rise; tests now wait for animations to finish. (2) coarse pointers prefer below and the block trigger sizes to its label (min 44px). (3) shortcut hints are platform-aware (shortcut.ts). (4) phone: seams hidden and cells tightened; the bubble wraps rather than clipping Comment. (5) block menu is a list with the same names as the trigger. (6) bubble hides when the selection is mostly scrolled out (visibleAnchor, unit tested).
- MA
CI on 68c64715: the toolbar e2e tests now pass (378 passed, 2 flaky). Remaining e2e failures are comment-preview specs (comment-motion:167, sidecar:1195, responsive-comments:44, decision-marker:407), which do not touch the selection toolbar; comment-motion:167 also failed on the first run of this PR. Lint job fails only on the expected design-contract exception for bubble.tsx (new hash in the PR body).
MaggieAppletonhead-ref-force-pushed 4dfb5da2505elast month on GitHub
MaggieAppletonhead-ref-force-pushed 216b83a227e1last month on GitHub
- MA
Final screenshots (head 216b83a2).
Phone, one row (Text, Bold, Italic, Link, Comment):

Phone block menu (types, then Strikethrough, Underline, Inline code):

Desktop toolbar above the selection:

Desktop block menu:





MaggieAppletonhead-ref-force-pushed 46a1e1fa8299last month on GitHub
- MA
Before and after (desktop):




MaggieAppletonreferenced this in Share one menu surface recipe across pickers and the language menu #377last month on GitHub
MaggieAppletonhead-ref-force-pushed 8a9022b29027last month on GitHub
MaggieAppletonhead-ref-force-pushed 7924de5862dalast month on GitHub
MaggieAppletonauto-rebase-enabledlast month on GitHub
MaggieAppletonmerged 46643d195417last month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub