feat(status-ui): enhance desktop session controls

Add native directory selection, persisted interface scaling, and navigation back to the latest conversation message.

Restore saved session transcripts when the bridge transcript RPC is unavailable so the desktop client remains useful during bridge compatibility gaps.
This commit is contained in:
2026-07-29 14:18:49 +02:00
parent a792c577ef
commit 12b9b83d51
11 changed files with 646 additions and 112 deletions
+87 -2
View File
@@ -23,6 +23,20 @@ test("renders every Pi RPC extension UI method without replying to fire-and-forg
assert.match(source, /stripAnsi\(extension\.statusText\)/);
assert.match(source, /className="pi-controls"/);
assert.match(source, /aria-label="Model and thinking controls"/);
assert.match(source, /const uiScaleKey/);
assert.match(source, /function clampUiScale/);
assert.match(source, /function updateUiScale/);
assert.match(source, /localStorage\.setItem\(uiScaleKey/);
assert.match(source, /className="scale-settings"/);
assert.match(source, /aria-label="Interface scale"/);
assert.match(source, /aria-label="Decrease interface scale"/);
assert.match(source, /aria-label="Increase interface scale"/);
assert.match(source, /type="range"/);
assert.match(source, /style=\{\{ zoom: uiScale \}\}/);
assert.doesNotMatch(
source,
/className="pi-controls"[\s\S]*aria-label="Interface scale"/,
);
assert.doesNotMatch(source, /<details className="pi-controls">/);
assert.doesNotMatch(
source,
@@ -35,7 +49,7 @@ test("renders every Pi RPC extension UI method without replying to fire-and-forg
);
assert.match(source, /useLayoutEffect/);
assert.match(source, /transcript\.scrollTop = transcript\.scrollHeight/);
assert.match(source, /requestAnimationFrame\(scrollToLatest\)/);
assert.match(source, /requestAnimationFrame\(scrollTranscriptToLatest\)/);
assert.match(source, /const loadGeneration = useRef\(0\)/);
assert.match(
source,
@@ -55,6 +69,12 @@ test("renders every Pi RPC extension UI method without replying to fire-and-forg
source,
/const lastEventSequence = useRef<Record<string, number>>/,
);
assert.match(source, /const eventReloadInFlight = useRef\(false\)/);
assert.match(source, /const queuedEventReload = useRef<string \| undefined>/);
assert.match(source, /function reloadAgentAfterEvent\(agentId: string\)/);
assert.match(source, /void reloadAgentAfterEvent\(selectedId\)/);
assert.match(source, /let disposed = false;/);
assert.match(source, /if \(disposed\) stop\(\);/);
assert.match(source, /generation !== loadGeneration\.current/);
assert.match(source, /current\.phase !== "working"/);
assert.match(source, /bridgeEvent\.seq <= \(lastEventSequence\.current/);
@@ -181,6 +201,19 @@ test("shows bridge-managed directory status and sessions for the opened director
/state: loadedState\.isStreaming \? "streaming" : "idle"/,
);
assert.match(component, /className="directory-add"/);
assert.match(
component,
/import \{ open \} from "@tauri-apps\/plugin-dialog"/,
);
assert.match(component, /function browseForFolder/);
assert.match(component, /directory: true/);
assert.match(component, /multiple: false/);
assert.match(component, /Browse…/);
assert.match(component, /const \[isAddingDirectory, setIsAddingDirectory\]/);
assert.match(component, /aria-busy=\{isAddingDirectory\}/);
assert.match(component, /className="directory-add-status" role="status"/);
assert.match(component, /pendingDirectoryPath/);
assert.match(component, /Added \$\{worktreeLabel\(pendingDirectoryPath\)\}/);
assert.match(component, /Add directory/);
assert.doesNotMatch(component, /className="folder-form"/);
assert.doesNotMatch(component, /Remembered folders/);
@@ -192,6 +225,9 @@ test("shows bridge-managed directory status and sessions for the opened director
assert.match(component, /New session/);
assert.match(component, /state\.isStreaming/);
assert.match(styles, /\.directory-add \{/);
assert.match(styles, /\.directory-add-status \{/);
assert.match(styles, /\.directory-add-spinner \{/);
assert.match(styles, /directory-add-spin/);
assert.match(styles, /\.directory-tab \{/);
assert.match(styles, /\.directory-tab-button \{/);
assert.match(styles, /\.directory-close \{/);
@@ -235,6 +271,28 @@ test("shows an optimistic prompt until the transcript receives it", async () =>
assert.match(styles, /\.pending-message\.sending/);
});
test("offers a lower-center control when the conversation is above its latest message", async () => {
const [component, styles] = await Promise.all([
readFile("ui/src/App.tsx", "utf8"),
readFile("ui/src/App.css", "utf8"),
]);
assert.match(
component,
/const \[isTranscriptAtBottom, setIsTranscriptAtBottom\]/,
);
assert.match(component, /function updateTranscriptScrollPosition/);
assert.match(component, /function scrollTranscriptToLatest/);
assert.match(component, /onScroll=\{updateTranscriptScrollPosition\}/);
assert.match(component, /!isTranscriptAtBottom/);
assert.match(component, /aria-label="Scroll to latest message"/);
assert.match(component, /className="scroll-to-latest"/);
assert.match(component, /scrollTranscriptToLatest\(\);/);
assert.match(styles, /\.transcript-container \{/);
assert.match(styles, /\.scroll-to-latest \{[\s\S]*left: 50%/);
assert.match(styles, /\.scroll-to-latest \{[\s\S]*bottom: 14px/);
assert.match(styles, /\.scroll-to-latest \{[\s\S]*border-radius: 50%/);
});
test("distinguishes user and assistant messages", async () => {
const styles = await readFile("ui/src/App.css", "utf8");
assert.match(styles, /\.message\.user \{/);
@@ -293,9 +351,36 @@ test("uses a compact workflow layout that keeps the composer and transcript stab
assert.match(styles, /conic-gradient\(/);
assert.match(styles, /transcript-border-orbit/);
assert.match(styles, /prefers-reduced-motion: reduce/);
assert.match(styles, /font-size: 18px/);
assert.match(styles, /\.directory-tab-button \{[\s\S]*font-size: 13px/);
assert.match(styles, /\.message pre \{[\s\S]*font-size: 14px/);
assert.match(
styles,
/\.workflow-main \{[\s\S]*grid-template-columns: minmax\(0, 1fr\) 210px/,
/\.workflow-main \{[\s\S]*grid-template-columns: minmax\(0, 1fr\) 250px/,
);
assert.match(
styles,
/\.pi-controls \{[\s\S]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/,
);
assert.match(styles, /\.scale-settings \{/);
assert.match(
styles,
/\.scale-adjustment \{[\s\S]*grid-template-columns: auto minmax\(0, 1fr\) auto/,
);
assert.match(styles, /\.scale-adjustment input\[type="range"\] \{/);
assert.match(styles, /\.pi-controls \{[\s\S]*padding: 4px 6px/);
});
test("configures a larger desktop window and enables the folder dialog", async () => {
const [config, capability, rust] = await Promise.all([
readFile("ui/src-tauri/tauri.conf.json", "utf8"),
readFile("ui/src-tauri/capabilities/default.json", "utf8"),
readFile("ui/src-tauri/src/lib.rs", "utf8"),
]);
assert.match(config, /"width": 1100/);
assert.match(config, /"height": 840/);
assert.match(config, /"minWidth": 640/);
assert.match(config, /"minHeight": 520/);
assert.match(capability, /"dialog:default"/);
assert.match(rust, /\.plugin\(tauri_plugin_dialog::init\(\)\)/);
});