SIGN IN SIGN UP

feat(annotate): hide HTML tools by default, toggle chords for annotate mode and tools, shortcut tooltips on the header controls (#1531)

* feat(annotate): hide the HTML surface tools by default

An HTML document is authored to fill the viewport, so a raw-HTML (or
live-app) annotate session now opens with the floating chrome over the
page removed and the header eye rendering its "Show tools" state from
the first paint. DEFAULT_HTML_CHROME_STATE.toolsHidden flips to true and
App seeds htmlToolsHidden true to match, so there is no flash of tools
before the restore effect runs.

A fresh persisted record still wins in both directions: a reviewer who
explicitly showed the tools keeps them next session. Hidden can never
strand anyone — the eye lives in the header on desktop and the compact
Options menu offers "Show tools" — which is the same reasoning that
already made restoring a hidden cookie safe.

* feat(annotate): make Mod+Shift+A a real toggle and add Mod+Shift+X for the tools

Mod+Shift+A already flipped annotate mode in both directions in the app;
the scope described it as a re-arm only. It is now documented as the
toggle it is — the answer to "Esc dropped me to Interact, how do I get
back?" — and pinned by a test that presses it twice.

Adds Mod+Shift+X for the header eye, wired through the same scope so it
reaches the help modal and the generated marketing shortcuts page, and
mirrored inside the sandboxed iframe by the bridge on the capture phase
exactly like the annotate chord (additive message type, no protocol
bump), so it works whichever document owns focus. The binding is
deliberately un-mnemonic: every mnemonic letter is a browser chord
(H Home/history, E/I/J/K/C devtools, B/O bookmarks, V paste-as-plain-text,
T reopen tab), and a chord the browser eats is worse than an arbitrary
one. The tools chord is not gated on documentReadOnly, matching the eye.

Disarming through either path already tears the pending draft down via
the bridge's set-annotate-mode(false) handler, so toggling to Interact
cannot leave a draft floating over a page that now takes clicks.

* feat(annotate): describe the HTML header controls with real tooltips

The eye, the pen and Refresh leaned on the native title attribute, which
never appears on keyboard focus, is unstyled, and cannot show a shortcut
legibly. They now use the app's Tooltip (the same primitive the review
header and DecisionControl use — no new dependency): two lines, the
control's description over its shortcut as keycaps rendered through the
registry's platform-aware formatter, so a Mac shows glyphs and everyone
else Ctrl rather than a hardcoded Cmd.

The description strings are the same host-overridable labels the titles
carried, so a @plannotator/ui consumer's overrides keep working; the new
shortcuts prop overrides the bindings per control and defaults to the
html-annotate scope, so a tooltip cannot claim a chord the app does not
dispatch. Because title no longer supplies the accessible name, the pen
states an aria-label (defaulting to its description), the eye keeps its
sr-only text and Refresh its aria-label, and the shortcut is attached as
a persistent aria-describedby span — this Base UI build puts no ARIA on
the popup at all, so the tooltip alone would never be announced. Refresh
has no chord and renders no keycap row.
M
Michael Ramos committed
85f66fdebbc6d42eb9d50ac6f4e11bb4a6385d0f
Parent: 01eda97
Committed by GitHub <noreply@github.com> on 9/15/2026, 4:28:03 AM