From 3d36fa4905316a74d6107eb8cde6cc77e73ffd1f Mon Sep 17 00:00:00 2001 From: Jeremy lewi Date: Wed, 13 May 2026 14:42:45 -0700 Subject: [PATCH 1/6] Restore notebook cell focus Signed-off-by: Jeremy lewi --- app/src/components/Actions/Actions.tsx | 166 +++++++++++++++++- app/src/components/Actions/Editor.tsx | 12 +- .../components/Actions/MarkdownCell.test.tsx | 144 +++++++++++++++ app/src/components/Actions/MarkdownCell.tsx | 67 ++++++- 4 files changed, 378 insertions(+), 11 deletions(-) create mode 100644 app/src/components/Actions/MarkdownCell.test.tsx diff --git a/app/src/components/Actions/Actions.tsx b/app/src/components/Actions/Actions.tsx index 565206b3..643f8168 100644 --- a/app/src/components/Actions/Actions.tsx +++ b/app/src/components/Actions/Actions.tsx @@ -287,6 +287,13 @@ type SupportedLanguage = | "markdown" | "python"; +type CellFocusRole = "editor" | "rendered"; + +type NotebookActiveCell = { + refId: string; + focusRole: CellFocusRole; +}; + const outputTextDecoder = new TextDecoder(); const ALWAYS_SKIP_MIMES = new Set([MimeType.StatefulRunmeTerminal]); @@ -481,12 +488,18 @@ export function ActionOutputItems({ outputs }: { outputs: parser_pb.CellOutput[] export function Action({ cellData, - docUri, + docUri = "", isFirst, + restoreFocusRequest = 0, + restoreFocusRole = "editor", + onFocusStateChange, }: { cellData: CellData; - docUri: string; + docUri?: string; isFirst: boolean; + restoreFocusRequest?: number; + restoreFocusRole?: CellFocusRole; + onFocusStateChange?: (state: NotebookActiveCell) => void; }) { const { store } = useNotebookStore(); const { listRunners, defaultRunnerName } = useRunners(); @@ -621,6 +634,28 @@ export function Action({ [], ); + const handleFocusCapture = useCallback( + (event: React.FocusEvent) => { + if (!cell?.refId || !onFocusStateChange) { + return; + } + const target = event.target; + if (!(target instanceof HTMLElement)) { + return; + } + const focusRole = + target.closest("[data-cell-focus-role]")?.dataset + .cellFocusRole === "rendered" + ? "rendered" + : "editor"; + onFocusStateChange({ + refId: cell.refId, + focusRole, + }); + }, + [cell?.refId, onFocusStateChange], + ); + const handleRemoveCell = useCallback(() => { cellData.remove(); setContextMenu(null); @@ -963,7 +998,9 @@ export function Action({ id={`markdown-action-${cell.refId}`} className="group/cell relative flex min-w-0" onContextMenu={handleContextMenu} + onFocusCapture={handleFocusCapture} data-testid="markdown-action" + data-cell-ref-id={cell.refId} > {/* Left gutter: top + bottom add-cell buttons */}
@@ -994,6 +1031,8 @@ export function Action({ languageOptions={LANGUAGE_OPTIONS} onLanguageChange={handleLanguageChange} forceEditRequest={markdownEditRequest} + restoreFocusRequest={restoreFocusRequest} + restoreFocusRole={restoreFocusRole} /> {/* Trash icon on the right, visible on hover */}