Repository navigation
Let scanning hooks run outside a provider - #2
Conversation
- Outside a ScanProvider the hooks use a shared controller that is never started, so shared components can call them whether or not their screen is scanned - Bump to 0.3.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
| if (!controller) throw new Error("Scanning hooks must be used inside a ScanProvider."); | ||
| return controller; | ||
| if (controller) return controller; | ||
| inert ??= new ScanController(); |
There was a problem hiding this comment.
Provider-less controls can start scanning
The fallback is a working, shared ScanController, not an inert one. Outside a ScanProvider, useScanner().start() or dispatch("select") starts it, and a bound key press through useKeyboardSwitches does the same. Controls in unrelated provider-less React trees then share its highlights and activation callbacks. This was reproduced with two separate roots: keyboard input highlighted and activated the second root's control without clicking it. Make the fallback unable to start scanning or activate items, and cover these entry points before merging.
Artifacts
- Rendered two separate providerless React roots and pressed Space, Enter, and Space on the parent revision, showing hook errors rather than a registered scanner.
- Pressed Space, Enter, and Space on the PR revision, showing scanning start, Tree B highlighting, and its handler count increase without clicking its button.
Both providerless roots report hook errors before the PR
- Captured the parent revision after the keyboard sequence, showing that providerless hooks reject mounting instead of enabling scanning.
Unrelated Tree B is highlighted after the PR
- Captured the PR revision after Space and Enter, showing an active scanner highlighting the separate providerless root.
- Ran all three providerless entry-point cases against the parent source and captured passing assertions that both roots throw before registration.
- Ran all three entry-point cases against the PR source and captured passing assertions for cross-root highlighting and handler invocation.
- Executed the browser harness and captured DOM observations for both revisions, confirming that the PR enables unrelated-tree activation.
- Printed revision hashes, both React implementations, related numbered source, and the production diff, locating F1 at line 102 with no production changes.
- Contains the rendered two-root tests and observed-state logging for public start, public select, and keyboard activation, making the reproduction rerunnable.
- Loads the exact parent React source for the before run without editing production files, keeping the two test runs comparable.
- Runs the supplied command and saves its actual output with command, working directory, and exit code, preserving execution provenance.
- Provides the two-root layout and scan-state styling used in both recordings, making the unrelated-tree highlight visible.
- Mounts two separate roots with real scanning hooks and rendered callback counters, exposing cross-root activation without an item click handler.
- Serves each source revision, performs keyboard and public-start interactions, checks rendered results, and captures videos and posters, reproducing F1 in Chromium.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/react/index.tsx
Line: 102
Comment:
**Provider-less controls can start scanning**
The fallback is a working, shared `ScanController`, not an inert one. Outside a `ScanProvider`, `useScanner().start()` or `dispatch("select")` starts it, and a bound key press through `useKeyboardSwitches` does the same. Controls in unrelated provider-less React trees then share its highlights and activation callbacks. This was reproduced with two separate roots: keyboard input highlighted and activated the second root's control without clicking it. Make the fallback unable to start scanning or activate items, and cover these entry points before merging.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| return <ControllerContext.Provider value={controller}>{children}</ControllerContext.Provider>; | ||
| } | ||
|
|
||
| let inert: ScanController | undefined; |
There was a problem hiding this comment.
The module-wide fallback retains every distinct item and group ID it has registered. ScanController.sequenceOf() stores IDs in sequences, but unregistering removes only the live registrations. Because this fallback lasts for the app session, provider-less lists with changing IDs keep growing that map after their rows unmount. Rendering and cleaning up two independent roots retained 400 historical IDs with no live items, groups, or subscriptions. This is a non-blocking memory-retention concern for long-running apps; avoid storing registration history in the inert fallback.
Artifacts
- This executed integration test changes item and group IDs across two independent roots and inspects controller maps after cleanup, proving historical IDs remain.
- This executed runner extracts the parent React module and captures both test runs with their commands, working directory, and exit codes, making the comparison reproducible.
- The before test executes this parent-revision module with relocated core import paths, showing providerless rendering was previously rejected.
- The runner captured revision hashes, the React diff, numbered registration and sequence code, and repository status, tying the runtime result to the requested source location.
- The parent-revision test rendered the providerless component tree and captured the expected provider-required exception, showing no fallback controller was created.
- The current-revision test captured every generation and unmount checkpoint, showing sequence retention grows from 200 to 400 IDs while live registrations return to zero.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/react/index.tsx
Line: 93
Comment:
**Old control IDs accumulate**
The module-wide fallback retains every distinct item and group ID it has registered. `ScanController.sequenceOf()` stores IDs in `sequences`, but unregistering removes only the live registrations. Because this fallback lasts for the app session, provider-less lists with changing IDs keep growing that map after their rows unmount. Rendering and cleaning up two independent roots retained 400 historical IDs with no live items, groups, or subscriptions. This is a non-blocking memory-retention concern for long-running apps; avoid storing registration history in the inert fallback.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Switchify Remote renders shared buttons both inside and outside scanned screens, and its component tests render them on their own. Until now the hooks threw without a
ScanProvider.useScanControllerreturns one shared controller that is never started, so items register harmlessly and are never highlighted.Validation
npm test: 61 tests pass, including a new one for a provider-less render.npm run lintis clean.🤖 Generated with Claude Code