diff --git a/CHANGELOG.md b/CHANGELOG.md index d958548..4877272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,17 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). renders the canonical `--flag` form. ### Fixed +- Opening the command palette while a scratchpad was focused left the + palette wedged — typing did nothing and Esc left the palette's top + border drawn over the pad until you closed the pad with Ctrl-W and + re-opened the palette. The stdin loop's scratchpad-input branch ran + before the palette branch and silently dropped every byte except a + handful of app-level chords, so palette filter input and Esc never + reached `palette.handleInput`. The palette branch now takes + precedence whenever the palette is open, and `closePalette` repaints + the pad (instead of the empty focused-child slot) on cancel / no-op + action. Switching from a pad to a child via the palette now clears + the pad focus and wipes the viewport, matching `focusProcess`. - Tab bar and bottom status row no longer get overwritten by long claude / codex sessions. Three holes were letting child output land on the chrome: (1) absolute cursor moves — CUP / HVP / VPA — added diff --git a/TODO.md b/TODO.md index 57e41f3..8645cfa 100644 --- a/TODO.md +++ b/TODO.md @@ -12,10 +12,6 @@ still feels slow after ≥15 minutes — the structural drivers the audit named are all addressed, so a remaining symptom is a new one and probably wants fresh profiling. -- [ ] Opening the command palette with a scratchpad open creates very buggy ui. - - Typing into the command palette doesn't work at all - - Hitting esc causes buggy chrome, the top border of the command palette is still visible - - This is only fixed by Ctrl + W, hitting esc again to close the palette, then re-opening it when over an agent view. - [ ] Context aware command palette options - Options for current scratchpad (delete, rename, edit) at the top when a scratchpad is selected. - Options for current agent (rename [renames tab], close) at the top when an agent is selected. diff --git a/internal/app/app.go b/internal/app/app.go index 9a8a1d5..4b70acf 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1178,7 +1178,11 @@ func (st *uiState) processStdin(chunk []byte) { // palette, Ctrl-WASD focus, Ctrl-B scrollback) fall through to // the handlers below; everything else is swallowed silently so // typing into a pad view can't leak to a child PTY. - if st.focusedPad != "" { + // + // When the palette is open we skip this block entirely so the + // palette handler below receives every byte. Otherwise typing + // (and Esc) get swallowed here and the palette appears wedged. + if st.focusedPad != "" && st.palette == nil { if b == 0x1b { // ESC or CSI if n := csiLen(chunk, i); n > 0 { final := chunk[i+n-1] @@ -1553,16 +1557,32 @@ func (st *uiState) closePalette(action paletteAction) { st.outMu.Unlock() st.clearScreen() + // When a scratchpad is focused, the "main viewport" is showing the + // pad's rendered body, not a child PTY. repaintFocused() would draw + // an empty state (focusedID == "") and leave the previous palette's + // top border visible in the pad area. Use the pad-aware helper for + // any branch below that wants to restore the prior view. + restoreView := func() { + st.mu.Lock() + padFocused := st.focusedPad != "" + st.mu.Unlock() + if padFocused { + st.repaintFocusedPad() + return + } + st.repaintFocused() + } + switch action.kind { case "", "cancel": - st.repaintFocused() + restoreView() st.drawTabBar() st.drawSidebar() st.drawStatusLine() case "spawn-agent": if action.preset == nil { - st.repaintFocused() + restoreView() return } l := st.layoutSnapshot() @@ -1575,7 +1595,7 @@ func (st *uiState) closePalette(action paletteAction) { case "spawn-process": if action.preset == nil { - st.repaintFocused() + restoreView() return } l := st.layoutSnapshot() @@ -1586,7 +1606,7 @@ func (st *uiState) closePalette(action paletteAction) { case "spawn-process-submit": if action.command == "" { - st.repaintFocused() + restoreView() return } l := st.layoutSnapshot() @@ -1617,16 +1637,24 @@ func (st *uiState) closePalette(action paletteAction) { case "switch": c := st.sess.FindChild(action.childID) if c == nil || (c.Kind == KindAgent && c.Status() != StatusRunning) { - st.repaintFocused() + restoreView() return } layout := st.layoutSnapshot() st.mu.Lock() + leavingPad := st.focusedPad != "" + st.focusedPad = "" st.focusedID = action.childID st.focusedName = c.DisplayName() st.updateActiveAgentLocked(c) st.renderer = newViewportRenderer(layout) st.mu.Unlock() + // Switching from a pad to a child: wipe the pad body so the + // child's snapshot paints onto a clean canvas, mirroring + // focusProcess. + if leavingPad { + st.clearViewportArea() + } st.repaintFocused() st.drawTabBar() st.drawSidebar() diff --git a/internal/harness/scenarios/palette_over_scratchpad.json b/internal/harness/scenarios/palette_over_scratchpad.json new file mode 100644 index 0000000..5f6289a --- /dev/null +++ b/internal/harness/scenarios/palette_over_scratchpad.json @@ -0,0 +1,28 @@ +{ + "name": "palette_over_scratchpad", + "cols": 120, + "rows": 30, + "steps": [ + { + "type": "mcp_call", + "method": "scratchpad_write", + "params": { "name": "pad-marker.md", "content": "# Pad Heading\n\nzealot-marker body line" } + }, + { "type": "wait_stable", "timeout_ms": 2000 }, + { "type": "send_chord", "chord": "ctrl-s" }, + { "type": "wait_text", "contains": "zealot-marker", "timeout_ms": 5000 }, + { "type": "assert_contains", "contains": "Pad Heading" }, + + { "type": "send_chord", "chord": "ctrl-k" }, + { "type": "wait_stable", "timeout_ms": 2000 }, + { "type": "send_text", "text": "quit" }, + { "type": "wait_text", "contains": "quit", "timeout_ms": 5000 }, + { "type": "assert_contains", "contains": "quit" }, + + { "type": "send_chord", "chord": "escape" }, + { "type": "wait_text", "contains": "zealot-marker", "timeout_ms": 5000 }, + { "type": "assert_contains", "contains": "Pad Heading" }, + { "type": "assert_contains", "contains": "zealot-marker" }, + { "type": "assert_not_contains", "contains": "quit" } + ] +}