Which package?
@termuijs/core
What happened?
FocusManager.release() unconditionally pops the trap stack with no validation:
release(): void {
this._trapStack.pop();
}
This causes two distinct bugs:
Bug A — Calling release() on an empty stack is silently ignored.
If a component's cleanup logic calls release() more than once (e.g., due to double-unmount
in a dev hot-reload or an error boundary teardown), there is no error or warning. The stack
silently stays empty. This makes debugging focus state corruption nearly impossible.
Bug B — release() has no containerId parameter, making nested modal cleanup order-sensitive
with no enforcement.
release() always pops the top of the stack regardless of which container is releasing.
If two nested modals are open (trap stack: ['outer-modal', 'inner-modal']) and the outer
modal's cleanup fires before the inner's (which can happen in React-style teardown), it pops
'inner-modal' off the top — the wrong entry. The inner modal then pops 'outer-modal'.
The trap stack ends up in a state that neither modal intended, and the bug produces no error.
Expected: release() should accept a containerId, verify it matches the top of the
stack, and emit a warning/throw if it does not.
Actual: Any call to release() pops the stack top silently — wrong container, empty
stack, or double-release are all silent no-ops.
Steps to reproduce
```typescript
import { FocusManager } from '@termuijs/core';
const fm = new FocusManager();
fm.register({ id: 'a', tabIndex: 0, focusable: true });
fm.register({ id: 'modal-btn', tabIndex: 0, focusable: true });
fm.registerContainerMembers('my-modal', ['modal-btn']);
fm.trap('my-modal');
console.log(fm.isTrapped); // true
// Simulate double-release (e.g. cleanup fired twice)
fm.release();
fm.release(); // No error. Stack is now empty. Silent bug.
console.log(fm.isTrapped); // false — correct
// But if there was a second trap underneath, it was silently destroyed.
```
Environment
- TermUI version: latest (main branch)
- Node.js 18+
- Any OS / terminal
GSSoC contributor?
Which package?
@termuijs/core
What happened?
FocusManager.release()unconditionally pops the trap stack with no validation:This causes two distinct bugs:
Bug A — Calling release() on an empty stack is silently ignored.
If a component's cleanup logic calls
release()more than once (e.g., due to double-unmountin a dev hot-reload or an error boundary teardown), there is no error or warning. The stack
silently stays empty. This makes debugging focus state corruption nearly impossible.
Bug B — release() has no containerId parameter, making nested modal cleanup order-sensitive
with no enforcement.
release()always pops the top of the stack regardless of which container is releasing.If two nested modals are open (trap stack:
['outer-modal', 'inner-modal']) and the outermodal's cleanup fires before the inner's (which can happen in React-style teardown), it pops
'inner-modal'off the top — the wrong entry. The inner modal then pops'outer-modal'.The trap stack ends up in a state that neither modal intended, and the bug produces no error.
Expected:
release()should accept acontainerId, verify it matches the top of thestack, and emit a warning/throw if it does not.
Actual: Any call to
release()pops the stack top silently — wrong container, emptystack, or double-release are all silent no-ops.
Steps to reproduce
```typescript
import { FocusManager } from '@termuijs/core';
const fm = new FocusManager();
fm.register({ id: 'a', tabIndex: 0, focusable: true });
fm.register({ id: 'modal-btn', tabIndex: 0, focusable: true });
fm.registerContainerMembers('my-modal', ['modal-btn']);
fm.trap('my-modal');
console.log(fm.isTrapped); // true
// Simulate double-release (e.g. cleanup fired twice)
fm.release();
fm.release(); // No error. Stack is now empty. Silent bug.
console.log(fm.isTrapped); // false — correct
// But if there was a second trap underneath, it was silently destroyed.
```
Environment
GSSoC contributor?