Lab — interface primitives

Every state,
on purpose.

These are the parts behind the chat on this site, as live specimens. A local fake stream drives them. It makes no network calls and runs the same way each time. Use the controls to force each state.

01

Streaming answer

An answer that arrives token by token, with inline citations. It uses the chat's real renderer, citation filter and state machine. A local fake stream sends the tokens.

The full answer, with no stream.

Machine state

Done. 4 sources cited.

Question: How does a streamed answer reach the screen?

The answer arrives as a stream of small text events. Each event carries the attempt it belongs to, so text from a stopped answer cannot write into a new one.

  • A token can split a citation marker in two. A filter holds the tail back until the marker is complete.
  • The text is split into blocks. On each token, only the last block parses again.
  • A source always arrives before the first text that cites it. A chip never shows without its evidence.

You can stop the answer at any time. The text that arrived stays on the screen, and the sources it cited stay with it.

Sources, in order of first citation

  1. Source 1: Chat state machinesrc/lib/chat/chat-machine.ts

  2. Source 2: Citation filtersrc/lib/chat/citation-filter.ts

  3. Source 3: Streaming markdownsrc/lib/chat/streaming-markdown.ts

  4. Source 4: Stream protocolsrc/lib/chat/protocol.ts

States
Idle, pending, streaming, complete, stopped and failed. Each forced state above moves the real machine into that state.
Keyboard path
Tab to a citation chip. Its preview opens on focus. Enter moves focus to the source in the list. Esc stops a stream.
What it proves
The text grows without layout jumps, a chip never shows before its source, and a stop or an error keeps the text that arrived.

02

Citation drawer

The evidence for an answer, in a drawer. On a phone it comes up from the bottom and stops at 60% or full height. On a wide screen it comes in from the right. The source cards are the chat's real EvidencePanel.

The full answer, with no stream.

Machine state

Done. 4 sources cited.

The answer arrives as a stream of small text events. Each event carries the attempt it belongs to, so text from a stopped answer cannot write into a new one.

  • A token can split a citation marker in two. A filter holds the tail back until the marker is complete.
  • The text is split into blocks. On each token, only the last block parses again.
  • A source always arrives before the first text that cites it. A chip never shows without its evidence.

You can stop the answer at any time. The text that arrived stays on the screen, and the sources it cited stay with it.

States
Closed or open, and in each the answer state: pending shows card skeletons, streaming adds cards as the answer cites them, and stopped or failed says how the answer ended.
Keyboard path
Enter on a chip opens the drawer with focus on that source. Enter on “Open evidence” opens it at the heading. Tab stays in the drawer. Esc closes it, and focus goes back to the control that opened it.
What it proves
The drawer is a real modal dialog with a focus trap and focus return. The stream does not stop while it is open: new sources appear in it live.

03

Fluid resizer

The answer beside its evidence. Drag the handle to resize the panel, or collapse it to a rail. The collapse runs a real spring, sampled into a CSS easing, so the browser animates it without JavaScript on each frame.

The full answer, with no stream.

Machine state

Done. 4 sources cited.

The resizer needs a screen at least 1024 px wide. On a narrow screen, the chat uses the drawer in specimen 02 instead.

Question: How does a streamed answer reach the screen?

The answer arrives as a stream of small text events. Each event carries the attempt it belongs to, so text from a stopped answer cannot write into a new one.

  • A token can split a citation marker in two. A filter holds the tail back until the marker is complete.
  • The text is split into blocks. On each token, only the last block parses again.
  • A source always arrives before the first text that cites it. A chip never shows without its evidence.

You can stop the answer at any time. The text that arrived stays on the screen, and the sources it cited stay with it.

States
Open at any width from 300 to 560 px, or collapsed to a 56 px rail. The rail keeps the source count live while the answer streams.
Keyboard path
Tab to the handle. Left and Right arrows resize the panel, Home and End go to the limits, and Enter opens or collapses the panel. In the panel, the hide button collapses it and moves focus to the rail. Enter on the rail opens it and moves focus to the heading. Enter on a chip opens the panel at its source, and Esc goes back to the chip.
What it proves
A drag follows the pointer with no spring. A toggle springs, and it opens again at the last width. With reduced motion, the panel moves at once.

04

A move that keeps state

A card moves between the feed and a side dock. Type a note, pin a source, or scroll the answer, then move the card. Start a slow stream and move it while the text arrives.

The full answer, with no stream.

Machine state

Done. 4 sources cited.

Card in the feed.

Saved answer

Mounted 0 times · scroll 0 px

The answer arrives as a stream of small text events. Each event carries the attempt it belongs to, so text from a stopped answer cannot write into a new one.

  • A token can split a citation marker in two. A filter holds the tail back until the marker is complete.
  • The text is split into blocks. On each token, only the last block parses again.
  • A source always arrives before the first text that cites it. A chip never shows without its evidence.

You can stop the answer at any time. The text that arrived stays on the screen, and the sources it cited stay with it.

Pin a source
States
Feed or dock, combined with every answer state. The stream, the note, the pin and the scroll position all survive the move.
Keyboard path
Tab to “Card placement”. Arrow keys move the card. Tab into the card to type a note, pin a source with Enter or Space, and scroll the answer with the arrow keys.
What it proves
The move is a layout animation on one element, not an unmount and a mount. The mount counter stays at 1. With reduced motion, the card jumps to its new place, with the same state.

The demo answer and its sources describe the code of this site's chat. The text is fixed, and the timings are fixed in src/lib/lab/fake-stream.ts. The renderer, the citation filter and the state machine are the same code that runs the chat.