diff --git a/ui-tui-opentui-v2/src/boundary/gateway/client.ts b/ui-tui-opentui-v2/src/boundary/gateway/client.ts
index b6831ce2376..ac358852bac 100644
--- a/ui-tui-opentui-v2/src/boundary/gateway/client.ts
+++ b/ui-tui-opentui-v2/src/boundary/gateway/client.ts
@@ -104,7 +104,10 @@ export class RawGatewayClient {
// A recovery-respawn re-enters start(), so this re-arms per respawn — desired.
this.startupTimer = setTimeout(() => {
this.startupTimer = undefined
- this.onEvent({ type: 'gateway.start_timeout', payload: { message: `no gateway.ready within ${STARTUP_TIMEOUT_MS}ms` } })
+ this.onEvent({
+ type: 'gateway.start_timeout',
+ payload: { message: `no gateway.ready within ${STARTUP_TIMEOUT_MS}ms` }
+ })
}, STARTUP_TIMEOUT_MS)
}
diff --git a/ui-tui-opentui-v2/src/entry/main.tsx b/ui-tui-opentui-v2/src/entry/main.tsx
index be1dfb9b275..c1a30d53d7c 100644
--- a/ui-tui-opentui-v2/src/entry/main.tsx
+++ b/ui-tui-opentui-v2/src/entry/main.tsx
@@ -244,7 +244,9 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) {
gateway
.request('session.interrupt', { session_id: sid })
.pipe(
- Effect.catchCause(cause => Effect.sync(() => getLog().warn('interrupt', 'failed', { cause: String(cause) })))
+ Effect.catchCause(cause =>
+ Effect.sync(() => getLog().warn('interrupt', 'failed', { cause: String(cause) }))
+ )
)
)
}
@@ -292,7 +294,11 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) {
}
try {
await Effect.runPromise(
- gateway.request('image.attach_bytes', { content_base64: img.data, filename: 'pasted.png', session_id: sid })
+ gateway.request('image.attach_bytes', {
+ content_base64: img.data,
+ filename: 'pasted.png',
+ session_id: sid
+ })
)
flashHint('🖼 image attached — type a message and send', 3000)
} catch {
diff --git a/ui-tui-opentui-v2/src/logic/store.ts b/ui-tui-opentui-v2/src/logic/store.ts
index cb928ba3c0b..66db44b3192 100644
--- a/ui-tui-opentui-v2/src/logic/store.ts
+++ b/ui-tui-opentui-v2/src/logic/store.ts
@@ -782,8 +782,18 @@ export function createSessionStore() {
const mcp = obj(root.mcp)
setState('catalog', {
mcp: { servers: strs(mcp.servers) },
- skills: { categories: list(skills.categories).map(pair).filter(p => p.name), total: num(skills.total) },
- tools: { toolsets: list(tools.toolsets).map(toolset).filter(p => p.name), total: num(tools.total) }
+ skills: {
+ categories: list(skills.categories)
+ .map(pair)
+ .filter(p => p.name),
+ total: num(skills.total)
+ },
+ tools: {
+ toolsets: list(tools.toolsets)
+ .map(toolset)
+ .filter(p => p.name),
+ total: num(tools.total)
+ }
})
}
diff --git a/ui-tui-opentui-v2/src/logic/toolOutput.ts b/ui-tui-opentui-v2/src/logic/toolOutput.ts
index 937ad1e8fc5..94e97871e73 100644
--- a/ui-tui-opentui-v2/src/logic/toolOutput.ts
+++ b/ui-tui-opentui-v2/src/logic/toolOutput.ts
@@ -40,7 +40,11 @@ export function truncate(s: string, width: number): string {
export function normalizeOutput(text: string): string {
const real = (text.match(/\n/g) ?? []).length
const literal = (text.match(/\\n/g) ?? []).length
- if (literal > real) return text.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\\t/g, ' ')
+ if (literal > real)
+ return text
+ .replace(/\\r\\n/g, '\n')
+ .replace(/\\n/g, '\n')
+ .replace(/\\t/g, ' ')
return text
}
diff --git a/ui-tui-opentui-v2/src/test/lib/render.ts b/ui-tui-opentui-v2/src/test/lib/render.ts
index 6231625dec5..26e960803f3 100644
--- a/ui-tui-opentui-v2/src/test/lib/render.ts
+++ b/ui-tui-opentui-v2/src/test/lib/render.ts
@@ -28,7 +28,12 @@ function withKeymap(node: () => JSX.Element): () => JSX.Element {
return () => {
const renderer = useRenderer()
const keymap = createMemo(() => createDefaultOpenTuiKeymap(renderer))
- return KeymapProvider({ keymap: keymap(), get children() { return node() } })
+ return KeymapProvider({
+ keymap: keymap(),
+ get children() {
+ return node()
+ }
+ })
}
}
diff --git a/ui-tui-opentui-v2/src/test/slash.test.ts b/ui-tui-opentui-v2/src/test/slash.test.ts
index 85526d5d435..fea857ad289 100644
--- a/ui-tui-opentui-v2/src/test/slash.test.ts
+++ b/ui-tui-opentui-v2/src/test/slash.test.ts
@@ -46,7 +46,11 @@ describe('planCompletion (items 5 + 13)', () => {
method: 'complete.path',
params: { word: '@src/fo' }
})
- expect(planCompletion('cat ./rea')).toEqual({ from: 'cat '.length, method: 'complete.path', params: { word: './rea' } })
+ expect(planCompletion('cat ./rea')).toEqual({
+ from: 'cat '.length,
+ method: 'complete.path',
+ params: { word: './rea' }
+ })
expect(planCompletion('open ~/proj')).toEqual({
from: 'open '.length,
method: 'complete.path',
diff --git a/ui-tui-opentui-v2/src/test/toolOutput.test.ts b/ui-tui-opentui-v2/src/test/toolOutput.test.ts
index 3ec5ef8df2b..3e88147c193 100644
--- a/ui-tui-opentui-v2/src/test/toolOutput.test.ts
+++ b/ui-tui-opentui-v2/src/test/toolOutput.test.ts
@@ -9,7 +9,9 @@ import { collapseToolOutput, stripAnsi, stripOmittedNote, stripToolEnvelope, tru
describe('stripAnsi (item 8 - gateway slash/notice text is ANSI-colored for Ink)', () => {
const ESC = String.fromCharCode(27)
test('removes SGR color codes, keeps the text', () => {
- expect(stripAnsi(`${ESC}[1;38;2;255;215;0m\u2713 Reasoning display: ON${ESC}[0m`)).toBe('\u2713 Reasoning display: ON')
+ expect(stripAnsi(`${ESC}[1;38;2;255;215;0m\u2713 Reasoning display: ON${ESC}[0m`)).toBe(
+ '\u2713 Reasoning display: ON'
+ )
})
test('removes italic + mouse sequences', () => {
expect(stripAnsi(`${ESC}[2;3m Model thinking shown.${ESC}[0m`)).toBe(' Model thinking shown.')
@@ -22,7 +24,9 @@ describe('stripAnsi (item 8 - gateway slash/notice text is ANSI-colored for Ink)
describe('stripOmittedNote (item 2 — peel the gateway verbose-tail label)', () => {
test('extracts the lines/chars note and returns the clean body', () => {
- const { body, omittedNote } = stripOmittedNote('[showing verbose tail; omitted 5 lines / 234 chars]\nline one\nline two')
+ const { body, omittedNote } = stripOmittedNote(
+ '[showing verbose tail; omitted 5 lines / 234 chars]\nline one\nline two'
+ )
expect(omittedNote).toBe('5 lines / 234 chars')
expect(body).toBe('line one\nline two')
})
diff --git a/ui-tui-opentui-v2/src/view/App.tsx b/ui-tui-opentui-v2/src/view/App.tsx
index 3238b9c2269..dbd6bf112e8 100644
--- a/ui-tui-opentui-v2/src/view/App.tsx
+++ b/ui-tui-opentui-v2/src/view/App.tsx
@@ -68,75 +68,75 @@ export function App(props: AppProps) {
return (
-
- {/* a bottom rule under the header bookends the transcript with the status
+
+ {/* a bottom rule under the header bookends the transcript with the status
bar's top rule — frames the chrome as intentional (item 8). */}
-
-
-
- {/* content zone: a full-screen overlay (pager / agents dashboard) OR the transcript + input zone */}
-
-
- {/* transient busy face floats at the bottom of the transcript area */}
-
- {/* input region — a top-edge rule separates the status bar + textbox from the
+
+
+
+ {/* content zone: a full-screen overlay (pager / agents dashboard) OR the transcript + input zone */}
+
+
+ {/* transient busy face floats at the bottom of the transcript area */}
+
+ {/* input region — a top-edge rule separates the status bar + textbox from the
transcript above; the status bar sits directly ABOVE the composer (item 14). */}
-
-
- props.store.state.completions ?? []}
- completionFrom={() => props.store.state.completionFrom}
- onDismiss={() => props.store.clearCompletions()}
- history={props.history}
- onImagePaste={props.onImagePaste}
- pasteStore={props.pasteStore}
- />
- }
+
-
-
-
-
- {sessions => }
-
-
- {p => (
- {
- p().onPick(value)
- closePicker()
- }}
- onClose={closePicker}
+
+ props.store.state.completions ?? []}
+ completionFrom={() => props.store.state.completionFrom}
+ onDismiss={() => props.store.clearCompletions()}
+ history={props.history}
+ onImagePaste={props.onImagePaste}
+ pasteStore={props.pasteStore}
/>
- )}
-
-
-
- >
- }
- >
- {p => }
-
-
-
-
-
+ }
+ >
+
+
+
+
+ {sessions => }
+
+
+ {p => (
+ {
+ p().onPick(value)
+ closePicker()
+ }}
+ onClose={closePicker}
+ />
+ )}
+
+
+
+ >
+ }
+ >
+ {p => }
+
+
+
+
+
)
}
diff --git a/ui-tui-opentui-v2/src/view/homeHint.tsx b/ui-tui-opentui-v2/src/view/homeHint.tsx
index 76fdfae9582..cfc95da5d03 100644
--- a/ui-tui-opentui-v2/src/view/homeHint.tsx
+++ b/ui-tui-opentui-v2/src/view/homeHint.tsx
@@ -98,81 +98,96 @@ export function HomeHint(props: { store: SessionStore }) {
border
borderColor={theme().color.border}
>
- {/* session info block: model · Nous Research / dir / Session id */}
-
-
- {model => (
+ {/* session info block: model · Nous Research / dir / Session id */}
+
+
+ {model => (
+
+ {shortModel(model())}
+ · Nous Research
+
+ )}
+
+
+ {cwd => (
+
+ {shortCwd(cwd())}
+
+ {` (${info().branch})`}
+
+
+ )}
+
+
- {shortModel(model())}
- · Nous Research
+ Session:
+ {props.store.state.sessionId}
- )}
-
-
- {cwd => (
-
- {shortCwd(cwd())}
-
- {` (${info().branch})`}
-
-
- )}
-
-
-
- Session:
- {props.store.state.sessionId}
-
-
-
+
+
- {/* SEPARATE collapsible sections (Ink parity) + summary */}
-
- {c => (
-
-
-
- {ts => (
+ {/* SEPARATE collapsible sections (Ink parity) + summary */}
+
+ {c => (
+
+
+
+ {ts => (
+
+ {`${ts.name}: `}
+
+ {truncate(
+ ts.tools.join(', ') || `${ts.count} tools`,
+ Math.max(20, dims().width - ts.name.length - 8)
+ )}
+
+
+ )}
+
+ TOOLSETS_MAX}>
- {`${ts.name}: `}
-
- {truncate(ts.tools.join(', ') || `${ts.count} tools`, Math.max(20, dims().width - ts.name.length - 8))}
-
+ {`(and ${enabledToolsets().length - TOOLSETS_MAX} more toolsets…)`}
- )}
-
- TOOLSETS_MAX}>
+
+
+
+
- {`(and ${enabledToolsets().length - TOOLSETS_MAX} more toolsets…)`}
+
+ {c()
+ .skills.categories.map(s => `${s.name} (${s.count})`)
+ .join(' ')}
+
-
-
+
-
-
-
- {c().skills.categories.map(s => `${s.name} (${s.count})`).join(' ')}
-
-
-
+
+
+ {c().mcp.servers.join(' ') || 'none configured'}
+
+
-
-
- {c().mcp.servers.join(' ') || 'none configured'}
-
-
-
-
-
- {`${c().tools.total} tools`}
- {` · ${c().skills.total} skills · ${c().mcp.servers.length} MCP · `}
- /help
- for commands
-
+
+
+ {`${c().tools.total} tools`}
+ {` · ${c().skills.total} skills · ${c().mcp.servers.length} MCP · `}
+ /help
+ for commands
+
+
-
- )}
-
+ )}
+
{/* end framed session panel */}
diff --git a/ui-tui-opentui-v2/src/view/keymap.tsx b/ui-tui-opentui-v2/src/view/keymap.tsx
index bcd71c1c90b..a1ebef2030d 100644
--- a/ui-tui-opentui-v2/src/view/keymap.tsx
+++ b/ui-tui-opentui-v2/src/view/keymap.tsx
@@ -18,7 +18,14 @@ import { useBindings } from '@opentui/keymap/solid'
export function useCloseLayer(target: () => BoxRenderable | undefined, onClose: () => void): void {
useBindings(() => ({
target,
- commands: [{ name: 'close', run() { onClose() } }],
+ commands: [
+ {
+ name: 'close',
+ run() {
+ onClose()
+ }
+ }
+ ],
bindings: [
{ key: 'escape', cmd: 'close' },
{ key: { name: 'c', ctrl: true }, cmd: 'close' }
diff --git a/ui-tui-opentui-v2/src/view/overlays/agentsDashboard.tsx b/ui-tui-opentui-v2/src/view/overlays/agentsDashboard.tsx
index d72ae293833..36aed97ecb4 100644
--- a/ui-tui-opentui-v2/src/view/overlays/agentsDashboard.tsx
+++ b/ui-tui-opentui-v2/src/view/overlays/agentsDashboard.tsx
@@ -39,7 +39,10 @@ export function AgentsDashboard(props: { subagents: SubagentInfo[]; onClose: ()
// Close (Esc/Ctrl+C) is the native keymap; select + scroll stay in the raw global
// handler below. Focus the root box on mount so the focus-within close layer is active.
onMount(() => rootRef?.focus())
- useCloseLayer(() => rootRef, () => props.onClose())
+ useCloseLayer(
+ () => rootRef,
+ () => props.onClose()
+ )
useKeyboard(key => {
// `q` closes (footer advertises "Esc/q close"); Esc/Ctrl+C close via the keymap.
@@ -107,7 +110,12 @@ export function AgentsDashboard(props: { subagents: SubagentInfo[]; onClose: ()
- (traceBox = el)} style={{ flexGrow: 1, minHeight: 0 }} stickyScroll stickyStart="bottom">
+ (traceBox = el)}
+ style={{ flexGrow: 1, minHeight: 0 }}
+ stickyScroll
+ stickyStart="bottom"
+ >
0}
fallback={(no activity yet)}
diff --git a/ui-tui-opentui-v2/src/view/overlays/pager.tsx b/ui-tui-opentui-v2/src/view/overlays/pager.tsx
index efb2491d0e4..8969aa691b5 100644
--- a/ui-tui-opentui-v2/src/view/overlays/pager.tsx
+++ b/ui-tui-opentui-v2/src/view/overlays/pager.tsx
@@ -27,7 +27,10 @@ export function Pager(props: { title: string; text: string; onClose: () => void
// handler below. Focus the root box on mount so the focus-within close layer is
// active (the scrollbox isn't focused — scroll is global, not focus-gated).
onMount(() => rootRef?.focus())
- useCloseLayer(() => rootRef, () => props.onClose())
+ useCloseLayer(
+ () => rootRef,
+ () => props.onClose()
+ )
useKeyboard(key => {
// `q` closes (the footer advertises "Esc/q close"); Esc/Ctrl+C close via the
diff --git a/ui-tui-opentui-v2/src/view/overlays/picker.tsx b/ui-tui-opentui-v2/src/view/overlays/picker.tsx
index 9abd5f7d3b6..eb3fa150ff8 100644
--- a/ui-tui-opentui-v2/src/view/overlays/picker.tsx
+++ b/ui-tui-opentui-v2/src/view/overlays/picker.tsx
@@ -20,7 +20,10 @@ export function Picker(props: {
const theme = useTheme()
let rootRef: BoxRenderable | undefined
// Native select handles ↑↓/j/k/Enter; the keymap owns Esc/Ctrl+C close.
- useCloseLayer(() => rootRef, () => props.onClose())
+ useCloseLayer(
+ () => rootRef,
+ () => props.onClose()
+ )
const options = createMemo(() =>
props.items.map(it => ({ description: it.description ?? '', name: it.label, value: it.value }))
diff --git a/ui-tui-opentui-v2/src/view/overlays/sessionSwitcher.tsx b/ui-tui-opentui-v2/src/view/overlays/sessionSwitcher.tsx
index 6d4162ed0ae..1315209dc1f 100644
--- a/ui-tui-opentui-v2/src/view/overlays/sessionSwitcher.tsx
+++ b/ui-tui-opentui-v2/src/view/overlays/sessionSwitcher.tsx
@@ -19,7 +19,10 @@ export function SessionSwitcher(props: {
const theme = useTheme()
let rootRef: BoxRenderable | undefined
// Native select handles ↑↓/Enter; the keymap owns Esc/Ctrl+C close.
- useCloseLayer(() => rootRef, () => props.onClose())
+ useCloseLayer(
+ () => rootRef,
+ () => props.onClose()
+ )
const options = createMemo(() =>
props.sessions.map(s => ({
diff --git a/ui-tui-opentui-v2/src/view/prompts/approvalPrompt.tsx b/ui-tui-opentui-v2/src/view/prompts/approvalPrompt.tsx
index eca9fb58287..37f58210743 100644
--- a/ui-tui-opentui-v2/src/view/prompts/approvalPrompt.tsx
+++ b/ui-tui-opentui-v2/src/view/prompts/approvalPrompt.tsx
@@ -26,7 +26,10 @@ export function ApprovalPrompt(props: {
let rootRef: BoxRenderable | undefined
// Native select handles ↑↓/j/k/Enter over the options; the keymap owns the
// Esc/Ctrl+C → deny cancel path the select doesn't cover.
- useCloseLayer(() => rootRef, () => props.onCancel())
+ useCloseLayer(
+ () => rootRef,
+ () => props.onCancel()
+ )
return (
void; onNo:
useBindings(() => ({
target: () => rootRef,
commands: [
- { name: 'confirm', run() { props.onYes() } },
- { name: 'cancel', run() { props.onNo() } }
+ {
+ name: 'confirm',
+ run() {
+ props.onYes()
+ }
+ },
+ {
+ name: 'cancel',
+ run() {
+ props.onNo()
+ }
+ }
],
bindings: [
{ key: 'y', cmd: 'confirm' },
diff --git a/ui-tui-opentui-v2/src/view/reasoningPart.tsx b/ui-tui-opentui-v2/src/view/reasoningPart.tsx
index 72f72f0e662..22b6bfa8d4b 100644
--- a/ui-tui-opentui-v2/src/view/reasoningPart.tsx
+++ b/ui-tui-opentui-v2/src/view/reasoningPart.tsx
@@ -41,10 +41,7 @@ export function ReasoningPart(props: { text: string; streaming?: boolean }) {
return (
-
+
{expanded() ? '▼' : '▶'}
diff --git a/ui-tui-opentui-v2/src/view/scrollAnchor.tsx b/ui-tui-opentui-v2/src/view/scrollAnchor.tsx
index c98f3bad548..bd3e31d853c 100644
--- a/ui-tui-opentui-v2/src/view/scrollAnchor.tsx
+++ b/ui-tui-opentui-v2/src/view/scrollAnchor.tsx
@@ -20,7 +20,10 @@ type AnchorFn = (toggle: () => void) => void
const Ctx = createContext()
-export function ScrollAnchorProvider(props: { scroll: Accessor; children: JSX.Element }) {
+export function ScrollAnchorProvider(props: {
+ scroll: Accessor
+ children: JSX.Element
+}) {
const around: AnchorFn = toggle => {
const sb = props.scroll()
if (!sb) {
diff --git a/ui-tui-opentui-v2/src/view/toolPart.tsx b/ui-tui-opentui-v2/src/view/toolPart.tsx
index bbdac8949a5..b5b79505ac8 100644
--- a/ui-tui-opentui-v2/src/view/toolPart.tsx
+++ b/ui-tui-opentui-v2/src/view/toolPart.tsx
@@ -81,9 +81,7 @@ export function ToolPart(props: { part: ToolPartState }) {
const collapsible = () => !running() && (lines().length > 1 || showArgs())
// Header subtitle: the primary-arg preview (item 2), else explicit summary, else first line.
const subtitle = () =>
- props.part.error
- ? `✗ ${props.part.error}`
- : props.part.argsPreview || props.part.summary || lines()[0] || ''
+ props.part.error ? `✗ ${props.part.error}` : props.part.argsPreview || props.part.summary || lines()[0] || ''
const body = createMemo(() => collapseToolOutput(result(), EXPANDED_MAX, bodyWidth() - 2))
const headGlyph = () => (collapsible() ? (expanded() ? '▼' : '▶') : '⚡')