The TUI is fully interactive from the first frame but `session.create`
(agent + tools + MCP) takes ~2s. Plain-text messages typed before the
session is live used to fail with "session not ready yet"; slash and
shell commands worked but agent prompts were dropped.
Now:
- `dispatchSubmission` enqueues plain text when `sid` is null (slash/shell
still short-circuit first)
- `useMainApp` tracks sid transitions and kicks off one `sendQueued()`
when the session first becomes ready; subsequent queued messages drain
on `message.complete` as before
- Fixed pre-existing double-Enter bug that dequeued without sid check
User flow: type `hello` → shows in `queuedDisplay` preview → 2s later
agent wakes → message auto-sends → reply streams. Zero wasted input.
Previously `historyItems` was seeded empty and the intro (with Banner +
SessionPanel) was only pushed after Python's `session.create` returned —
~1.8s of agent + tools + MCP init with nothing on screen. Base CLI feels
instant because it prints the banner as its first action.
Seed `historyItems` with an info-less intro on mount. `appLayout` now
renders the Banner unconditionally for `kind === 'intro'` and gates only
the SessionPanel on `info` being present. Gateway.ready swaps the skin
(~200ms) and session.info fills in the panel when the agent is ready.
Net: first usable frame drops from ~2s to ~300ms (node + module graph +
React mount). No behavior change — intro message is replaced in place
by `introMsg(info)` when `newSession()` / `resumeById()` resolve.
Python's slash worker already prints every echo/panel command through Rich.
TS was reformatting the same data client-side for 23 commands. Delete those
shadows; let the `slash.exec` fallback in `createSlashHandler` route the
worker's text (via `<Ansi>`) and page-wrap long output.
TS registry now contains 23 commands (down from 45) — only those that:
- mutate React-local state (composer, transcript, overlays, uiStore)
- touch the terminal (OSC52 copy, `$EDITOR`, clipboard)
- open pickers (`/model`, `/resume`)
- trigger history surgery (`/undo`, `/retry`, `/compress`, `/personality`)
- need TS-only composition (`/help` merges HOTKEYS + catalog)
Deleted shadows:
session: yolo, skin, verbose, reasoning, provider, stop, reload-mcp,
save, title, insights, debug, fast, platforms, snapshot,
usage, history, profile
ops: plugins, rollback, agents, tasks, cron, config, toolsets,
browser, skills (list/browse only; `/tools configure` kept
for its history-reset side effect)
Side effects:
- Drops `slash/shared.ts` + `SlashShared` + `shared`/`SLASH_OUTPUT_PAGE` —
generic slash.exec fallback handles titled paging via `createSlashHandler`.
- Prunes 17 now-unreferenced `*Response` interfaces from gatewayTypes.ts.
- `createSlashHandler` fallback now pages long output (len>180 || lines>2)
and uses the command name as title.
session.ts: 670 -> 199 (-70%)
ops.ts: 460 -> 52 (-88%)
gatewayTypes.ts: 450 -> 302 (-33%)
Hoist turn state from a 286-line hook into $turnState atom + turnController
singleton. createGatewayEventHandler becomes a typed dispatch over the
controller; its ctx shrinks from 30 fields to 5. Event-handler refs and 16
threaded actions are gone.
Fold three createSlash*Handler factories into a data-driven SlashCommand[]
registry under slash/commands/{core,session,ops}.ts. Aliases are data;
findSlashCommand does name+alias lookup. Shared guarded/guardedErr combinator
in slash/guarded.ts.
Split constants.ts + app/helpers.ts into config/ (timing/limits/env),
content/ (faces/placeholders/hotkeys/verbs/charms/fortunes), domain/ (roles/
details/messages/paths/slash/viewport/usage), protocol/ (interpolation/paste).
Type every RPC response in gatewayTypes.ts (26 new interfaces); drop all
`(r: any)` across slash + main app.
Shrink useMainApp from 1216 -> 646 lines by extracting useSessionLifecycle,
useSubmission, useConfigSync. Add <Fg> themed primitive and strip ~50
`as any` color casts.
Tests: 50 passing. Build + type-check clean.