Skip to content

fix(desktop): annotation tool keys work after clicking a toolbar button - #26211

Open
juliusmarminge wants to merge 4 commits into
mainfrom
fix/issue-7700
Open

juliusmarminge wants to merge 4 commits into
mainfrom
fix/issue-7700

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

Problem

In the desktop browser's annotation mode, the tool keys V, R, D and E stop working as soon as you click one of the toolbar buttons (Select, Region, Draw, Erase). Steps: open the browser panel, load any page, press Annotate, click Region, press V. The tool stays on Region, as in the reporter's recording.

Change

The overlay lives in a closed shadow root. Clicking a toolbar button leaves focus on that button, and at the window keydown listener every such keypress is retargeted to the overlay host. The handler skipped every non-Escape key whose target was the overlay, which was meant to protect text fields but also caught the buttons. It now skips tool keys only when the focused element inside the overlay (shadowRoot.activeElement) is an input, textarea or select. Escape, submission keys and page isolation are unchanged.

The test runs the real preload against a DOM. apps/desktop had no DOM test setup and the repo's lint rule rejects per-file environment comments, so apps/desktop/vite.config.ts gains a dom Vitest project next to the existing suite (the pattern apps/mobile uses), and jsdom (already in the lockfile for mobile) becomes a desktop devDependency.

Scope and approval

Fixes #7700

Triaged via the automated issue flow.

Verification

Focused tests in apps/desktop/src/preview/PickPreload.test.ts:

  • switches tools from the keyboard after a toolbar button is clicked fails against the old PickPreload.ts (expected 'marquee' to be 'select') and passes with the fix.
  • keeps the tool while typing a tool key in the comment passes, and fails if the text-field guard is removed.
  • vp run check:changed passes (format, lint, knip, typecheck, tests). The desktop suite keeps all its existing test files under the unit project.

Evidence

Real Linux desktop app: Electron 44.9.3 / Chromium 152.0.7977.167 under Xvfb, no emulation, app 1256×780, guest 1000×700, DPR 1, zoom and font scaling 100%, dark theme, a fresh database on each side, a synthetic fixture page. Input was trusted CDP mouse and key events. Before is 5808469018, after is 1667f6fbf1. R12 was recorded at 7787049cda, whose production change is the same.

What else changed:

  • R01: Select loses its active text color; Erase gains the active text color and background. The Select focus outline stays in place.
  • R02: Region loses its active text color; Erase gains the active text color and background. The Region focus outline stays in place.
  • R03: Draw loses its active text color; Erase gains the active text color and background. The Draw focus outline stays in place.
  • R04: no final-frame difference at zero threshold; its recording shows the changed intermediate transitions.
  • There are no changed pixels outside the annotation toolbar in the adopted before/after pairs.

Diff0 marks every nonzero RGB-channel difference in bright pink, so a one-level change is amplified to a visible pixel. DiffT12 marks pixels whose maximum absolute RGB-channel difference exceeds 12/255. Both use the full 1256×780 frame. Changed pixels (Diff0 / DiffT12): R01: 2052 / 1977, R02: 2081 / 2006, R03: 2002 / 1932, R04: 0 / 0.

R01 — Select button focused; v/r/d/e · guest 1000×700 · dark

Linux x86_64 (fs-06), real Electron 44.9.3 / Chromium 152.0.7977.167 under headless-desktop/Xvfb; no device emulation; full app 1256×780, guest 1000×700 CSS px, DPR 1, zoom 100%, font scaling 100%, dark theme. The 1256×860 recordings add an 80px narration band below the complete app surface.

Before: Select remains active for all four keys. After: Select → Region → Draw → Erase; Select retains keyboard focus.

Before steps (seconds from recording start): 1.43s Select button focus; trusted v/r/d/e → select; 1.88s Click Select → select; 2.67s Key v → select; 3.73s Key r → select; 4.79s Key d → select; 5.84s Key e → select.

