Imported from mqtt-viewer/mqtt-viewer (
frontend/AGENTS.md). Install upstream withnpx skills add mqtt-viewer/mqtt-viewer --skill frontend. Copyright stays with the author.
Frontend / Component Library — Agent Guide
MQTT Viewer's frontend. Plain Svelte 5 (legacy syntax) + Vite + Tailwind 3,
running inside a Wails desktop shell. This is also the code half of a
design system that mirrors a Figma component library. Read this before
touching src/components, src/views, or any .spec.json / .stories.svelte.
Full design: docs/design-system/STORYBOOK_SPEC.md. Workflow: docs/design-system/WORKFLOW.md.
Adding ANY new
.svelteundersrc/components/orsrc/views/? (yes, even mid-feature — this is not just for design-system tasks.) It MUST ship two colocated siblings orpnpm ds:validatefails CI (.github/workflows/design-system.yml):
pnpm ds:make-spec <Name>— scaffolds<Name>.spec.json.- Create
<Name>.stories.svelte(copy a sibling;title=<Tier>/<path>exactly asds:validateexpects,tags: ["autodocs"],parameters.design).pnpm ds:validateuntil green (it also regeneratescomponent-index.json
COMPONENT_CHECKLIST.md— commit those too).No hardcoded colors: use token utilities (
bg-elevation-0,text-error), never hex likebg-[#0c0c0c](rule 8 below).Two CI steps, not one.
ds:validateis the spec/story/token gate;pnpm test-storybook(also in thevalidatejob) actually renders every story in vitest and fails on any unhandled error. So also:
- If the component imports a new Wails bound method, add it to
.storybook/mocks/bindings/…(and any new event tomockEventSetinsrc/stories/fixtures.ts) — otherwise the story fails to import and the error cascades to every parent story that renders it.- The component must not throw on mount with story props: stories render dialogs open (
isOpen: writable(true)) withmockConnection. Beware top-levellet x = reactiveVar.…— reactive$:assignments haven't run yet, so read from props directly and guard optional data.- Run
pnpm test-storybookANDpnpm ds:validatelocally before pushing any branch that touches.sveltefiles (svelte-check + vite build are not enough — they don't render stories).
The design-system loop (why this library exists)
Figma library (design) ──▶ Drift agent (/ds-figma-handover) ──▶ handover doc
│
▼
Implement agent (/ds-implement-handover) ──▶ code + spec + story
- Figma is authority for primitives (variants, tokens). Code follows.
- Code is authority for behavior/data wiring (views especially).
- The sync key is the PascalCase component name, identical in Figma and code.
Source of truth = colocated files (never a central registry)
Each component folder holds:
Button/
Button.svelte # component
Button.stories.svelte # Storybook story (visual layer)
Button.spec.json # design<->code contract (hand-authored TRUTH)
src/design-system/component-index.jsonis generated from the.spec.jsonfiles. Never hand-edit it. Runpnpm ds:registry.src/design-system/also holds the JSON Schemas, generateddesign-tokens.json, generatedCOMPONENT_CHECKLIST.md, andchanges/(handover docs). Only the prose plan lives in repo-rootdocs/design-system/.- Validate everything with
pnpm ds:validate(also regenerates the index +COMPONENT_CHECKLIST.md). This is the CI gate — keep it green.
Tiers (drive title namespace, diff strategy, layering rules)
| Tier | What | Figma object | Depends on |
|---|---|---|---|
primitive |
generic, domain-agnostic, props-only | variant component-set | nothing in the system |
component |
app-specific, knows MQTT/connections | composed instance | primitives only |
view |
a screen/frame | frame | components + primitives |
Hard rules (validator enforces):
- No uphill dependencies. A primitive may not depend on a component/view.
- Raw tokens belong on primitives. Components/views consume primitives, not raw token names. (Warning if violated.)
- Tier wrong? Fix tier first — it changes how the sync engine diffs.
Authoring rules (so stories + sync work)
- Props over fetching. A component must render from props alone in a story.
No store reads / no
bindingscalls in the render path. Where a component currently reads a store or Wails, pass mock data in the story; if impossible without refactor, record it inspec.json.notes— don't fake it. - Wails is mocked in Storybook (
.storybook/mocks/bindings/and.storybook/mocks/wailsio-runtime.ts). Don't import real Go bindings into shared/presentational components. - Co-locate the story; title =
<Tier>/<Name>(Primitives/Button). tags: ['autodocs']on every story meta (prop tables + smoke tests).argTypesfor every enum prop;optionsmust matchspec.jsonprops[].options(which match the Figma variant values).- Figma link on every meta:
parameters.design = { type: 'figma', url }. Use''+// TODO(figma-url)until known. - Don't rewrite to runes. Components use Svelte 4 syntax (
export let,on:click,$$Props) on Svelte 5 — leave it unless the task is a migration. - Tailwind tokens live in
tailwind.config.js; the canonical list issrc/design-system/design-tokens.json(generated bypnpm ds:tokens). Use token utilities (bg-primary,text-secondary-text), never hex.
.spec.json quick shape
Validated by src/design-system/component-spec.schema.json. Minimum: name,
tier, status. When figma-linked: figma.url + figma.nodeId + props[].
tokens[] must all exist in design-tokens.json. Scaffold with make:spec <Name>.
Status lifecycle
todo → story-only → figma-linked → deprecated. Advance only when the
requirements for the next state are real (validator checks).
Commands
| Command | Does |
|---|---|
pnpm storybook |
run Storybook |
pnpm ds:tokens |
regenerate design-tokens.json from tailwind config |
pnpm ds:registry |
regenerate component-index.json from .spec.json files |
pnpm ds:validate |
validate specs/tokens/coverage, regen index + checklist (CI gate) |
pnpm test-storybook |
run story smoke/interaction tests |
Storybook MCP (agent read-access)
Agents read the rendered library (components, stories, props, screenshots, tests)
via the framework-agnostic mcpland/storybook-mcp
(STORYBOOK_URL=<storybook>/index.json). The official Storybook MCP is
React-only in preview — don't use it here. The MCP needs Storybook served or
built first (pnpm storybook, or pnpm build-storybook). It complements the
specs (it knows rendered reality; the specs know Figma drift + the dep graph).
Writing copy (UI text, changelog, docs)
Anything a user reads (dialog copy, empty states, tooltips, the "What's new"
changelog, README) follows docs/WRITING_STYLE.md: warm, first person, British
spelling, concise, and no em dashes. The changelog lives in
frontend/src/changelog.ts; use the /changelog skill to update it.
Skills
/ds-add-component— scaffold or formalize a component (spec + story + tier)./ds-figma-handover— diff Figma library vs this code, emit a handover doc./ds-implement-handover— apply a handover doc to code + specs + stories./changelog— add to the "What's new" changelog, or promote it at release.