Summary
The chat assistant currently renders answers as a single block of plain text after up to six tool rounds. This issue covers making it feel like a chat: streaming tokens, rendering tool results as grounded cards, and the drawer affordances a conversation needs.
This is the approved second half of the design spec. The first half — growing the tool layer from 6 to 18 model-facing tools — is PR #378. A full implementation plan already exists at docs/superpowers/plans/2026-07-27-chat-streaming-and-rendering.md (12 tasks, written in full with code).
Depends on #378.
Scope
Streaming. ai.py gains a streaming sibling to chat_completion_tools that yields content deltas and accumulates tool_calls deltas index-by-index into the same assistant-message shape the non-streaming path returns, so the agent loop is unchanged. A new delta SSE event carries tokens.
Result cards. tool SSE events grow a rows payload, and the client renders per-tool card types: spools (colour swatch, remaining weight, link to detail), filaments (low-stock and on-order tags), orders (shop, outstanding count), locations and vendors. Structure comes from the tool's own result payload, not from model-authored prose — a card cannot display a number the tools did not return.
Usage chart. get_usage_stats renders a compact variant of the existing dependency-free usageChart.tsx. No charting library added.
Drawer affordances. Conversation persisted in localStorage (the server loop stays stateless by design), a stop button via AbortController on the SSE reader, and starter prompt chips on the empty state.
Constraints worth knowing up front
- No new client dependencies. Notably no markdown renderer — cards carry the structure instead.
- The drawer stays 420 px. Cards are single-column, capped at five rows with a "+N more" deep-link. Nothing may scroll horizontally.
- The
message event must keep firing at end-of-turn with the complete text. delta is additive; the confirm/resume round-trip must be untouched.
- Streaming needs a per-request fallback. Spoolman points at arbitrary OpenAI-compatible endpoints whose streaming fidelity varies. An endpoint that streams badly must produce a slow chat, never a broken one — and the fallback decision is made once per request, not per token.
- Every new user-visible string needs all 30 locales.
npm run check-i18n gates it. This is a meaningful slice of the work, not a footnote.
- UI review gate: per the repo owner's standing rule, nothing visual commits until before/after screenshots of the running app have been reviewed and approved.
Non-goals
Photo attachment in chat, bulk actions and proactive nudges are the third sub-project and are tracked separately.
Summary
The chat assistant currently renders answers as a single block of plain text after up to six tool rounds. This issue covers making it feel like a chat: streaming tokens, rendering tool results as grounded cards, and the drawer affordances a conversation needs.
This is the approved second half of the design spec. The first half — growing the tool layer from 6 to 18 model-facing tools — is PR #378. A full implementation plan already exists at
docs/superpowers/plans/2026-07-27-chat-streaming-and-rendering.md(12 tasks, written in full with code).Depends on #378.
Scope
Streaming.
ai.pygains a streaming sibling tochat_completion_toolsthat yields content deltas and accumulatestool_callsdeltas index-by-index into the same assistant-message shape the non-streaming path returns, so the agent loop is unchanged. A newdeltaSSE event carries tokens.Result cards.
toolSSE events grow arowspayload, and the client renders per-tool card types: spools (colour swatch, remaining weight, link to detail), filaments (low-stock and on-order tags), orders (shop, outstanding count), locations and vendors. Structure comes from the tool's own result payload, not from model-authored prose — a card cannot display a number the tools did not return.Usage chart.
get_usage_statsrenders a compact variant of the existing dependency-freeusageChart.tsx. No charting library added.Drawer affordances. Conversation persisted in
localStorage(the server loop stays stateless by design), a stop button viaAbortControlleron the SSE reader, and starter prompt chips on the empty state.Constraints worth knowing up front
messageevent must keep firing at end-of-turn with the complete text.deltais additive; the confirm/resume round-trip must be untouched.npm run check-i18ngates it. This is a meaningful slice of the work, not a footnote.Non-goals
Photo attachment in chat, bulk actions and proactive nudges are the third sub-project and are tracked separately.