After steps (seconds from recording start): 1.42s Select button focus; trusted v/r/d/e → select; 1.86s Click Select → select; 2.66s Key v → select; 3.71s Key r → marquee; 4.76s Key d → draw; 5.81s Key e → erase.

Before After Diff0 DiffT12
before-R01.mp4
before-R01-final.png
after-R01.mp4
after-R01-final.png
R01-diff0.png R01-diffT12.png
5808469018 1667f6fbf1 5808469018 → 1667f6fbf1 5808469018 → 1667f6fbf1

Read-only focus/tool telemetry: after-R01-events.json · before-R01-events.json

Contact sheets (derived from the recordings):

before-R01-sheet-1.jpg

after-R01-sheet-1.jpg

R02 — Region button focused; v/r/d/e · guest 1000×700 · dark

Linux x86_64 (fs-06), real Electron 44.9.3 / Chromium 152.0.7977.167 under headless-desktop/Xvfb; no device emulation; full app 1256×780, guest 1000×700 CSS px, DPR 1, zoom 100%, font scaling 100%, dark theme. The 1256×860 recordings add an 80px narration band below the complete app surface.

Before: Region remains active, including after v. After: Select → Region → Draw → Erase; Region retains keyboard focus.

Before steps (seconds from recording start): 1.42s Region button focus; trusted v/r/d/e → select; 1.90s Click Region → marquee; 2.71s Key v → marquee; 3.76s Key r → marquee; 4.82s Key d → marquee; 5.87s Key e → marquee.

After steps (seconds from recording start): 1.42s Region button focus; trusted v/r/d/e → select; 1.86s Click Region → marquee; 2.65s Key v → select; 3.70s Key r → marquee; 4.75s Key d → draw; 5.80s Key e → erase.

Before After Diff0 DiffT12
before-R02.mp4
before-R02-final.png
after-R02.mp4
after-R02-final.png
R02-diff0.png R02-diffT12.png
5808469018 1667f6fbf1 5808469018 → 1667f6fbf1 5808469018 → 1667f6fbf1

Read-only focus/tool telemetry: after-R02-events.json · before-R02-events.json

Contact sheets (derived from the recordings):

before-R02-sheet-1.jpg

after-R02-sheet-1.jpg

R03 — Draw button focused; v/r/d/e · guest 1000×700 · dark

Linux x86_64 (fs-06), real Electron 44.9.3 / Chromium 152.0.7977.167 under headless-desktop/Xvfb; no device emulation; full app 1256×780, guest 1000×700 CSS px, DPR 1, zoom 100%, font scaling 100%, dark theme. The 1256×860 recordings add an 80px narration band below the complete app surface.

Before: Draw remains active for all four keys. After: Select → Region → Draw → Erase; Draw retains keyboard focus.

Before steps (seconds from recording start): 1.42s Draw button focus; trusted v/r/d/e → select; 1.85s Click Draw → draw; 2.64s Key v → draw; 3.69s Key r → draw; 4.74s Key d → draw; 5.79s Key e → draw.

After steps (seconds from recording start): 1.42s Draw button focus; trusted v/r/d/e → select; 1.85s Click Draw → draw; 2.65s Key v → select; 3.70s Key r → marquee; 4.75s Key d → draw; 5.79s Key e → erase.

Before After Diff0 DiffT12
before-R03.mp4
before-R03-final.png
after-R03.mp4
after-R03-final.png
R03-diff0.png R03-diffT12.png
5808469018 1667f6fbf1 5808469018 → 1667f6fbf1 5808469018 → 1667f6fbf1

Read-only focus/tool telemetry: after-R03-events.json · before-R03-events.json

Contact sheets (derived from the recordings):

before-R03-sheet-1.jpg

after-R03-sheet-1.jpg

R04 — Erase button focused; v/r/d/e · guest 1000×700 · dark

Linux x86_64 (fs-06), real Electron 44.9.3 / Chromium 152.0.7977.167 under headless-desktop/Xvfb; no device emulation; full app 1256×780, guest 1000×700 CSS px, DPR 1, zoom 100%, font scaling 100%, dark theme. The 1256×860 recordings add an 80px narration band below the complete app surface.

