Item 11 — "stopping the agent doesn't work". Ctrl+C used to immediately destroy
the renderer. Now a turn-aware state machine (opencode's double-press model, the
user's preferred behaviour):
- While a turn runs (store.info.running): first Ctrl+C → session.interrupt
{session_id} (STOP the agent), and arms a 3s quit window with a warn hint
"⏹ stopped — Ctrl+C again to quit".
- Idle: first Ctrl+C arms the window ("Ctrl+C again to quit"); a stray single
press never nukes the session.
- A second Ctrl+C within the window KILLS the TUI (renderer.destroy → clean
scope teardown → gateway child EOF).
- A blocking prompt still owns Ctrl+C (deny/cancel) — unchanged.
Wiring: renderer.ts gains an `onCtrlC` hook (owns Ctrl+C when not blocked);
entry builds the machine (gateway yielded before the renderer so it can read
`running` + send interrupt). store gains a transient `hint` slice; StatusLine
shows hint (warn, priority) or the busy face (dim).
Live-smoked: long turn → Ctrl+C shows "stopped" + idle dot; second press exits
cleanly with no orphaned gateway child (the user's installed-venv sessions
untouched). 60 pass.
32 lines
1.1 KiB
TypeScript
32 lines
1.1 KiB
TypeScript
/**
|
|
* StatusLine — the transient line just below the transcript (spec §3 chrome).
|
|
* Shows EITHER:
|
|
* - a `hint` (e.g. "Ctrl+C again to quit" — item 11), in the warn colour and
|
|
* taking priority; or
|
|
* - the kaomoji busy face/verb from `thinking.delta`/`status.update` WHILE a
|
|
* turn runs (Ink's FaceTicker), dim, cleared on `message.complete`.
|
|
* This keeps those transient indicators OUT of the transcript. Renders nothing
|
|
* when both are idle.
|
|
*/
|
|
import { Show } from 'solid-js'
|
|
|
|
import type { SessionStore } from '../logic/store.ts'
|
|
import { useTheme } from './theme.tsx'
|
|
|
|
export function StatusLine(props: { store: SessionStore }) {
|
|
const theme = useTheme()
|
|
const line = () => props.store.state.hint ?? props.store.state.status
|
|
const isHint = () => props.store.state.hint !== undefined
|
|
return (
|
|
<Show when={line()}>
|
|
{text => (
|
|
<box style={{ flexShrink: 0 }}>
|
|
<text>
|
|
<span style={{ fg: isHint() ? theme().color.warn : theme().color.muted }}>{text()}</span>
|
|
</text>
|
|
</box>
|
|
)}
|
|
</Show>
|
|
)
|
|
}
|