Xeo Forge v1.22.0 — Local-First Agent Workbench
v1.22.0 — One Language
Summary
Two user-reported defects drove this release, and both turned out to be
honesty problems wearing UI clothes.
First: the chat surface could hang forever in "Thinking" with a locked
composer — the user watched a stuck background process that never finished
and never explained itself. Root cause: the client treated the SSE stream
as the only source of status truth. Any missed done event (a dead
EventSource, a provider crash without a terminal event, an Electron reload
orphaning the stream) left the status "running" permanently — and the
cancelled status was not even in the client's terminal check, so the
composer stayed locked after a stop. The DB row knew the run was over; the
UI never asked.
Second: the app read as two different products. The workspace and settings
pages spoke the black minimal design language, while leftover unicode
glyphs — ← → ⌘K ⚙ ◇ ◈ ◎ ⊕ ◌ ⌕ ‹ › × ▶ ✓ ✕
◆ ▲ ▼ ☀ ☾ ⌗ ••• — read as placeholders from another era, and
the live work session still carried a handful of them. Every glyph is now a
stroke icon from one inline library, sized and colored by the same tokens.
The icon system
components/icons.tsx grew from 7 to 29 icons (Lucide geometry, MIT, zero
dependencies — inline SVG with currentColor strokes so themes stay
correct for free). The set now covers direction (arrows, chevrons, panel
collapse/expand), objects (search, message, folder, settings gear, user,
plug, command), actions (play, plus, stop, x, check), states (circle,
help-circle), and appearance (sun, moon, monitor). Every consumer was
rewired: the workspace sidebar and topbar, session rows, capabilities nav,
settings back-link and section arrows, MCP server rows and approval mark,
provider rows and form dismiss buttons, theme segmented control, dialog
close, command palette, app shell, diff rename arrows, thinking block,
tool ledger disclosure, run-failure mark, login brand marks, and the admin
back link. The new glyph-eradication smoke scan proves the vocabulary is
gone from every .tsx — with two documented exemptions: the icon library's
own doc comments, and setMod('⌘'), which is a keyboard key label
(rendered as "Ctrl" on Windows), not an icon.
The run that never lies stuck
The fix is a contract change: the stream is an input; the task row is
the truth.
isTerminalTaskStatus(inruntime-state.ts) now encodes the server's
full terminal vocabulary —completed/failed/cancelled/
planned— so a stop or a promote can no longer strand the UI in a live
state.- While a turn is live, both the chat and work surfaces re-read the task
row every 4 seconds and adopt the server's terminal status. The missed-
donefamily of hangs self-heals within one poll, regardless of cause. - If the server finished but nothing streamed to this client, the persisted
answer is already in the database — the client refreshes and renders it
verbatim rather than showing an empty completion. - A Stop control is rendered for the whole live turn (not only before
the first token), wired to the real cancel route; the reconciliation poll
adopts the resultingcancelledstatus even if the stream never
delivers it. - If the live connection drops, the surface says so honestly ("Live
connection interrupted — still reconciling with the saved task state")
instead of silently pretending to think. - The chat done-handler now reads the streamed text through a ref mirror,
fixing a stale-closure dedupe that could double-append a summary.
Verification
Local environment cannot install node_modules (better-sqlite3 native
build — same limitation as v1.21), so verification is the same honest
three-part shape: TSX syntax validated via esbuild across all 22 touched
files; a pure-module smoke (scripts/smoke_ui_unify.mjs) passing 12/12
under Node 24 type-stripping — terminal-vocabulary behavior, both
reconciliation contracts, glyph eradication, icon-library integrity; and
test/chat-hang-reconciliation.test.ts (+13 assertions in CI) pinning the
regression permanently: the terminal vocabulary, both surfaces'
reconciliation behavior, the Stop hatch, and the honest stream-loss state.