Skip to content

Getting Started

neodigm edited this page Oct 4, 2026 · 1 revision

Getting Started

Install

npm i @machfivetechchicago/machvive-chat-syncopation-ai
// Everything, registered on import.
import '@machfivetechchicago/machvive-chat-syncopation-ai';

// Or cherry-pick; each subpath self-registers its own tag.
import '@machfivetechchicago/machvive-chat-syncopation-ai/canvas';
import '@machfivetechchicago/machvive-chat-syncopation-ai/prompt';

Registration happens on import, not on first use. Import order does not matter.

Bundler note. Never mark this package sideEffects: false, and do not let a tree-shaker treat import 'pkg/canvas' as dead code — the import is the registration, and dropping it means the tag silently never upgrades. The package's own manifest declares sideEffects: true and a test enforces it.

Without a bundler

The files are published as authored ESM, so a plain <script type="module"> works:

<script type="module">
  import 'https://esm.sh/@machfivetechchicago/machvive-chat-syncopation-ai';
</script>

The smallest working surface

One tag. It creates its own services element and defaults to the echo transport, which replies by repeating what you said — enough to build and style the entire surface before any model exists.

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

The container is layout and nothing else. It gives you three slots:

Slot Typical contents
header A title bar, a close button, a model selector
(default) The canvas, and anything above it such as nudges
footer The composer, the CLI, the voice controls

Adding the rest

<machvive-chat-syncopation theme="dark">
  <h2 slot="header">Support</h2>

  <machvive-chat-syncopation-nudge
    suggestions="Where's my order?|I'd like to return something|Talk to a human"
    idle-ms="45000"
    idle-text="Still here if you need anything.">
  </machvive-chat-syncopation-nudge>

  <machvive-chat-syncopation-canvas></machvive-chat-syncopation-canvas>

  <machvive-chat-syncopation-prompt slot="footer" placeholder="How can we help?">
  </machvive-chat-syncopation-prompt>
</machvive-chat-syncopation>

That is a complete support surface: suggested openings for the empty state, a streaming transcript, a composer, and a once-only idle prompt.

Owning the services explicitly

Wrap the surface in <machvive-chat-syncopation-services> when the page needs to reach the bus, wants persistence, or runs two conversations that must stay separate.

<machvive-chat-syncopation-services id="chat" transport="local" model="Llama-3.2-1B" persist>
  <machvive-chat-syncopation>
    <machvive-chat-syncopation-canvas></machvive-chat-syncopation-canvas>
    <machvive-chat-syncopation-prompt slot="footer"></machvive-chat-syncopation-prompt>
  </machvive-chat-syncopation>

  <machvive-chat-syncopation-history></machvive-chat-syncopation-history>
</machvive-chat-syncopation-services>
const chat = document.getElementById('chat');

chat.bus.on('record:added', (record) => {
  analytics.track('chat_turn', { role: record.role });
});

await chat.send('Hello');

Configuration

Set these on the services element — or on the container, when you let it create its own and it will pass them through.

Attribute Default Meaning
transport echo echo, local, remote, or your own registered name
model — Handed to the transport
endpoint — Handed to the remote transport
persist off Keep conversations in IndexedDB. Opt-in, deliberately
max-turns 200 Oldest turns are trimmed past this
streaming true Whether transports stream
theme OS light or dark, overriding prefers-color-scheme

A bare attribute means on: persist and persist="true" are the same, and persist="false" turns it off.

Two surfaces on one page

Because discovery is by DOM position, this just works — two conversations, two caches, no shared state, no configuration:

<machvive-chat-syncopation-services transport="local" model="Llama-3.2-1B">
  <machvive-chat-syncopation><!-- the private one --></machvive-chat-syncopation>
</machvive-chat-syncopation-services>

<machvive-chat-syncopation-services transport="remote" endpoint="/api/chat">
  <machvive-chat-syncopation><!-- the hosted one --></machvive-chat-syncopation>
</machvive-chat-syncopation-services>

Connecting a real model

The echo transport exists so you can finish the interface first. When you are ready, see Writing a Transport — it is an async generator that yields strings, and that is the entire contract.

Frameworks

These modules are browser-only. class X extends HTMLElement is evaluated at import, so a server-side import throws ReferenceError: HTMLElement is not defined. That is inherent to custom elements, not a bug to work around.

Framework Import from
React / Next.js inside useEffect, or next/dynamic with ssr: false
Vue / Nuxt onMounted, or <ClientOnly>
Svelte / SvelteKit onMount
Astro client:load
Anything else await import() behind typeof window !== 'undefined'

React also needs the usual custom-element caveats: pass data via attributes or imperative properties on a ref, and subscribe to custom events with addEventListener rather than an onEvent prop.

TypeScript needs nothing — every entry point ships a .d.ts and HTMLElementTagNameMap is augmented, so querySelector is typed.

Next: Component Reference.

Clone this wiki locally