The founder opened a document, pressed −, pressed =, pressed
Shift+= — and nothing moved. Zoom existed, but only under the mouse: the strip's
−/+ cluster was the sole way to reach it. Every reader a lawyer has ever used (Preview, Acrobat,
Chrome's own PDF pane) answers those three keys, so their silence read as a broken viewer rather
than as a missing feature.
The Reading Room's key map now owns them: − zooms out one step,
= and + (Shift+=) zoom in one step, 0 returns to Fit.
The one way a key and a button can drift apart is by having two code paths. They do not have two. The shell already owned the zoom (it is a per-document, fit-relative preference in the persisted store — Story-2367, Bug-2356), and it already handed the strip three verbs. The key binder is handed the same three:
src/components/documents/viewer/document-permalink-shell.tsx
const zoomIn = useCallback(() => zoomBy(ZOOM_STEP), [zoomBy]);
const zoomOut = useCallback(() => zoomBy(-ZOOM_STEP), [zoomBy]);
useZoomKeys({
enabled: lens === "original", // only the lens that HAS paper
onZoomIn: zoomIn,
onZoomOut: zoomOut,
onZoomFit: zoomFit,
});
// ...and the strip is given the very same three:
paging={ lens === "original" ? { …, onZoomIn: zoomIn, onZoomOut: zoomOut, onZoomFit: zoomFit } : undefined }
So a press of + and a click of + step by the same
ZOOM_STEP (10 fit-relative percent), clamp at the same
ZOOM_MIN/ZOOM_MAX (50 / 400), read the same live base out of the store,
and write the same per-document entry. There is no second step size to keep in sync, because there
is no second implementation.
| Keys | Bound by | Why there |
|---|---|---|
| ↑ ↓ ← → PageUp/Down Home End | the scroll column (useReadingKeyboard) |
they move the scrolling element, which only the column holds |
| F | the shell (useFocusKey) |
focus is the shell's state, and works on Text and Insights too |
| − = + 0 | the shell (useZoomKeys) |
zoom is the shell's per-document state — the same state the strip writes |
All four sets are declared in one map (readingKeyIntent), even though
three different hooks act on it. That is deliberate and predates this Story: the shortcuts overlay
reads the map, so a key declared anywhere else is a key counsel is never told about. The two hooks
bound on the same window skip each other's intents, so one press never fires twice — the scroll
column ignores zoom / zoom-fit / toggle-focus, and the zoom
binder ignores everything else.
| Situation | Behaviour |
|---|---|
Caret in the strip's page box, a note composer, any contenteditable | quiet — typing 0 types a 0 |
| A dialog is open (delete confirm, the shortcuts overlay itself) | quiet — the dialog owns the keyboard |
⌘− / ⌘+ / ⌘0 (and the Ctrl equivalents) | not intercepted — page zoom stays the browser's |
| Text / Insights lens | not bound at all — no paper, and the strip hides the cluster there for the same reason |
The chord guard is structural, not a special case: readingKeyIntent returns
null for any metaKey/ctrlKey/altKey press
before it looks at the key. Shift is deliberately not in that list —
it has to reach through, because + IS Shift+= (and ? is
Shift+/, which is how the overlay opens).
Both = and + zoom in: = is the unshifted key the
+ is printed on, so counsel who does not reach for Shift still zooms in — and
counsel who does gets the key the label promised.
The registry is the single declaration the ? overlay reads. Two rows were added to the
Reading Room's View group, and that is the entire overlay change — no overlay
component, story, or fixture was touched:
src/components/shortcuts/shortcut-registry.ts
{
id: "view",
bindings: [
{ id: "focus", keys: ["F"] },
{ id: "zoom", keys: ["−", "+"] }, // Story-2398
{ id: "zoomFit", keys: ["0"] },
],
},
The glyphs are what a reader expects on a cheat sheet, not the raw event.keys the map
matches: the row reads − + because + is what the key is printed
with. Copy landed in both locales (Shortcuts.readingRoom.actions.zoom,
.zoomFit) — the registry test asserts every named key exists in en AND
hi, so a one-locale row fails CI rather than rendering a raw message key at the one
moment counsel asked for help.
pramaan-desk .../keyboard-shortcuts-v2/keyboard-shortcuts.dc.html) governs how the
overlay — zoom rows included — will look. That redesign is
Story-2401, not this one. This Story delivers the KEYS and the REGISTRY ROWS the
redesign will render; the rows appear in today's overlay in today's styling. No conflict with
design/v1 tokens arose, because no styling was written here.
use-reading-keyboard.test.ts): −/=/+ / 0 produce the
right intents; Shift+= reaches through as +; ⌘−,
⌘=, Ctrl++, ⌘0 all return null. And for the
binder itself: it fires the three handlers, stays quiet in an <input> and under
an open role="dialog", is not bound when enabled: false (Text / Insights),
and unbinds on unmount.
| Gate | Result |
|---|---|
pnpm lint | 0 errors (3 pre-existing useVirtualizer warnings) |
pnpm test | 197 files, 1302 tests, all passing |
pnpm build | clean |
pnpm storybook:check | clean |
git diff --check | clean |
Storybook: Shortcuts/ShortcutsOverlay → Reading Room (registry map)
is a READ of the registry, so the two new View rows appear there with no story edit. (Keyboard
binding itself is shell state, not a component prop — it is proven by the hook tests above, not by
a story.)
stg.app.pramaan.io). A green build is not
deployed-runtime proof (LI-7). On a document's Original lens:
− → indicator steps down 10% (100% → 90%). Press = → back to 100%.
Press Shift+= → 110%. Press 0 → back to Fit (100%).− down to the floor: it stops at 50%. Hold +:
it stops at 400%. Same ends as the buttons.⌘− / ⌘0 → the browser page zoom moves, and
the paper's indicator does not.0 → a 0 is typed; the zoom does not move.? → the overlay lists − + and 0 under
View.− / + → nothing happens
(there is no paper to zoom).7 file(s), +243 / -70 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +3 | -1 |
| messages/hi.json | Config | +3 | -1 |
| src/components/documents/viewer/document-permalink-shell.tsx | Component | +16 | -3 |
| src/components/documents/viewer/use-reading-keyboard.test.ts | Component | +113 | -0 |
| src/components/documents/viewer/use-reading-keyboard.ts | Component | +88 | -9 |
| src/components/shortcuts/shortcut-registry.ts | Component | +11 | -4 |
| src/components/shortcuts/shortcuts-overlay.stories.tsx | Component | +9 | -52 |