Skip to content

Feat/issue 5129 tui overlay focus#5235

Merged
badlogic merged 4 commits into
earendil-works:mainfrom
nicobailon:feat/issue-5129-tui-overlay-focus
Jun 1, 2026
Merged

Feat/issue 5129 tui overlay focus#5235
badlogic merged 4 commits into
earendil-works:mainfrom
nicobailon:feat/issue-5129-tui-overlay-focus

Conversation

@nicobailon

Copy link
Copy Markdown
Contributor

Fixes #5129.

The issue was caused by focus returning to the editor while a visible overlay still expected to own input. That left the overlay rendered on screen but no longer interactive.

I fixed this in pi-tui, where the relevant state already lives: overlay visibility, focus order, preFocus, nonCapturing, and input routing. Overlay focus restoration is now modeled as a single TUI-level state, so the overlay that actually had focus can reclaim input after temporary replacement UI closes.

This covers raw tui.showOverlay() callers too, not just extension custom UI. It also handles replacement UI keeping input while active, focus moving inside replacement UI, explicit setFocus(null), and stale preFocus chains when child overlays are removed before their parent closes.

I also added OverlayHandle.unfocus({ target }) so callers can intentionally keep an overlay visible while releasing input to the editor or another component.

Expands /overlay-focus into a manual QA demo for multiple visible overlays, focus cycling, text input, dismissal, rendering order, and returning focus to the built-in editor after all overlays close.

demo.mp4

Preserve a focused visible overlay as the input owner across extension custom UI replacement, while letting the replacement receive and close its own input before focus is restored. Fixes earendil-works#5129.
Add an explicit overlay unfocus target so callers can move input to the editor or another component while overlays remain visible. Align fallback overlay focus with visual focus order and cover blocked replacement release, null targets, and multi-overlay cycling.
Update the overlay QA focus demo to exercise three visible overlays with text input, focus cycling, per-panel dismissal, and visual focus ordering.
@badlogic badlogic added the inprogress Issue is being worked on label May 31, 2026
@badlogic

Copy link
Copy Markdown
Collaborator

What it does:

  • Moves overlay focus restoration into pi-tui, so a focused visible overlay can reclaim input after temporary non-overlay UI steals focus and closes. Adds OverlayHandle.unfocus({ target }), expands overlay focus tests, and updates the manual overlay QA demo.

Good:

  • Fixes the reported ctx.ui.custom(factory) without overlay:true bricks any open sibling overlay #5129 path for the common coding-agent flow: overlay opened from editor, non-overlay custom UI takes focus, then restoreEditor() focuses the editor and the overlay reclaims input.
  • The rendering/compositing path is mostly untouched; risk is concentrated in focus routing.
  • Test coverage is much stronger for blocked replacement focus, hidden overlays, setFocus(null), non-capturing overlays, visual focus order, and stale preFocus chains.

Bad:

  • Contributor PR edits packages/tui/CHANGELOG.md. Per repo policy, contributor PRs should not edit changelogs.
  • Coding-agent docs should be updated. packages/coding-agent/docs/tui.md and packages/coding-agent/docs/extensions.md still only document handle.setHidden() / hide() in onHandle, not focus(), unfocus({ target }), or the new “focused visible overlay reclaims input” behavior.
  • Missing an integration/regression test around actual InteractiveMode.showExtensionCustom: overlay ctx.ui.custom(..., { overlay: true }) followed by non-overlay ctx.ui.custom(...). Current tests simulate TUI behavior but not the coding-agent path that caused ctx.ui.custom(factory) without overlay:true bricks any open sibling overlay #5129.

Ugly:

  • setFocus() semantics become stateful when a visible overlay is focused. A caller can no longer reliably move input to a base component with tui.setFocus(base) while the overlay remains visible; the overlay may reclaim input unless the caller uses handle.unfocus({ target }). That may be intended, but it is a public behavior change and needs clear docs.
  • Restoration depends on the temporary UI returning focus to the overlay’s preFocus target. If an overlay was focused with preFocus !== editor, then a coding-agent non-overlay close that calls setFocus(editor) can leave the overlay visible but non-interactive again. That edge is not tested.

Tests:

  • Covered: many TUI-level unit cases for overlay focus stealing/restoring, blocked replacement input, explicit unfocus targets, invisible overlays, null focus, focus order, and preFocus retargeting.
  • Missing: coding-agent integration coverage for the real ctx.ui.custom overlay/non-overlay sequence, and an edge case where overlay preFocus differs from the component focused when replacement UI closes.

This comment is AI-generated.

@badlogic

Copy link
Copy Markdown
Collaborator

@nicobailon sorry for the ai review above, but it's 0:30am and i'm tired :D

@nicobailon nicobailon force-pushed the feat/issue-5129-tui-overlay-focus branch from 3e72873 to 91a2f86 Compare June 1, 2026 04:20
@nicobailon

Copy link
Copy Markdown
Contributor Author

Updated: removed the changelog entry, added the missing coding-agent docs, and covered the real InteractiveMode.showExtensionCustom path with a regression test. TUI now also restores overlay focus when the temporary replacement component is removed from the mounted tree before focus returns.

@badlogic badlogic merged commit 893ba99 into earendil-works:main Jun 1, 2026
1 check passed
@badlogic

badlogic commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

cheers!

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

Labels

inprogress Issue is being worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ctx.ui.custom(factory) without overlay:true bricks any open sibling overlay

2 participants