feat(quick-pane): add follow-up chat renewal

This commit is contained in:
2026-08-27 16:26:17 +02:00
parent 05874a0be1
commit 60fb91f375
14 changed files with 1147 additions and 549 deletions
+74 -12
View File
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
test("Quick Pane polls snapshots, renders answers, and gates handoff", async () => {
test("Quick Pane polls snapshots, renders answers, and gates manual transcript handoff", async () => {
const source = await readFile(
new URL("../ui/src/quick-pane/QuickPane.tsx", import.meta.url),
"utf8",
@@ -11,10 +11,10 @@ test("Quick Pane polls snapshots, renders answers, and gates handoff", async ()
assert.match(source, /setInterval\(\(\) => void refresh\(\), 800\)/);
assert.match(source, /<ReactMarkdown remarkPlugins=\{\[remarkGfm\]\}>/);
assert.match(source, /className="quick-pane-notice escalation"/);
assert.match(
source,
/<button type="button" onClick=\{continueFull\}>\s*Continue\s*<\/button>/,
);
assert.match(source, /className="quick-pane-notice continue"/);
assert.match(source, /Continue in full session/);
assert.match(source, /Quick Pane transcript:/);
assert.doesNotMatch(source, /maximumSeconds|Quick time budget/);
});
test("main Settings uses shared catalog selectors with provider derived from models", async () => {
@@ -46,19 +46,46 @@ test("main Settings uses shared catalog selectors with provider derived from mod
assert.match(pane, /preparedRuntimeRef/);
assert.doesNotMatch(pane, /get_model_catalog/);
assert.match(pane, /set_thinking_level/);
assert.match(pane, /Loading models…/);
assert.match(pane, /catalogStatus !== "ready"/);
assert.doesNotMatch(pane, /showSettings/);
assert.doesNotMatch(pane, /Save Quick Pane settings/);
assert.match(pane, /isStreaming === false/);
assert.match(pane, /!snapshotIsActive\(snapshot\)/);
assert.match(pane, /current === submitted\.request \? "" : current/);
assert.match(pane, /readOnly=\{runActive\}/);
assert.match(pane, /window\.requestAnimationFrame\(focusInput\)/);
assert.match(pane, /\.slice\(0, 5\)/);
assert.match(pane, /aria-label="Answer history"/);
assert.doesNotMatch(pane, /\.slice\(0, 5\)/);
assert.doesNotMatch(pane, /aria-label="Answer history"/);
assert.match(
pane,
/transcriptMessages\(snapshot, submittedPromptRequestsRef\.current\)/,
);
assert.match(pane, /const QUICK_PANE_ADVISORY_PREFIX =/);
assert.match(pane, /submittedPromptRequests\.get\(text\)/);
assert.match(
pane,
/text\.startsWith\(QUICK_PANE_ADVISORY_PREFIX\) \? "" : text/,
);
assert.match(
pane,
/submittedPromptRequestsRef\.current\.set\(guardrails, submittedRequest\)/,
);
assert.match(pane, /submittedPromptRequestsRef\.current\.clear\(\)/);
assert.doesNotMatch(
pane,
/delimiterIndex|text\.indexOf\(USER_REQUEST_DELIMITER\)/,
);
assert.match(
pane,
/message\.role !== "user" && message\.role !== "assistant"/,
);
assert.match(pane, /lastUserQuestionAtRef\.current = submittedAt/);
assert.match(pane, /lastCompletedAnswerAtRef\.current = Date\.now\(\)/);
assert.doesNotMatch(pane, /localStorage|sessionStorage|save_ui_state/);
assert.match(persistence, /quickPane: current\.quickPane/);
});
test("Quick Pane keeps slash-containing model IDs atomic and reloads catalog on focus", async () => {
test("Quick Pane keeps slash-containing model IDs atomic and renews catalog on focus", async () => {
const [pane, catalog] = await Promise.all([
readFile(
new URL("../ui/src/quick-pane/QuickPane.tsx", import.meta.url),
@@ -70,7 +97,16 @@ test("Quick Pane keeps slash-containing model IDs atomic and reloads catalog on
assert.match(pane, /provider: submittedModel\.provider/);
assert.match(pane, /modelId: submittedModel\.modelId/);
assert.doesNotMatch(pane, /selectedModel\.split/);
assert.match(pane, /onFocusChanged[\s\S]*?void prepareRuntime\(\)/);
assert.match(pane, /onFocusChanged[\s\S]*?void renewChatOnFocus\(\)/);
assert.match(pane, /if \(!event\.payload\) return;/);
assert.match(pane, /const CHAT_RENEWAL_MS = 180_000/);
assert.match(pane, /Date\.now\(\) - lastActivity > CHAT_RENEWAL_MS/);
assert.match(pane, /event\.ctrlKey && event\.key\.toLowerCase\(\) === "n"/);
assert.match(pane, /event\.preventDefault\(\)/);
assert.doesNotMatch(
pane,
/setInterval[^\n]*CHAT_RENEWAL_MS|setTimeout[^\n]*CHAT_RENEWAL_MS/,
);
assert.match(
catalog,
/JSON\.stringify\(\[identity\.provider, identity\.modelId\]\)/,
@@ -88,6 +124,11 @@ test("Quick Pane dismiss and mutually exclusive window visibility stay native-se
]);
assert.match(pane, /invoke\("dismiss_quick_pane"\)/);
assert.doesNotMatch(pane, /getCurrentWindow\(\)\.hide\(\)/);
const dismissPane = pane.slice(
pane.indexOf("const dismissPane"),
pane.indexOf("const clearChatState"),
);
assert.doesNotMatch(dismissPane, /closeRuntime|clearRuntime|prepareRuntime/);
assert.match(nativeSource, /fn dismiss_quick_pane\(/);
assert.match(
nativeSource,
@@ -165,9 +206,17 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
{
width: quickPane.width,
height: quickPane.height,
minWidth: quickPane.minWidth,
minHeight: quickPane.minHeight,
transparent: quickPane.transparent,
},
{ width: 640, height: 440, transparent: true },
{
width: 640,
height: 360,
minWidth: 520,
minHeight: 360,
transparent: true,
},
);
assert.ok(capability.windows.includes("quick-pane"));
assert.match(nativeSource, /WindowEvent::CloseRequested/);
@@ -177,6 +226,10 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
/WebviewWindowBuilder::from_config\(app, config\)/,
);
assert.match(nativeSource, /get_or_create_quick_pane/);
assert.match(nativeSource, /fn resize_quick_pane/);
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/);
assert.match(source, /className="quick-pane-request"/);
assert.match(source, /className="quick-pane-exchange"/);
@@ -200,7 +253,8 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
);
assert.match(stylesheet, /\.quick-pane-send[\s\S]*?position: absolute/);
assert.match(stylesheet, /@media \(max-width: 400px\)/);
assert.match(stylesheet, /\.quick-pane\s*\{[\s\S]*?overflow: visible/);
assert.match(stylesheet, /html,[\s\S]*?#root\s*\{[\s\S]*?overflow: hidden/);
assert.match(stylesheet, /\.quick-pane\s*\{[\s\S]*?overflow: hidden/);
assert.doesNotMatch(
stylesheet,
/\.quick-pane-primary-controls\s*\{[^}]*overflow:\s*(?:hidden|clip)/,
@@ -227,4 +281,12 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
selectorStylesheet,
/\.model-trigger\s*>\s*span\s*\{[\s\S]*?overflow:\s*hidden;[\s\S]*?text-overflow:\s*ellipsis/,
);
assert.match(
selectorStylesheet,
/\.model-menu\s*\{[\s\S]*?grid-template-rows:\s*auto auto minmax\(0, 1fr\);[\s\S]*?overflow:\s*hidden/,
);
assert.match(
selectorStylesheet,
/\.model-results\s*\{[\s\S]*?overflow-y:\s*auto/,
);
});