The smallest working surface

One tag. It creates its own services element, so a single element on the page is a working chat. Type something and press Enter.

Markup
<machvive-chat-syncopation>
  <machvive-chat-syncopation-canvas></machvive-chat-syncopation-canvas>
  <machvive-chat-syncopation-prompt slot="footer"></machvive-chat-syncopation-prompt>
</machvive-chat-syncopation>

A support surface

Suggested openings for the empty state, because an empty prompt box is the highest-friction moment in any chat interface. Click a chip β€” it fills the composer rather than sending, so you stay the author of your own message.

Support

Wait 20 seconds without typing to see the idle nudge. It fires once per conversation β€” a surface that keeps asking whether you are still there is a surface people close.

Streaming, and interrupting it

Switch the transport above to scripted and send anything. The reply streams a paragraph. While it generates, the button is Stop β€” not disabled, because a disabled control leaves you watching output you cannot interrupt. Stopping keeps the partial text you already read.

Scroll up mid-stream and the transcript stays where you left it. Autoscroll follows the bottom only while you are already there.

A command line instead of a composer

Same conversation, different input surface. Try /help, /clear, /transport, and the Up arrow. Slash commands run locally and never reach the model β€” /clear is not a prompt.

/order 1234 is registered from page code, which is how you add domain verbs without forking the component.

Voice, honestly gated

What your browser cannot do is disabled with the reason shown, rather than rendered as a button that silently does nothing. Dictation fills the composer; it never sends.

SpeechRecognition in most browsers sends audio to a vendor service. It is not local β€” worth saying out loud if the rest of your surface is private.

The instruments

The inspector reads the bus and shows topics it was never taught, including the playground:* events this page emits. History lists what is stored and gives it back β€” export as JSON, or delete it for real.

This is the only surface on the page with persist set. Persistence is off by default β€” storing someone's conversation is a decision a page has to make deliberately.

Two surfaces, no shared state

Components find their services element by walking up the DOM, so nesting is the wiring. There is no registry and no global, which is what makes these two conversations genuinely independent. Type in one; the other does not move.

Surface B sets theme="dark". An explicit choice wins in either direction β€” it stays dark in a light OS, and the one next to it stays with your OS preference.

Every role and status

User, assistant, system and tool turns, plus the pending and error states. Error turns are visible on purpose: silently dropping a failed turn leaves someone staring at a prompt that appears to have done nothing.

The last bubble contains <img src=x onerror=alert(1)>. It renders as those characters β€” record text goes through textContent everywhere, inspector included, because model output is untrusted input.