Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
feat(core): Implement Scoped Keyboard Focus Trap & Modal Management Subsystem (#3346) #3360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(core): Implement Scoped Keyboard Focus Trap & Modal Management Subsystem (#3346) #3360
Changes from all commits
b3bf015File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a comment justifying the non-null assertion.
Line 36 uses
this._trapStack.pop()!. This is a non-null assertion, which TypeScript tooling treats as a type assertion that bypasses the type checker. The coding guidelines require an inline comment explaining why any type assertion is safe.Add a short comment noting that the preceding length check on line 34 guarantees
pop()returns a value.🔧 Proposed fix
popTrap(): string | null { if (this._trapStack.length === 0) return null; - const popped = this._trapStack.pop()!; + // Non-null: the length check above guarantees an element exists. + const popped = this._trapStack.pop()!; this._focusManager.release(); return popped; }As per coding guidelines, "No type assertions without an inline comment explaining why."
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Karanjot786/TermUI
Length of output: 17315
🏁 Script executed:
Repository: Karanjot786/TermUI
Length of output: 4255
🌐 Web query:
Vitest environment node global test aliases describe expect vi import from vitest official docs💡 Result:
In Vitest, environment, globals, and API usage are configured as follows: Environment: The default environment in Vitest is 'node' [1][2]. You can change this to 'jsdom', 'happy-dom', or 'edge-runtime' via the 'test.environment' option in your configuration file [1][3]. For file-specific requirements, you can use the
@vitest-environmentcontrol comment at the top of the file [1][2]: //@vitest-environmentjsdom Globals: By default, Vitest does not inject globals for explicitness [4][5]. To use APIs like 'describe', 'test', 'expect', and 'vi' globally, you must enable them in your configuration [4][6]: // vitest.config.ts export default defineConfig({ test: { globals: true, }, }) If using TypeScript, you must also add 'vitest/globals' to your 'tsconfig.json' to ensure proper type resolution [4][5]: { "compilerOptions": { "types": ["vitest/globals"] } } Imports and Aliases: If 'globals' are disabled (the recommended default), you must import the necessary APIs directly from the 'vitest' package [7][8]: import { describe, expect, test, vi } from 'vitest' Regarding aliases, Vitest uses your Vite configuration [9][10]. You can define path aliases in your 'vite.config.ts' (or 'vitest.config.ts') using standard Vite 'resolve.alias' options [9][10]. Note that for 'vi.mock', Vitest performs static analysis; if you use 'vi' without importing it directly from 'vitest', it may not be able to hoist the mock correctly unless 'globals' is enabled [7][11].Citations:
🌐 Web query:
Vitest supports Bun test imports from 'bun:test' environment node default config💡 Result:
Vitest does not natively support or execute tests using the 'bun:test' module directly. 'bun:test' is a built-in test runner specific to the Bun runtime [1], whereas Vitest is a separate test runner that is designed primarily to run in Node.js environments [2]. While recent versions of Vitest (starting from 4.0.7+) have improved compatibility for running within the Bun runtime, this refers to executing Vitest's own test suite—using Vitest's APIs (e.g., vitest/dist/index.js)—inside the Bun environment [3]. It does not enable the use of Bun's native 'bun:test' API [1]. Attempting to import 'bun:test' while running Vitest can cause issues, such as build-time errors where Vite (Vitest's underlying bundler) attempts to process the import [4] or run-time errors where the Vitest module runner incorrectly modifies module specifiers [5]. Developers needing to run code that uses 'bun:test' must use the native 'bun test' command [1]. If you are using Vitest to test code meant for both environments, you must use Vitest's own mocking and assertion APIs rather than importing 'bun:test' directly [1]. If you encounter issues with static analysis, a common workaround is to use dynamic imports with a non-literal specifier (e.g.,
import('bun' + ':test')) to prevent the bundler from attempting to resolve the module [4].Citations:
Use the project test runner’s APIs in
packages/core/test/focusLock.test.ts.The core package test script runs Vitest against
packages/core/src/**/*.test.*, and Vitest usesimport { ... } from 'vitest'when globals are configured per-project. This file only imports frombun:test, which Vitest does not run and blocksbun testfrom discovering the suite consistently. Move the tests/fixture into the configured core test path, or importdescribe, expect, test, beforeEachfromvitest.🤖 Prompt for AI Agents
Source: Learnings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Strengthen the "tab navigation cycling" test to assert observable focus movement.
This test only checks
handled === true. It does not verify that focus actually moved to'modal-btn2', that a secondTabwraps back to'modal-btn1', or that'bg-btn'is never reachable while the trap is active. The test name promises "cycling inside trap," but nothing here confirms cycling or isolation actually occurred.Add assertions on
focusManager.currentIdafter eachhandleKeyDown('tab')call, and confirm'bg-btn'is skipped.✅ Proposed strengthening
test('handles tab navigation cycling inside trap', () => { focusLock.pushTrap({ containerId: 'modal-1' }, [ { id: 'modal-btn1', tabIndex: 1, focusable: true }, { id: 'modal-btn2', tabIndex: 2, focusable: true }, ]); const handled = focusLock.handleKeyDown('tab'); expect(handled).toBe(true); + expect(focusManager.currentId).toBe('modal-btn2'); + + focusLock.handleKeyDown('tab'); + expect(focusManager.currentId).toBe('modal-btn1'); });As per coding guidelines, "Tests must be real. ... Assert observable behavior or rendered output."
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Assert focus restoration after
popTrap, and add a dedicated isolation test.This test verifies
isLockedbecomesfalseafter Escape, but does not verify that focus is restored to the widget that was focused before the trap (focusManager.currentId), which is an explicit PR objective ("Restore the previous focus highlight state").Separately, no test in this file verifies that background widgets (
'bg-btn') are unreachable via Tab while a trap is active — the core "isolate key event propagation" objective from the linked issue is untested.✅ Proposed additions
test('pops trap on escape key', () => { focusLock.pushTrap({ containerId: 'modal-1' }, [ { id: 'modal-btn1', tabIndex: 1, focusable: true }, ]); const handled = focusLock.handleKeyDown('escape'); expect(handled).toBe(true); expect(focusLock.isLocked).toBe(false); + expect(focusManager.currentId).toBe('bg-btn'); }); + + test('isolates focus from background widgets while trapped', () => { + focusLock.pushTrap({ containerId: 'modal-1' }, [ + { id: 'modal-btn1', tabIndex: 1, focusable: true }, + { id: 'modal-btn2', tabIndex: 2, focusable: true }, + ]); + + for (let i = 0; i < 5; i++) { + focusLock.handleKeyDown('tab'); + expect(focusManager.currentId).not.toBe('bg-btn'); + } + });As per PR objectives, "Restore the previous focus highlight state" and "Include unit tests covering focus isolation and related behavior."
📝 Committable suggestion
🤖 Prompt for AI Agents
Uh oh!
There was an error while loading. Please reload this page.