diff --git a/docs/quick-pane-v1-design.md b/docs/quick-pane-v1-design.md index 4a55f18..51fc780 100644 --- a/docs/quick-pane-v1-design.md +++ b/docs/quick-pane-v1-design.md @@ -40,7 +40,7 @@ The app creates or focuses a dedicated Quick Pane window. `Esc` and **Close** hi `Ctrl+N` starts a new chat: it aborts active work when needed, closes the old runtime with retryable cleanup, clears draft/transcript and submitted-prompt mappings, then prepares a fresh runtime and model catalog. Automatic stale renewal performs the same idle-chat replacement while preserving the current draft. -Quick Pane opens at 640×240 with a 520×240 minimum. Frontend content measurement requests native growth up to 520px tall after transcript updates; beyond that cap, transcript and model results scroll inside pane while outer document and pane remain clipped. +Quick Pane opens at 640×360 with a 520×360 minimum. Frontend content measurement requests native growth up to 520px tall after transcript updates; beyond that cap, transcript and model results scroll inside pane while outer document and pane remain clipped. ## Escalation diff --git a/docs/quick-pane-v1-test-plan.md b/docs/quick-pane-v1-test-plan.md index a208812..a28731a 100644 --- a/docs/quick-pane-v1-test-plan.md +++ b/docs/quick-pane-v1-test-plan.md @@ -26,7 +26,7 @@ - `Esc` and **Close** hide without cleanup. Reopening an idle chat at or under three minutes preserves runtime, draft/catalog, and transcript; reopening after more than three minutes renews it. - `Ctrl+N` prevents browser default, aborts active work when needed, retries queued cleanup, clears draft/transcript/raw-prompt mappings, and prepares a fresh runtime/catalog. Idle reset does not send an abort. - Stale runtimes created by superseded preparation are queued after cleanup failure and retried before later stale-chat renewal. -- Quick Pane opens at 640×240 with a 520×240 native minimum. Content-driven native growth stops at 520px tall; transcript and model results scroll internally after that. +- Quick Pane opens at 640×360 with a 520×360 native minimum. Content-driven native growth stops at 520px tall; transcript and model results scroll internally after that. - Settings-startup and model-catalog errors show **Retry**. Catalog retry cleans up failed runtime before replacement preparation; model selection and Send stay disabled until ready. - **Continue in full session** appears after first completed assistant answer with valid handoff settings, plus preserved request/runtime escalation notices. It never starts automatically. Handoff creates normal session with transcript-derived current-chat context, workspace, and follow-up model. Current answer extraction starts only after exact mapped submitted prompt in snapshot transcript; failed follow-ups with no current assistant message never reuse prior answer as partial findings. @@ -41,7 +41,7 @@ 7. Continue; verify normal session receives handoff and follow-up model. 8. Close Quick Pane and reopen within three minutes; verify idle runtime, draft/catalog, and full transcript remain. Repeat after more than three idle minutes; verify fresh chat/runtime appears. 9. Press `Ctrl+N` while idle and active; verify fresh empty chat in both cases and active work aborts before replacement. -10. Verify pane opens at 640×240, grows only to 520px tall as transcript grows, then keeps transcript/model results scrolling internally. +10. Verify pane opens at 640×360, grows only to 520px tall as transcript grows, then keeps transcript/model results scrolling internally. 11. Induce settings-startup or model-catalog failure; verify visible **Retry**, disabled model/Send controls until recovery, and successful retry. Verify empty catalog stays blocked without unsupported actions. ## Exit criteria diff --git a/test/quick-pane-ui.test.js b/test/quick-pane-ui.test.js index e9e8205..1cb47a3 100644 --- a/test/quick-pane-ui.test.js +++ b/test/quick-pane-ui.test.js @@ -242,9 +242,9 @@ test("Quick Pane uses native dimensions and request-first compact layout", async }, { width: 640, - height: 240, + height: 360, minWidth: 520, - minHeight: 240, + minHeight: 360, transparent: true, }, ); @@ -257,7 +257,7 @@ test("Quick Pane uses native dimensions and request-first compact layout", async ); assert.match(nativeSource, /get_or_create_quick_pane/); assert.match(nativeSource, /fn resize_quick_pane/); - assert.match(nativeSource, /height\.clamp\(240\.0, 520\.0\)/); + assert.match(nativeSource, /height\.clamp\(360\.0, 520\.0\)/); assert.match(source, /new ResizeObserver\(resizeToContent\)/); assert.match(source, /invoke\("resize_quick_pane", \{ height \}\)/); assert.doesNotMatch(source, /onCloseRequested/); @@ -267,7 +267,10 @@ test("Quick Pane uses native dimensions and request-first compact layout", async assert.match(source, /className=\{`quick-pane-exchange/); assert.match(source, /onOpen=\{handleModelMenuOpen\}/); assert.match(source, /onClose=\{handleModelMenuClose\}/); - assert.match(source, /requestPaneResize\(520\)/); + assert.match(source, /requestPaneResize\(QUICK_PANE_MAX_HEIGHT\)/); + assert.match(source, /const QUICK_PANE_MIN_HEIGHT = 360/); + assert.match(source, /const QUICK_PANE_MAX_HEIGHT = 520/); + assert.match(source, /height = clampQuickPaneHeight\(height\)/); assert.match(source, /if \(modelMenuOpenRef\.current\) return;/); assert.match(source, /submittedPromptTurnsRef/); assert.match(source, /function elapsedLabel/); @@ -324,6 +327,17 @@ test("Quick Pane uses native dimensions and request-first compact layout", async stylesheet, /\.quick-pane-header-actions\s*\{[^}]*overflow:\s*(?:hidden|clip)/, ); + assert.match( + stylesheet, + /\.quick-pane-toolbar-control\s*\{[\s\S]*?height:\s*27px[\s\S]*?padding:\s*0 8px[\s\S]*?border:[\s\S]*?border-radius:[\s\S]*?background:[\s\S]*?font:/, + ); + assert.match(stylesheet, /\.quick-pane-toolbar-control:hover:not\(:disabled\)/); + assert.match(stylesheet, /\.quick-pane-toolbar-control:focus-visible/); + assert.match(stylesheet, /\.quick-pane-toolbar-control:disabled/); + assert.match(source, /triggerClassName="quick-pane-toolbar-control"/); + assert.match(source, /controlClassName="quick-pane-toolbar-control"/); + assert.match(source, /className="quick-pane-toolbar-control quick-pane-action"/); + assert.match(source, /className="quick-pane-toolbar-control quick-pane-close"/); assert.match(stylesheet, /#f0a347/); assert.doesNotMatch(stylesheet, /#8aa9d6|#46494f|#23252a/); diff --git a/ui/src-tauri/src/lib.rs b/ui/src-tauri/src/lib.rs index 654bd93..aa43ab2 100644 --- a/ui/src-tauri/src/lib.rs +++ b/ui/src-tauri/src/lib.rs @@ -334,7 +334,7 @@ fn quick_pane_height(height: f64) -> Result { if !height.is_finite() { return Err("Quick Pane height must be finite".to_owned()); } - Ok(height.clamp(240.0, 520.0)) + Ok(height.clamp(360.0, 520.0)) } #[tauri::command] @@ -891,9 +891,9 @@ mod tests { .unwrap(); assert_eq!(quick_pane.width, 640.0); - assert_eq!(quick_pane.height, 240.0); + assert_eq!(quick_pane.height, 360.0); assert_eq!(quick_pane.min_width, Some(520.0)); - assert_eq!(quick_pane.min_height, Some(240.0)); + assert_eq!(quick_pane.min_height, Some(360.0)); assert_eq!(quick_pane.url.to_string(), "index.html"); assert!(!quick_pane.visible); assert!(quick_pane.transparent); @@ -903,7 +903,7 @@ mod tests { #[test] fn quick_pane_height_stays_within_content_limit() { - assert_eq!(quick_pane_height(200.0), Ok(240.0)); + assert_eq!(quick_pane_height(200.0), Ok(360.0)); assert_eq!(quick_pane_height(420.0), Ok(420.0)); assert_eq!(quick_pane_height(900.0), Ok(520.0)); } diff --git a/ui/src-tauri/tauri.conf.json b/ui/src-tauri/tauri.conf.json index 3e1dd65..2acb63b 100644 --- a/ui/src-tauri/tauri.conf.json +++ b/ui/src-tauri/tauri.conf.json @@ -29,9 +29,9 @@ "label": "quick-pane", "title": "Pi Quick Pane", "width": 640, - "height": 240, + "height": 360, "minWidth": 520, - "minHeight": 240, + "minHeight": 360, "center": true, "visible": false, "decorations": false, diff --git a/ui/src/components/ModelSelector.tsx b/ui/src/components/ModelSelector.tsx index 46113eb..8742133 100644 --- a/ui/src/components/ModelSelector.tsx +++ b/ui/src/components/ModelSelector.tsx @@ -92,6 +92,7 @@ export function ModelSelector({ label = "Model", placement = "up", className = "", + triggerClassName = "", onOpen, onClose, }: { @@ -103,6 +104,7 @@ export function ModelSelector({ label?: string; placement?: "up" | "down"; className?: string; + triggerClassName?: string; onOpen?: () => void; onClose?: () => void; }) { @@ -209,7 +211,7 @@ export function ModelSelector({