Skip to content

feat(scanning): add row scanning, gentler scrolling and switch repeat stop - #199

Merged
enaboapps merged 2 commits into
mainfrom
codex/scanning-rows-repeat-198
Oct 9, 2026
Merged

enaboapps merged 2 commits into
mainfrom
codex/scanning-rows-repeat-198

Conversation

@enaboapps

@enaboapps enaboapps commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes #198

Summary

  • Rows option: Settings → Switch scanning now offers Sections, Rows and Every control. In Rows mode, ResponsiveGrid turns each row of several controls into a nested scan group (section, then row, then control), ending with a labelled "Leave row" stop. Lone controls stay direct. Stored as scanning.rows, defaulting to off.
  • Gentler scrolling: the highlighted control only scrolls into view when it isn't fully visible inside its scroll area. It is then centred with a smooth scroll (instant with reduced motion), or aligned to the top if too tall.
  • Repeat stop: while pointer movement or a key repeats, the next switch press stops the repeat and is consumed, as on Switchify Android. This uses interceptPress from @switchify/scanning v0.4.0 (Let apps consume a switch press with interceptPress switchify-scanning#4) through a useScanInterrupt registry that the Remote screen fills.
  • Hold during repeats: automatic scanning stays on the chosen control until the repeat stops, using holdMovement from v0.5.0 (Hold automatic movement for app-defined reasons switchify-scanning#5).
  • Manual setup: choosing Manual adds any missing Next or Previous switch on a free key (Enter and Backspace first, as in Switchify PC), so manual scanning works with the default switches.

Validation

  • npm run validate: lint, typecheck, 691 Jest tests and 21/21 Expo Doctor checks pass.
  • Driven in headless Chrome against a production web export with a fake Web Bluetooth PC:
    • Rows: Movement scans as three row groups and then "Leave section". A row scans its arrows and then "Leave row".
    • Manual: choosing it with the default switches saves Space as Select, Enter as Next and Backspace as Previous.
    • Repeat, with automatic scanning at one step per second: selecting Move up sent mouse.repeat.start, and the highlight stayed on Move up for 2.8 seconds. The next press sent mouse.repeat.stop without a scan action, and scanning then carried on.
    • Scrolling: over 18 steps through every control at a 640px-tall viewport, the page scrolled once.

🤖 Generated with Claude Code

… stop

- Add a Rows option: in grouped scanning each grid row of several controls becomes its
  own group inside its section, ending with a "Leave row" stop
- Only scroll when the highlighted control is not fully visible in its scroll area,
  then centre it smoothly (instantly with reduced motion) instead of nudging each step
- While pointer movement or a key repeats, the next switch press stops the repeat and
  is consumed, using @switchify/scanning v0.4.0's interceptPress
- Cover row preferences, the scroll decision and scan interrupts with tests, and
  document the behaviour

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
switchify-remote Ready Ready Preview Oct 9, 2026 2:12pm UTC

Request Review

- Hold automatic scanning on the chosen control while pointer movement or a key
  repeats, using @switchify/scanning v0.5.0's holdMovement
- Choosing Manual adds any missing Next or Previous switch on a free key (Enter and
  Backspace first), so manual scanning works with the default switches
- Test the switch helper and document both

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@enaboapps
enaboapps marked this pull request as ready for review October 9, 2026 14:30
@enaboapps
enaboapps merged commit d72126d into main Oct 9, 2026
5 checks passed
@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium impact] Adds row-level scanning and repeat-stop behavior to switch interface.

Do not merge until horizontally clipped scan controls are revealed and automatically added manual switches receive unique IDs.

Findings

  1. P1 Highlighted controls stay off-screen ▶
  2. P1 Manual mode cannot be saved ▶
Fix with agent prompt
### Issue 1
src/scanning/useScannable.ts:43-44
`reveal` skips scrolling whenever a control fits vertically, even if it is off-screen horizontally. Saved layouts use a horizontal `ScrollView` and can overflow on narrow screens. Scanning a clipped column now leaves its highlight hidden because `scrollIntoView` never runs, so switch users cannot see which control they are selecting. Check horizontal visibility too, and scroll when either axis is clipped before merging.

### Issue 2
src/scanning/preferences.ts:84
`withManualSwitches` can reuse an existing switch ID. Choose Manual once, return to Automatic, change the added Previous switch to Pause, then choose Manual again. The helper adds another `switch-back` on a free key, but `validateSwitchSettings` rejects the duplicate ID, so Manual cannot be saved. Choose an unused ID as well as an unused key before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

T-Rex evidence

Before: F1 01

  • What the screen looked like at this point in the check.

▶ f1-01-before.webm

  • The flow T-Rex ran, recorded end to end.

After: F1 02

  • What the screen looked like at this point in the check.

▶ f1-02-after.webm

  • The flow T-Rex ran, recorded end to end.

Before: F2 Pr199 01

  • What the screen looked like at this point in the check.

▶ f2-pr199-01-before.webm

  • The flow T-Rex ran, recorded end to end.

After: F2 Pr199 02

  • What the screen looked like at this point in the check.

▶ f2-pr199-02-after.webm

  • The flow T-Rex ran, recorded end to end.

Evidence from the check

  • The preserved authored script executed the actual base and HEAD reveal functions and produced the regression evidence; this reference is repository-local.

Evidence from the check

  • The preserved command script captured source evidence and runtime output with execution metadata; this reference is repository-local.

Evidence from the check

  • The preserved retry command ran the corrected harness and retained the initial error output; this reference is repository-local.

Command output from the check

  • The preserved observed output records exit code 0, browser measurements, and passing regression assertions; this reference is repository-local.

▶ Recording of the check

  • The preserved Chromium recording shows base revealing the highlighted control horizontally; this reference is repository-local.

▶ Recording of the check

  • The preserved Chromium recording shows HEAD leaving the highlighted control horizontally clipped; this reference is repository-local.

Existing base screenshot — upload unconfirmed

  • The preserved browser screenshot captures the base result after horizontal reveal; this reference is repository-local.

Existing HEAD screenshot — upload unconfirmed

  • The preserved browser screenshot captures the HEAD result without horizontal reveal; this reference is repository-local.

Command output from the check

  • The preserved command output records the commit diff and relevant numbered source; this reference is repository-local.

Evidence from the check

  • The preserved generated page contains the executed base function and overflow fixture; this reference is repository-local.

Evidence from the check

  • The preserved generated page contains the executed HEAD functions and overflow fixture; this reference is repository-local.

Evidence from the check

  • The preserved source was extracted from base f6515ac for the executed harness; this reference is repository-local.

Evidence from the check

  • The preserved source was extracted from HEAD 3dc841d for the executed harness; this reference is repository-local.

Command output from the check

  • The preserved output documents the initial harness failure before correction and is not successful regression proof; this reference is repository-local.

Evidence from the check

  • Preserved the executed Chromium sequence and persisted-state assertions; this is the reproducible source, not a confirmed upload.

Evidence from the check

  • Preserved the executed wrapper that records command, working directory, and exit code; upload remains unconfirmed.

Evidence from the check

  • Preserved the server source used to render the unchanged export on port 8192; upload remains unconfirmed.

Command output from the check

  • Preserved actual execution output with exit code 0 and passing assertions confirming F2; upload remains unconfirmed.

▶ Recording of the check

  • Recorded Manual setup and successful reassignment to Pause / resume before the triggering selection; upload remains unconfirmed.

Before screenshot — local, upload unconfirmed

  • Captured settings before selecting Manual again; upload remains unconfirmed.

Command output from the check

  • Captured saved preferences and storage writes after reassignment; upload remains unconfirmed.

▶ Recording of the check

  • Recorded the validation error and retained Automatic state after the second Manual selection; upload remains unconfirmed.

Validation-error screenshot — local, upload unconfirmed

  • Captured the rendered unique-ID validation error after the triggering selection; upload remains unconfirmed.

Command output from the check

  • Captured the duplicate-ID candidate, validator error, and unchanged persisted state; upload remains unconfirmed.

Command output from the check

  • Captured Automatic and the Pause binding remaining persisted after reload; upload remains unconfirmed.

View artifacts

Summary

Adds row scan groups, quieter scrolling, switch presses that stop repeats, and automatic movement holds during repeats. Choosing Manual also adds missing movement switches.

Two scanning problems need fixes before merging:

  • Rows mode scans grid rows between sections and their controls.
  • Manual mode fills in missing Next and Previous switches.
  • A switch press stops a repeating Remote action.
  • Highlighted controls scroll only when they are out of view.

T-Rex validation blocked

Both browser checks completed, but evidence publication is blocked because no artifact-upload operation or authorized upload destination is available. The executed scripts and observed output remain repository-local; no uploaded evidence references could be confirmed.

Reviews (1) · Last reviewed commit: "feat(scanning): hold scanning during rep..." · Reviewed by Greptile

Comment on lines +43 to +44
const block = revealPlan(element.getBoundingClientRect(), scrollArea(element));
if (block) element.scrollIntoView({ block, inline: 'nearest', behavior: reducedMotion ? 'auto' : 'smooth' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Highlighted controls stay off-screen

reveal skips scrolling whenever a control fits vertically, even if it is off-screen horizontally. Saved layouts use a horizontal ScrollView and can overflow on narrow screens. Scanning a clipped column now leaves its highlight hidden because scrollIntoView never runs, so switch users cannot see which control they are selecting. Check horizontal visibility too, and scroll when either axis is clipped before merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/scanning/useScannable.ts
Line: 43-44

Comment:
**Highlighted controls stay off-screen**

`reveal` skips scrolling whenever a control fits vertically, even if it is off-screen horizontally. Saved layouts use a horizontal `ScrollView` and can overflow on narrow screens. Scanning a clipped column now leaves its highlight hidden because `scrollIntoView` never runs, so switch users cannot see which control they are selecting. Check horizontal visibility too, and scroll when either axis is clipped before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

const used = new Set(bindings.map((item) => item.key));
const key = MANUAL_KEYS[action].find((candidate) => !used.has(candidate));
if (!key) continue;
bindings.push({ id: `switch-${action}`, name: action === 'next' ? 'Next' : 'Previous', key, pressAction: action, holdActions: [] });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Manual mode cannot be saved

withManualSwitches can reuse an existing switch ID. Choose Manual once, return to Automatic, change the added Previous switch to Pause, then choose Manual again. The helper adds another switch-back on a free key, but validateSwitchSettings rejects the duplicate ID, so Manual cannot be saved. Choose an unused ID as well as an unused key before merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/scanning/preferences.ts
Line: 84

Comment:
**Manual mode cannot be saved**

`withManualSwitches` can reuse an existing switch ID. Choose Manual once, return to Automatic, change the added Previous switch to Pause, then choose Manual again. The helper adds another `switch-back` on a free key, but `validateSwitchSettings` rejects the duplicate ID, so Manual cannot be saved. Choose an unused ID as well as an unused key before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

This branch was successfully deployed

1 active deployment
Preview — 3dc841d2 Deployed Oct 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add row scanning, gentler auto-scroll and switch repeat stop

2 participants