Skip to content

Refactor and fix focus management#1276

Merged
epatters merged 10 commits into
mainfrom
kb/focus-context
May 20, 2026
Merged

Refactor and fix focus management#1276
epatters merged 10 commits into
mainfrom
kb/focus-context

Conversation

@kasbah
Copy link
Copy Markdown
Member

@kasbah kasbah commented May 15, 2026

This is largely a refactor of focus and keyboard shortcut management (closes #161). It also:

image

@kasbah kasbah added the frontend TypeScript frontend and Rust-wasm integrations label May 15, 2026
@kasbah kasbah force-pushed the kb/focus-context branch from e7fdff7 to 6978dbd Compare May 15, 2026 14:29
@kasbah kasbah requested a review from epatters May 15, 2026 18:53
Copy link
Copy Markdown
Member

@epatters epatters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for undertaking this refactor. Focus management has been a longstanding problem, and I'm glad we're thinking through a design for it instead of piling on hacks.

There may well be subtleties that I'm overlooking, but the present design is more centralized and specific to notebooks that I would have expected. As the comments in type.ts explain, the application-level focus state says not only which notebook is focused, but which cell and even which popover in the notebook is focused. I have several worries about this design:

  1. It is specialized to notebooks, but we will have documents that are not notebooks
  2. It leaks information deep in the component hierarchy up to the application top-level
  3. It conflates data and layout: focus is attached to notebooks by ID, rather than to panes

To focus the mind, consider the concrete scenario of integrating a Petri net editor into CatColab (cf. #1279). Such an editor will need to get and lose focus coherently with the rest of the app.

I'm trying to imagine a more compositional/hierarchical approach, where a component tracks which of its immediate children has focus. We're already kind of doing this in the notebook and object/morphism editors, e.g., a notebook keeps track of which cell is focused. I propose doing the same thing one level up: the document page should keep track of which of its 1-2 panes has focus. In this approach, focus changes bubble up the component hierarchy, e.g., when a name field in a morphism editor gets focus, it notifies its cell that it has focus, which notifies its notebook, which notifies the document page. In this way, a component pushes its focus events upward.

To handle keyboard shortcuts, components must also be able to "pull" the latest focus state, since another component may have grabbed it. To do so, one must walk up the component hierarchy again, asking if each parent has focus. I would imagine this pulling and pushing could be handled by passing a getter and a setter for focus state.

What do you think?

changeNotebook={(f) => liveDoc().changeDoc((doc) => f(doc.notebook))}
formalCellEditor={AnalysisCellEditor}
cellConstructors={cellConstructors()}
noShortcuts={true}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exciting to finally be rid of this hack!

@kasbah kasbah force-pushed the kb/focus-context branch 3 times, most recently from e33f92b to 9e0c9cd Compare May 19, 2026 12:29
@kasbah kasbah force-pushed the kb/focus-context branch from 9e0c9cd to 219612e Compare May 19, 2026 16:21
@kasbah kasbah changed the title Introduce focus and shortcut contexts Refactor and fix focus management May 19, 2026
@kasbah
Copy link
Copy Markdown
Member Author

kasbah commented May 19, 2026

I've introduced a FocusHandle and a useChildFocus that handles the recursion into parent components. I believe this implements your idea as described and seems to mesh better with the rest of the code than my previous design.

Copy link
Copy Markdown
Member

@epatters epatters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Kaspar! The way you've set up the new FocusHandle abstraction is quite elegant IMO.

if (!props.isActive) {
setActiveInput("name");
}
});
Copy link
Copy Markdown
Member

@epatters epatters May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great that the new design avoids having to handle this tricky logic across a bunch of individual components.

@epatters epatters merged commit 0bb57a1 into main May 20, 2026
17 checks passed
@epatters epatters deleted the kb/focus-context branch May 20, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend TypeScript frontend and Rust-wasm integrations

Projects

None yet

2 participants