Before: Erase remains active for all four keys. After: Select → Region → Draw → Erase. The identical final Erase frame does not replace the recording of intermediate transitions.

Before steps (seconds from recording start): 1.42s Erase button focus; trusted v/r/d/e → select; 1.86s Click Erase → erase; 2.66s Key v → erase; 3.70s Key r → erase; 4.75s Key d → erase; 5.79s Key e → erase.

After steps (seconds from recording start): 1.42s Erase button focus; trusted v/r/d/e → select; 1.86s Click Erase → erase; 2.66s Key v → select; 3.71s Key r → marquee; 4.76s Key d → draw; 5.81s Key e → erase.

Before After Diff0 DiffT12
before-R04.mp4
before-R04-final.png
after-R04.mp4
after-R04-final.png
R04-diff0.png R04-diffT12.png
5808469018 1667f6fbf1 5808469018 → 1667f6fbf1 5808469018 → 1667f6fbf1

Read-only focus/tool telemetry: after-R04-events.json · before-R04-events.json

Contact sheets (derived from the recordings):

before-R04-sheet-1.jpg

after-R04-sheet-1.jpg

After-only rows (recordings and contact sheets)

Row What it shows Sheet
R05 Typing letters, including V/R/D/E, in the comment box keeps the tool; refocusing a toolbar button restores the keys R05
R06 Style panel text input keeps the tool R06
R07 Number input keeps the tool R07
R08 Range input keeps the tool R08
R09 Colour input (picker closed) keeps the tool R09
R10 Select control keeps the tool R10
R11 Expand/collapse, aspect lock and drag-handle buttons let tool keys through R11
R12 Escape cancels from a toolbar button, the comment box, a text input and a select; reopening works R12 retake
R13 PDF page: input layer still routes tool keys and Escape R13
R14 Keyboard-chosen tools select, draw a region, draw and erase R14
R15 Tool keys work with focus on the page before any overlay control is focused R15

Matrix: fixture-matrix.md. Drivers and fixtures to rebuild any state: reproduction.md, reproduction-bundle.zip, r12-retake-3-drivers.zip. Full evidence index: evidence.md.

