feat(canvas): focus mode — one node fills the window, chrome yields (⌘⇧F) - #267
feat(canvas): focus mode — one node fills the window, chrome yields (⌘⇧F)#267eneskirca wants to merge 5 commits into
Conversation
…⌘⇧F) The #78 focus-mode feature, v1 in the shape agreed there: - The focused .term-node root is REPARENTED (imperatively, in a layout effect) into an always-mounted fixed surface outside <ReactFlow> — the same DOM-move operation park/adopt already proves safe, so the SAME xterm element carries over with no respawn and no redraw. Not a React portal: switching portal containers remounts the subtree and would blank the terminal. The cleanup restores the node before React could ever detach it (unfocus AND unmount). - Shared-GPU interplay is the agreed v1: `focused` joins the glyphOff MUST-BE-OPAQUE reasons (a portaled node is not where the shared layer paints), riding the exact setup/teardown path collapse/cmd-M/stacking/drag use, and the focused id joins the opaque set in the same render that commits the reparent. Mouse-coordinate math reads scale 1 while focused (the node left the viewport transform); the getter reads the store keyed by node id, so a parked terminal's closure stays correct across adopt. - ⌘⇧F toggles (selected terminal; palette "Focus node" too). Esc is NEVER an exit key — it reaches the CLI — so exit is the same chord or the pill in the surface; leaving lands framed on the node via goToNode. Refused while the kanban board is up (its card modal is already a focused session view). - Chrome isn't display-toggled: the opaque surface (z 40) simply covers it, and the dock lifts above (z 41) on pointer proximity or hover, faded by opacity only. fitView's obstacle math is untouched. Needs a real-device pass (GPU + shared-renderer interaction) before this is called done — flagged in the PR checklist. Refs #78 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review from the terminal-rendering side (I own the WebGL budget coordinator and the glyphgrid attachment paths). Two interactions with that machinery, plus a note on what this PR already gets right. What's already right, so it doesn't get "fixed" later: adding 1. On a busy canvas the fullscreen terminal can end up on the DOM renderer (the one thing focus mode most wants accelerated). The focus surface is an opaque layer above the canvas ( Two ways out, either is small:
2. Shared-renderer mode paints one blank frame per ⌘⇧F (and per exit). The reparent is a Narrowest fix: in the focus layout effect, run Neither finding is a correctness bug in the focus feature itself; 1 is the one I'd want closed before merge, because it silently undercuts exactly the case the feature exists for. |
…ode blank frame Review on #267 (terminal-rendering side) found two interactions with the renderer machinery; both closed in the recommended shape: 1. Budget starvation: IntersectionObserver does not model occlusion, so covered-but-displayed terminals kept reporting visible, kept their WebGL contexts, and a full budget refused the one node filling the screen (tryGrant only evicts hidden holders). The flow pane now goes display:none while focused — covered nodes report hidden, the budget reclaims them, and occluded terminals stop rendering invisible frames. Two consequences are handled with it: covered terminals vote "not viewing" (null) through applyFit's new focus gate — the kanban board's exact semantics, re-run on every focus flip via a mount-stable subscription — and the exit-time goToNode is deferred past the class flip plus two frames, because fitView math against a 0-sized pane is the origin-jump bug. 2. Shared-mode blank frame: the reparent ran before paint (layout effect) while the grid teardown ran after (passive effect), so one fullscreen frame painted with the grid still registered on-canvas and the node still in glyph-mode styling. The focus layout effect now runs the glyph sync (off) in the same commit as the DOM move; the passive effect then re-runs with the same answer and no-ops. Refs #78 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both findings closed in 1 (budget starvation) — took the first option: 2 (shared-mode blank frame) — the narrow fix as suggested: the focus layout effect runs Thanks for confirming the glyphOff term and the store-read scale getter — noted as load-bearing in the comments so they don't get "simplified" later. |
…ced two sections The conflict resolution in 6baf545 reordered the two appended sections by slicing the conflict hunk, but the hunk covered only the differing middle — the focus-mode header ended up glued mid-keyframes with its comment never terminated, which is exactly the 'Unterminated string' the tailwind pass tripped on in CI. Rebuilt wholesale from the two clean sources (origin/main's tail + the focus section from a8e7372, with the review-fix flow-wrap rule re-applied). electron-vite build verified locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The #78 focus-mode feature, v1 in the exact shape agreed there (same-DOM reparent, Esc never exits, forced DOM/WebGL renderer for the focused node while the shared layer is on).
How it works
.term-noderoot is reparented imperatively (layout effect) into an always-mounted fixed surface OUTSIDE<ReactFlow>— the DOM move park/adopt already proves safe; every listener the terminal owns is bound toterm.elementor the host. Not a React portal: switching portal containers remounts the subtree and would blank the terminal. The effect cleanup restores the node before React could ever detach it (unfocus AND unmount/project switch).focusedjoins theglyphOffMUST-BE-OPAQUE reasons — a reparented node is not where the shared layer paints — riding the same setup/teardown path collapse/⌘M/stacking/drag use; the focused id also joins the opaque set in the same render that commits the reparent. Teaching the shared layer an off-canvas plate stays the follow-up.patchTerminalScalegets a getter that answers 1 while focused; it reads the focus STORE keyed by node id, so a parked terminal's closure stays correct across park/adopt.goToNode(no bare fitView). Refused while the kanban board is up.fitView's obstacle math is untouched.Needs a real-device pass before this counts as done
Refs #78
🤖 Generated with Claude Code