Skip to content

docs: Explain recovering focus when the focused element stops rendering - #3386

Merged
huacnlee merged 1 commit into
longbridge:mainfrom
AncientPixel:docs-focus-lost
Oct 7, 2026
Merged

huacnlee merged 1 commit into
longbridge:mainfrom
AncientPixel:docs-focus-lost

Conversation

@AncientPixel

Copy link
Copy Markdown
Contributor

Description

The Focus guide does not say what happens when a view stops rendering the element that holds Focus, for example when a table is replaced by an empty state. In an app this shows up as "keyboard shortcuts stop working after a view is hidden".

What GPUI does (gpui-pre 0.3.8, the version pinned on main):

  • The window keeps the old FocusId. Window::focus_node_id_in_rendered_frame (src/window.rs:6325) looks it up in the rendered dispatch tree and, when the element was not rendered, falls back to dispatch_tree.root_node_id(). Key and action dispatch then walks the path to the root node only, so bindings under the view's key_context and its on_action handlers are skipped. Only handlers on the root node and global cx.on_action handlers still run.
  • GPUI already has the recovery hooks. After a frame is drawn, if the previous focus path was non-empty and the new one is empty, the window runs the focus_lost_listeners (src/window.rs:3406). Context::on_focus_lost(window, listener) (src/app/context.rs:616) registers one. While those listeners run, Window::focus_lost_restore_target(cx) (src/window.rs:2337) returns the closest ancestor of the lost element that is still rendered and focusable.

The change:

  • website/docs/focus.md: a short section, "When the focused element stops rendering", that explains the fallback and shows a root view restoring Focus with on_focus_lost and focus_lost_restore_target. A new row in the "Verify and debug" table for the symptom.
  • website/zh-CN/docs/focus.md: the same section and table row in Chinese.
  • skills/gpui-kit/references/gpui/focus-handle.md: the same pattern as item 4 under "Common Patterns". This file is source, not one of the vendored copies of website/docs/ that AGENTS.md lists.

This is a documentation-only change. It was written with AI assistance (Claude Code).

How to Test

  1. Read the new section in website/docs/focus.md and website/zh-CN/docs/focus.md, and the new table row in each.
  2. The example compiles. I put it in a scratch crate that depends on this branch's crates/kit by path (so gpui-pre 0.3.8), with a Render impl and main around it. cargo check passed with no errors and no warnings. The crate's src/main.rs:
src/main.rs
use gpui_kit::*;

struct Library {
    focus_handle: FocusHandle,
    _focus_lost: Subscription,
}

impl Library {
    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
        let focus_lost = cx.on_focus_lost(window, |this, window, cx| {
            let target = window
                .focus_lost_restore_target(cx)
                .unwrap_or_else(|| this.focus_handle.clone());
            target.focus(window, cx);
        });

        Self {
            focus_handle: cx.focus_handle(),
            _focus_lost: focus_lost,
        }
    }
}

impl Render for Library {
    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
        div()
            .key_context("Library")
            .track_focus(&self.focus_handle)
            .child("No cards yet")
    }
}

fn main() {
    gpui_kit::application().run(|cx| {
        gpui_kit::init(cx);
        gpui_kit::open_window(WindowOptions::default(), cx, |window, cx| {
            cx.new(|cx| Library::new(window, cx))
        })
        .expect("Failed to open window");
    });
}
  1. The website docs tests (bun run test:docs) and typos were not run: neither bun nor typos is installed on my machine. The change adds no links and no version strings.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes. Not applicable: documentation only.
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) Not applicable: documentation only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PRpJSFk3zTwdgAJHehdZFm

When a view stops rendering the element that holds Focus (a table
swapped for an empty state), the window keeps the old FocusId and key
dispatch falls back to the dispatch tree's root node. The view's
key_context bindings and on_action handlers then stop firing, and the
Focus guide gave no hint why.

Add a short section to the Focus guide, in English and Chinese, that
explains the fallback and shows a root view restoring Focus with
cx.on_focus_lost and window.focus_lost_restore_target, plus a symptom
row in the "Verify and debug" table. Add the same pattern to the
gpui-kit skill's focus-handle reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRpJSFk3zTwdgAJHehdZFm
@AncientPixel

Copy link
Copy Markdown
Contributor Author

The failing GPUI Fast (windows-latest) check isn't from this change. It fails at link time with LNK1123: failure during conversion to COFF, the same way it fails on main at c0bebdc, the commit this branch is based on. #3380 looks like the fix. Once it lands I'll rebase onto main so CI runs again.

@huacnlee
huacnlee merged commit bf44ad7 into longbridge:main Oct 7, 2026
15 of 16 checks passed
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.

2 participants