Not checked: Windows desktop (the reporter's platform) and macOS desktop. The changed code is platform-independent and the bug reproduced on Linux with the reporter's steps. Light theme and narrow viewports were not captured; the change does not touch layout.

Model and harness: Claude Opus 5.5 (lead, red test, implementation, review B) and GPT-6.1 Sol (review A, proof audits, captures), in Claude Code and Codex via T3 Code.

🤖 Generated with Claude Code

juliusmarminge and others added 4 commits October 27, 2026 21:01
…icking a toolbar button

Adds a jsdom test project for the desktop package and a PickPreload test
that starts an annotation, clicks the Region toolbar button, then presses
V. On main the tool stays on Region because the window keydown handler
ignores every key whose target is inside the overlay host.

These tests reproduce #7700 and fail until it is fixed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The annotation overlay ignored every non-Escape key whose target was inside
the overlay host, so V/R/D/E did nothing once a toolbar button held focus.
Skip tool keys only when an input, textarea or select inside the overlay's
shadow root has focus.

Fixes #7700

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…each test

A cold transform of the preload under heavy load exceeded the 15s test
timeout when it ran inside the test body.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 28, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 7787049

Macroscope's review found this PR approvable — Restores annotation-tool keyboard shortcuts after toolbar clicks while preserving normal typing in editor controls. The accompanying test-project split and session-scoped behavior introduce no obvious material risk.

You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.6 KiB 21.6 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.1 KiB 5.1 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 0894156 · PR result: 7787049 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge juliusmarminge added the bugfix Fix PR opened by an issue lead; enters the PR loop label Oct 28, 2026
@coderabbitai

coderabbitai Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 0894156 and 7787049.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 4
  • apps/desktop/package.json
  • apps/desktop/src/preview/PickPreload.test.ts
  • apps/desktop/src/preview/PickPreload.ts
  • apps/desktop/vite.config.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

Walkthrough

The annotation key handler now allows tool shortcuts when toolbar controls have focus and suppresses them when form controls have focus. New DOM tests cover both cases, and Vitest runs those tests in a jsdom project.

Changes: Annotation shortcuts

Layer / File(s) Summary
Shortcut focus handling and DOM tests
apps/desktop/src/preview/PickPreload.ts, apps/desktop/src/preview/PickPreload.test.ts, apps/desktop/vite.config.ts, apps/desktop/package.json
The key handler now checks whether an input, textarea, or select is active before ignoring tool shortcuts. Tests check shortcut behavior with a focused Region button and a focused comment textarea. Vitest adds a jsdom project for the DOM tests and jsdom as a development dependency.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related PRs

Suggested reviewers: maria-rcks, t3dotgg

Merge Risk: Minimal · up to 77870

No merge-blocking issue was identified in the shortcut change or its test setup.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title is concise, conventional, and clearly describes the main fix: annotation tool keyboard shortcuts work after toolbar focus.
Description check Passed The description covers the problem, reproduction steps, technical change, linked issue and approval context, focused verification, visual evidence, limitations, and agent details.
Linked Issues check Passed Directly linked issue #7700 asks for reliable annotation-mode shortcuts (Escape, V, R, D, E) in apps/desktop. The reviewed change addresses the V/R/D/E failure. In apps/desktop/src/preview/PickPreload…
Out of Scope Changes check Passed All changed areas connect to the linked issue or to its test coverage. The PickPreload.ts change is the production fix for the keyboard guard. PickPreload.test.ts adds focused regression tests for the…
Full details: Linked Issues check

Explanation

Directly linked issue #7700 asks for reliable annotation-mode shortcuts (Escape, V, R, D, E) in apps/desktop. The reviewed change addresses the V/R/D/E failure. In apps/desktop/src/preview/PickPreload.ts, the window keydown guard in startAnnotation no longer ignores every non-Escape key whose target is the overlay host. It now ignores tool keys only when shadowRoot.activeElement is an input, textarea, or select. Focus on a toolbar button therefore no longer blocks tool keys. Escape handling runs before this guard and is unchanged. The PR description reports Escape working from a toolbar button, a comment box, a text input and a select (R12), with reopening working. Text-field protection is kept (R05 to R09). The new test in apps/desktop/src/preview/PickPreload.test.ts, switches tools from the keyboard after a toolbar button is clicked, targets the reported failure. The second test, keeps the tool while typing a tool key in the comment, covers the text-field guard. Test infrastructure (jsdom devDependency and a dom Vitest project) supports these tests. Limits: the evidence is from Linux with Electron and synthetic input. Windows, the reporter's platform, was not checked. The reporter's Escape failure was not shown as a before/after pair. I did not run the test suite. The conclusion relies on the PR description, the change summary, and the stated test results.

Full details: Out of Scope Changes check

Explanation

All changed areas connect to the linked issue or to its test coverage. The PickPreload.ts change is the production fix for the keyboard guard. PickPreload.test.ts adds focused regression tests for the same behavior. apps/desktop/package.json adds jsdom as a devDependency. This is required to run the DOM test. apps/desktop/vite.config.ts splits Vitest into unit and dom projects so that the DOM test can run with jsdom. The PR states that existing desktop test files remain under the unit project, and the change summary confirms that unit tests keep their existing includes and setup files. The commit that moves preload loading to test collection addresses a test timeout in the new test. It does not alter application behavior. pnpm-lock.yaml is excluded from review by the ignored_files rule, so its contents were not inspected. Its change is limited to the jsdom dependency already described in package.json, and this does not affect the assessment. No unrelated production behavior change was identified in the reviewed summary.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@juliusmarminge juliusmarminge added the pr:gate-failed A land gate failed label Oct 28, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed size:S 10-29 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: The browser's select tool keyboard shortcuts are unreliable

1 participant