RD

reghartner/design-viz

开发工具
0 stars 质量 64 趋势 20MIT

Turn a written design document into an animated, meeting-ready visual page — without anyone hand-writing HTML.

概览

Turn a written design document into an animated, meeting-ready visual page — without anyone hand-writing HTML. An agent (or a person) emits one declarative JSON spec; a self-contained HTML template renders it as a full documentation page: titled sections with prose and bullets in colored bounding boxes, tab groups, and animated flow diagrams that each offer two views of the same time-aware data — an ambient always-animating view and a guided step-through (click-through) view. Maintained diagrams belong in diagrams/, one subfolder per diagram page with its required JSON spec and any optional portable HTML export. The directory guide covers building and moving existing diagrams. Root canon.json is the shared canon membership list for Backstage and the nginx workbench. For live local authoring with a filesystem-only agent, build the workbench and run node tools/agent-session.mjs. It prints an editor URL and a scratch folder for document/selection snapshots and undoable agent proposals.

README

design-viz

Turn a written design document into an animated, meeting-ready visual page — without anyone hand-writing HTML. An agent (or a person) emits one declarative JSON spec; a self-contained HTML template renders it as a full documentation page: titled sections with prose and bullets in colored bounding boxes, tab groups, and animated flow diagrams that each offer two views of the same time-aware data — an ambient always-animating view and a guided step-through (click-through) view.

Maintained diagrams belong in diagrams/, one subfolder per diagram page with its required JSON spec and any optional portable HTML export. The directory guide covers building and moving existing diagrams. Root canon.json is the shared canon membership list for Backstage and the nginx workbench.

For live local authoring with a filesystem-only agent, build the workbench and run node tools/agent-session.mjs. It prints an editor URL and a scratch folder for document/selection snapshots and undoable agent proposals. See local design sessions; no browser access is needed.

For a small facilitated study of this workflow, use the pilot operating runbook and reusable story/layout scorecard. They cover the four authoring starts, both connection methods, next-day resume, engineer handoff, observation measures, and separate handling of review packages and pilot-research archives.

Build or download the workbench

With Python 3.10+ and Node 24 installed, run:

python3 tools/build.py

To keep a company landing page across upstream updates, commit company-owned config and its JSON spec, then rebuild. See homepage configuration for automatic workbench/site.json and external --config workflows.

Open workbench/flowspec.html locally, or serve the checkout with python3 -m http.server 8765 and visit /workbench/flowspec.html. The build also creates template/flowview.html. Both HTML entrypoints are ignored build outputs; commit their source changes, not these files. Rebuild after pulling source changes.

For ready-to-open files without build tools, download flowview-html from a successful CI run and extract the archive. The workbench and viewer are self-contained; HTTP is needed for fetching the adjacent catalog and Canon library. The nginx image builds the HTML from source automatically. See build outputs and distribution.

Connected company flows

For the complete reproducible integration, use the portable Backstage/GitHub rehearsal. It creates fresh mock-company and designer repositories, seeds the workbench through a real HTTP GET, runs an actual Backstage app with the Flowview plugin, and includes daily catalog/drift workflows plus harmless and breaking source experiments. Repository names are configurable; no access to private demo repos is needed. All resulting PRs are left for human review and manual merge.

Run node apps/backstage-mock/server.mjs and open port 8766 for a fictional central repository with catalog-seeded editing, code drift reviews, approved reference traces, and incident alternates. No company sandbox is needed. See the canonical flow guide, doorbell recipe, and Backstage/company handoff. The runnable sample app rehearses harmless and breaking source changes against actual local Git commits and fixed contract tests. For unattended GitHub scans, use the automation setup: weekday runs, report-only previews, saved evidence, and human-reviewed drift PRs. The Backstage entity plugin renders related diagrams inside service/API pages, with in-place step jumps and external editing. Its native renderer owns a ShadowRoot and makes no background network requests; the Backstage host reads specs through the authenticated proxy. The plugin guide includes a local preview.

The presentation edition covers the current platform in eight guided chapters, with real local Backstage screenshots, slide-ready diagram images and a suggested talk track. It separates ownership, catalog-seeded authoring, native viewing, code drift, human review, trace evidence, shared releases and the end-to-end rehearsal.

The detailed system walkthrough includes Deployment and Inline viewer tabs: repository ownership, external editing, authenticated reads, native mount ownership and stale-revision handling, alongside the existing GitHub drift and trace flows. Its embedded screenshot shows the actual plugin with local fictional data.

Embedded image panels store small PNG/JPEG/WebP captures inside the spec, with alt text, captions and optional reference links. Add an image panel in the workbench and upload its Image file; placement, resizing, copy/paste and Undo use the existing editor controls.

App screens turn manually exported Figma screens into a reusable collection. Upload images once, choose a starting screen, and select screens in each step’s inspector. A phone frame and cut/crossfade transitions keep the product experience beside its system flow in standalone and Backstage viewers. The example includes four illustrative screens with connected/offline paths.

Standalone pages offer a first-visit tour and a ? replay button. The tour authoring guide explains audience tracks, named views, Explore practice, demonstrations, and optional page.tour customization.

The pipeline

For agents, start with the HLD-to-page skill and its cookbook index. They include complete recipes for alternate timelines, Home-centered stories, and camera recording/event timing.

For a larger seed, use one flow, two perspectives: an 11-component doorbell design with an 18-step happy path, two alternate endings, and synchronized engineering and resident-story views. Its source, storyboard, coverage ledger and spec are provided together for authoring agents. The skill now plans evidence and state before JSON; the authoring trials record the smaller-model tests, observed failures, instruction changes, and remaining review needs.

The thermal-protection example adds hot/cold Home effects, independent camera shutdown and charging, temperature limits on both ends, and explicit unavailable-video explanations. Open it from the workbench’s Open file using the thermal spec.

For repository PRs, use the PR-handling skill. It starts with the diff and loads only the review guidance for the affected surfaces.

Confluence viewer: keep editing in the workbench, then use Export for Confluence or Copy JSON for Confluence for manual import into the Forge macro. The viewer retains playback, alternate paths and panels. The company’s agent can finish deployment using the Forge integration handoff.

Alternate paths: show a happy path and failure outcomes on one diagram. Colored path rows align their step numbers beneath the transport controls; playback and panel state follow the selected sequence and stop at its own ending. Open the alternate paths example, or fork a beat in the Steps tab.

Failed communications: show a packet dropped in transit or a communication that was never sent, using broken edges on the same diagram. Choose an outcome with each step’s Delivery control.

New: Honeycomb trace → diagram. In the workbench, choose import trace…, paste or open event JSON, and Build diagram. The result is a service board, an offset-aware waterfall, and a step for each span. No account connection is required. Start new project → A trace, explained opens a fictional trace example. Agents use node tools/trace2spec.js.

Every rows array renders its slots left to right. Stack members render top to bottom within one slot.

All row diagrams offer Auto, Fit width and Readable views, with curved edges or lane routing. Auto scales smoothly as the diagram column resizes; Readable keeps labels large and lets you scroll. Drag the diagram background to pan, use − / + or Ctrl/⌘ + wheel (including trackpad pinch) to zoom, and Fit diagram to see the whole graph. Manual zoom stays inside the diagram frame without changing the page height; Fit width returns to responsive sizing. These controls also work in published pages; no routing setting is required.

Broader design panels: table shows data snapshots, checks shows authored decisions and invariants, and budget compares resource usage with explicit limits. Open the software & IoT example for both domains. The cost breakdown / comparison panel shows one operation or up to six entries with per-million usage and fixed charges. Component amounts and shares stay visible for a single operation; exactly two entries also show cost difference and break-even volume. Stacked bars share one zero-based scale with Auto, Compact and Expanded density. Start with the single-operation example, or open Messaging cost tradeoffs in the workbench templates for the original 1M / 10M / 100M comparison and its Compact comparison view. Use the retries & circuits example for retry success, deadline admission, and circuit recovery; the recipe explains how to adapt their authored policies and timing. The replica positions example compares applied positions against a session token while keeping reported lag, offline state and unrelated histories distinct. Its recipe also covers device/cloud configuration copies. The rollout decisions example separates configured traffic, sampled health, firmware trial boots and persistent confirmation. Its recipe covers promotion, holds, traffic rollback and missing device reports. The workbench’s document outline (⌘/Ctrl K) searches sections, hidden tabs, nodes, groups, edges, panels, and steps and opens the matching inspector. See the ranked feature roadmap for the next additions.

The visual panel library opens from Add to diagram → Panel → Browse panels: browse actual panel previews, select a type, and confirm with Add panel.

Repository catalog sync reads company catalog/API files from GitHub, proposes an updated service catalog, and bundles the approved snapshot with the nginx editor. New projects load those dropdown choices automatically. Use From service catalog on the homepage or Add to diagram → Services from catalog to select services and optionally create their declared dependency edges. See catalog graph seeding.

The effective-state inspector explains the panel values at a selected step, including inherited and transient values, with links to their authored JSON and read-only snapshots. Use workspace focus and the resizable editor for more room: drag the divider or Expand editor beside a compact live preview. Inspect / Steps / JSON each get a dedicated pane. Width and selected tool persist locally; phones use a stacked layout. Section arrangements and host previews let you drag and resize panels and diagrams, with separate layouts for Backstage and Confluence.

The story-step editor adds caption search, hidden-tab jumps, duplication, appending and reordering through the same undo history. Reuse steps copies or shares beats between paths, previews their resulting state, and can detach a shared step in place or remove just one path’s occurrence. The object clipboard copies Home elements, panels, nodes and whole sections between diagrams. It includes mobile controls, ⌘/Ctrl C/V/D, fresh IDs, and one-step Undo.

design doc (prose / permalinks / mermaid)
        │
        ▼   an LLM agent, given only contract/authoring-contract.md
  page spec (JSON — no coordinates, no HTML)
        │
        ▼   tools/inject.py spec.json template/flowview.html out.html
  standalone HTML page (share, publish, present)

Feedback loop: meeting feedback goes back to the agent as plain English, the agent edits the JSON, re-inject, republish. The spec is data, so revisions diff cleanly in git.

How this was built

Everything here — content and tooling — came out of an agent-driven loop:

  1. Research fanout. AI agents gathered the IoT device offerings of 27 companies (docs/research/iot-device-catalog.md), focusing on dual-chip devices that maintain an active internet connection, with industry-standard solutions as the baseline.

  2. Fictional HLDs. Agents drafted twelve fake high-level design documents (docs/hlds/) against those feature sets. Every company, service, and product name in them is invented. Each draft went through human and agent review passes for realism and internal contradictions.

    One concrete example of how that grounding works: the doorbell page (examples/doorbell/, expanded in examples/doorbell-atlas/) is a composite of two real designs, built only from public information. Its hardware architecture — a low-power chip that stays connected on microamps handling Wi-Fi, MQTT, and the PIR sensor, waking a separate camera/encoder chip over an interrupt line for seconds per visit — follows Blink’s publicly documented dual-chip battery design. Its cloud architecture — motion events over an MQTT broker, video never touching the broker, clips uploaded over HTTPS and linked back to the originating event — follows Ring’s event-versus-media split. The radar-presence sibling (examples/chime-radar/) corresponds to the 24 GHz radar in Ring’s Battery Doorbell Pro. No affiliation with either company; everything named in the documents is fictional.

  3. Widgets. Agents built the inspector widgets needed to visualize those features — built-in panels ranging from state machines and gauges to a ring buffer, a radar sweep, a wall-clock heartbeat timeline, and a phone notification stack.

  4. Generator + contract. The page generator was built around a strict authoring contract (contract/authoring-contract.md) and a written agent runbook (.claude/skills/hld-to-page/SKILL.md) for turning an HLD into a page.

  5. Authoring fanout. Agents of all capability levels then attempted to follow the runbook and build pages from the fake HLDs (examples/), reporting every hiccup. Those reports fed back into the contract, the cookbook recipes, and the skill definition.

Layout

path what it is
company/ Fork-owned overlay. Upstream ships only its README. Company forks add panel types and helpers, contract fragments, examples, tests and release notes here, and the build and test suites discover them without upstream list edits. company/company.json sets the fork’s name, repository, brand and package identity. See company forks.
src/panels/types/.js One complete panel definition: validation, state rules, renderer, editor controls/metadata, picker example, styles, layout/reference capabilities and release metadata. Shared primitives stay in src/panels/shared.js; see panel development.
src/ + tools/build.py The engine source of truth. build.py assembles both single-file pages and tools/canon/generated-runtime.cjs for Node backends from src/. Viewer/workbench HTML is ignored and generated for local use, CI artifacts and image builds. Authored diagram exports remain independent. Runtime JavaScript and copied declarations/licenses are ignored build outputs too. Local commands and CI generate them before tests and packaging; see build and distribution instructions. The Canon index is regenerated during the build.
tests/ + .github/workflows/ci.yml Python + Node unit tests (zero dependencies): injection anchoring, build determinism, spec validation, lint rules, layout math, panel-state folding, tool exports, and end-to-end CLI checks over seeded fixtures. CI runs them plus an examples-build and spec-validation check on every push and PR.
tools/browser-tests/ Required pinned-Chromium contracts for offline HTML, editor source/history and teardown, native React isolation, and copied Forge resources. Commands and boundaries.
tools/validate.js Validator + lint CLI: node tools/validate.js [ ...] prints errors, warnings, and lint findings with field paths; exit 1 on errors. When any input declares shared topology, all inputs form one authored snapshot that is resolved only in memory before each file is checked. --quiet for CI. Loads the same validator the pages ship, so CLI and in-page results cannot drift.
tools/visibility-check.cjs Presentation-eligibility CLI: node tools/visibility-check.cjs [ ...]. Provider arguments supply the complete authored topology snapshot for an importing consumer; resolution is read-only and in memory.
tools/export-layout.cjs Local diagnostic export: writes only anonymous node bounds, numeric edge topology and the exact rendered SVG edge paths. It never writes titles, source IDs, labels, prose, panels or metadata.
template/flowview.html The render target (generated locally, not tracked — edit src/). Self-contained single file: layout engine, themes, protocol-keyed legend, tabs, step player, containment groups, synchronized inspector panels (state machine, LEDs, gauge, log, camera screen), permalink affordances. Reads its spec from an embedded JSON block.
tools/inject.py The injection step: inject.py . Validates the JSON, refuses unescaped </script, sets the page title from page.title, and discovers derived sibling links when the conventional output root already has crossref.json.
tools/build_index.py Generates a root’s index.html and crossref.json from manifest.json plus every named spec. The index groups pages by family and lists exact-title services shared by 2+ pages; the JSON catalog supplies derived per-page backlinks.
tools/mermaid2spec.py Converts a mermaid sequenceDiagram (bare, or the first ```mermaid fence in markdown) into a deliberately bland skeleton spec: python3 tools/mermaid2spec.py [-o out.json] [--title "..."]. Enriching icons, tints, protocols, and prose stays the authoring LLM’s job; unsupported mermaid constructs fail loud.
contract/authoring-contract.md The complete authoring contract. Self-sufficient: hand this file plus a source document to any LLM and it can emit a valid spec with zero other context.
cookbook/ Task-shaped recipes for authoring agents: one file per common request (temperature thresholds, battery drain, Radar sensing geometry and authored alerts, wake-up mailbox, persistent-connection-while-awake, LP-chip MQTT relay, egress routing) plus adjustments.md, a phrase-to-knob table for visual feedback (“move that up and to the right”). Every ```json fence in it is a complete spec kept lint-clean by tests/test_cookbook.py.
workbench/flowspec.html Generated locally, not tracked. Interactive workbench: the same engine plus an editable JSON panel with a Render button, click-to-definition selection with per-element inspector forms (field edits, id renames with reference rewrite, deletes with reference pruning, step reorder, undo), snippet INSERT buttons, schema reference, and known-limits notes. For hand-tuning specs.
examples/cumulus/ End-to-end proof. cumulus-hld.md is a realistic fixture design doc (mermaid + prose-only flow + facts buried in paragraphs). cumulus-page.spec.json was generated from it by GPT-5.6 given only the contract; cumulus-page.spec.v2.json applied three plain-English “meeting feedback” items. cumulus-flow.html is the rendered v2 output.
mockups/ The hand-built design explorations that defined the target: flowline-mockups.html (four visual treatments; mockup 04 “Aurora Combined” is the chosen direction), device-lens.html (click-through with a device-internals inspector), doorbell-northstar.html (the northstar: three synchronized panels — flow board, two-chip hardware view with wake states, camera viewfinder that visibly starts recording).

Panel modules are discovered through src/source-bundles.json, including a company fork’s company/panels/ overlay. For headless Node tools or VM tests, load readSource('validator.js') from tools/source-loader.cjs, then the assembled engine when needed; raw validator and engine file reads omit the registered panel implementations.

Sharing anonymous graph geometry

Build the local backend once, then point the diagnostic exporter at a FlowSpec:

python3 tools/build.py --runtime-only
node tools/export-layout.cjs input.spec.json output.layout.json

The output is a coordinate snapshot for discussing awkward layouts. It is not a FlowSpec and cannot recreate the source content. Every diagram is exported in page and tab order; prose sections are skipped. Node IDs are local integers in renderer node order (integer-like keys enumerate numerically), and edges refer only to those integers. Coordinates use the renderer’s SVG user units: x and y are the node’s top-left corner, while width and height are its rendered card bounds. path is the exact SVG path data produced by the renderer, including authored curves, ports, automatic routing, overlaps, negative coordinates and fractional values. The exporter does not arrange, normalize, snap or simplify the graph.

{
  "format": "flowview-layout-v1",
  "diagrams": [
    {
      "nodes": [
        {"id": 1, "x": 35, "y": 42, "width": 150, "height": 54},
        {"id": 2, "x": 995, "y": 42, "width": 150, "height": 54}
      ],
      "edges": [
        {"from": 1, "to": 2, "path": "M 185 69 L 995 69"}
      ]
    }
  ]
}

Only the fixed keys above, finite numbers, the format literal and generated SVG commands appear in the file. The CLI refuses source/output aliases, writes with private permissions and replaces an existing destination only after the entire input validates and all geometry has been computed.

Regenerating the example

python3 tools/build.py
python3 tools/inject.py examples/cumulus/cumulus-page.spec.v2.json \
    template/flowview.html examples/cumulus/cumulus-flow.html

Using this repo as a tool from another project

Company forks can publish internal engine releases, with the designs repo and Backstage pinning releases independently. Spec/runtime compatibility records the minimum renderer and required features, with upgrade notices in the viewer. Workbench Save/Export stamps metadata; agents can use node tools/compatibility.js --stamp input.spec.json > versioned.spec.json.

This repo is designed to be pulled in (cloned or vendored) and driven from a consuming project. An agent converting a design document follows the full runbook at .claude/skills/hld-to-page/SKILL.md — it establishes three paths (the HLD, this checkout as read-only VIZ, and a caller-chosen output directory OUT in the consuming project), walks a coverage ledger and an operator-approved visualization proposal, then builds with:

python3 /tools/page_build.py  --root 

--root creates OUT if missing; .html, the source-named spec copy, and OUT’s own manifest.json land flat in that directory — nothing is written inside this repo. The page name comes from either .spec.json or plain .json, and its manifest family defaults to OUT’s directory name. Pass an explicit / after the spec when a nested library is wanted; that established mode still writes //.html and .spec.json. An explicit bare plus `--root` selects a different flat output name. Both and --root may be given relative to your own working directory — the tool anchors them to the directory you run it from, and a symlinked spec or root keeps its alias name in the outputs and the manifest family. python3 /tools/build_index.py --root --title "..." adds a browsing index over everything in OUT plus an OUT/crossref.json service catalog. Selective widget documentation comes from python3 /tools/widget_doc.py so an agent loads only the contract sections its document needs (--list prints the valid type names; --contract-card prepends the shared message-contract-card section).

Authoring workflow for an agent (the core loop)

  1. Give the agent contract/authoring-contract.md and the source document — or better, the full skill runbook above.
  2. The agent emits strict JSON only (page → blocks → sections/tabs → diagrams with nodes, rows, edges, steps). No coordinates, no HTML. Unknown fields are ignored; validation errors name the field and say what to fix.
  3. Run tools/page_build.py (validate → inject → manifest, with --root for external output), or bare tools/inject.py for a one-off render. Every diagram with a steps array automatically has both the ambient view and the guided step-through; view: "step" opens a board in guided mode.

Editing a page by hand (human loop)

Every rendered page has its spec JSON sitting next to it (usually .spec.json, with plain .json also supported, beside .html — under examples// in this repo, or under your own output directory if you built with --root). The HTML is generated; never edit it. To change a page:

  1. Edit the spec. Open the .spec.json in any editor. Common edits and where they live:

    • wording → section text / bullets, step text
    • a node’s name/subtitle/link → nodes..title / .sub / .link
    • an arrow’s label or protocol → the entry in edges (label, kind)
    • what a step shows → that entry in steps (its edge/nodes and the panels patch)
    • panel values (a temperature, a battery %) → the panel’s initial or a step’s panels patch
    • the source link under the title → page.generatedFrom The full field reference is contract/authoring-contract.md; for “move that label / too crowded” style tweaks, cookbook/adjustments.md maps plain-English complaints to the exact knob.
  2. Rebuild the page. Same command that built it:

    python3 tools/page_build.py path/to/name.spec.json
    # add --root  when output is separate from the source spec
    # optional nested layout: add / (and --root when external)
    

    With no slug and no --root, .html and manifest.json are updated in the spec’s own directory; no redundant spec copy or subdirectory is created. It validates first and refuses on problems, printing each one with the JSON field path and what to fix — validator warnings also stop the build (cookbook/adjustments.md maps the common ones to spec knobs); --allow-warnings builds through them. PAGE_BUILD OK means the HTML and the manifest are updated. Descriptions/tags are kept from last time unless you pass --desc / --tags again. This repo’s existing examples/ library deliberately remains nested, so rebuild those pages with their explicit / and then run python3 tools/build_index.py to refresh the index and cross-page service catalog.

  3. Look at it. Open the rebuilt .html in a browser — no server needed.

Cross-page catalog refresh loop

Cross-page identity starts with exact, case-sensitive node title strings; node ids remain page-local. For service nodes, matching is unrestricted across families: a title keeps that behavior when any occurrence has tint cmd, auth, data, or mqtt, or has no tint. A title whose occurrences are all dev is family-scoped instead. Its manifest family, the leading segment of a maker-product family slug, and an explicit node group (when present) identify its product family; cross-family device-title matches are dropped. This keeps real components such as Sentry LP across the Doorbuzz pages and Sentry Panel across Hearthline pages while treating a generic phone title repeated by unrelated products as coincidence.

tools/build_index.py reads every spec named by the target root’s manifest, writes deterministic crossref.json in this shape, and adds a Shared services section to the bottom of the index:

{
  "services": {
    "Relay": [
      {"file": "cumulus/cumulus-flow.html", "family": "cumulus", "title": "Cumulus Command & OTA Paths"}
    ]
  }
}

Surviving all-dev records additionally carry generator-owned "scope": "family" and a sorted "families" list. Consumers use those fields to keep both index rows and per-page backlinks inside the matching product family; authors never add them to a flowspec.

page_build.py keeps the catalog out of the authored spec: when /crossref.json exists, it writes only the current page’s sibling links to a second embedded JSON block named flowbacklinks. Nodes then expose a small ↗ affordance whose popover says also in: and links to those sibling pages. An author-declared node source link keeps its own ↗ beside it, and all derived affordances are hidden in print.

The catalog intentionally reflects the last build_index.py run, so one-run staleness is acceptable. For fully fresh links use this loop:

  1. Build or update the pages with page_build.py.
  2. Run build_index.py for that root.
  3. Rebuild pages that need fresh backlinks.

Skipping step 3 simply leaves those pages one catalog run behind; no authoring field or agent-authored cross-reference data is required.

For human authoring instructions, open User guide in the workbench header or workspace toolbar. The bundled guide includes a first-diagram walkthrough, alternate paths, shared steps, Home animation, layouts, catalogs, and sharing. Canon diagrams on welcome opens the read-only repository library, with an explicit Edit in Workbench handoff. Keep maintained JSON under diagrams// and optionally add a portable HTML export. Add the folder and owner to root canon.json after review. The normal build and nginx image build publish that central list; per-spec flags do not enroll a document. Use Copy link in the reader to share a direct ?diagram= URL before Backstage is connected.

Open workbench/flowspec.html to Paste JSON, Open file, or start a New project from a curated template or a blank diagram. The welcome guide also covers saved drafts and copyable instructions for building with your agent. Use New / open to return here from the editor; the previous project stays available through Resume and Undo. Once open, tweak the live preview or use the JSON pane and Render button, then save the result back to your .spec.json. Clicking a rendered node, edge, edge label, step coin, panel widget, or section box opens an inspector form for it, with a link to select its exact JSON: every common field is an editable control (selects for icon, tint, kind, edge endpoints, step edge and lane; text fields elsewhere) that writes straight through to the JSON and re-renders on commit. Clearing an optional field removes it from the spec. Renaming a node or panel id rewrites every reference — rows, floats, edge endpoints, step from->to keys, step node lists, panel patches. delete removes the element and prunes what pointed at it (deleting a node also removes its edges and their step references; steps legally survive as captions). Steps get ↑ earlier / ↓ later reorder buttons. Every builder action pushes an undo snapshot (the always-visible undo button below the editor, 30 deep). Selection maps the last render onto the current editor text — re-render after hand-reordering edits to keep the two aligned. Add to diagram, above the editor tabs, adds nodes, connections, steps and panels. The Into selector beneath it chooses the destination section, including sections inside tabs; clicking a section in the preview updates it too. Choose Node for icon presets (Console, API, Auth, Store, Broker, Sensor, …), or Panel for the visual panel library. Each addition uses an explicit button and is one Undo. Connection asks you to click a source and target in the chosen section (Esc cancels; duplicate from->to pairs are refused). For a shortcut, Alt/Option-click a node, then click its destination, or select a node and use Connect from this node in its inspector. A live arrow previews the connection and eligible targets highlight. The new edge opens for label/protocol/port edits; one Undo removes it. Step appends to its selected timeline. Page structure inside the chooser adds sections or tabs. Dragging an edge label commits the movement as labelDx/labelDy nudges — the hand-tuning chore for crowded corridors — while a plain click still selects. Prose is selectable too: paragraphs, bullets, and contract-card rows each open their own edit form with delete; nodes and sections also get a duplicate button (the node copy lands beside the original — same row, stack, or float). Keyboard: Esc clears the selection or cancels connect mode; Delete removes the selected element (only when focus is not in a form field or the editor). The editing loop is durable: open… loads a .spec.json from disk (undoable), save downloads the editor text as .spec.json (even when it does not parse — unfinished work is still work), every edit auto-saves a draft to browser storage, and after a reload a bar offers to restore or discard the unsaved draft (restore is one undo step).

For free node placement and explicit edge ports, choose float → Free placement, then drag the node or edit Float X/Y. Floating nodes can move anywhere; row nodes keep their reorder/swap behavior. Edges expose Exit / Entry side and position (%). Each drag is one Undo. Try the flexible service map. undo has a matching redo (a new action clears the redo line; hand edits in the editor are not snapshotted, but undo stashes the current text on the redo side first, so nothing is discarded). Validation findings are clickable: every error/warning in the message list selects the offending JSON field in the editor (or its nearest existing parent when the message names a field the text does not carry). Steps are selectable everywhere: besides the numbered coins, every chip in the click-through bar selects its step (and jumps playback there), and clicking the caption line selects the step being shown — so steps that have no edge at all are reachable too. The step form edits the whole step contract: the caption, lane, and link sit above chip rows for the step’s hops, lit nodes, and panel patches — each chip removes with one click (hop shapes normalize automatically between edge, edges, and edgeless), clicking a panel chip opens that patch’s JSON in a mini editor, and the selected step’s members carry distinct markers on the board (dashed outlines on nodes and panels; edges brighten their halo in the protocol color — no CSS filter, so every skin shows it). ADD TO STEP turns board clicks into membership toggles: while the mode is on, clicking any edge, node, or panel in the section adds it to the step (or removes it if already there), each toggle is one undo step, and Esc or DONE ends the mode. Tabs are managed in place: clicking a tab button switches to it AND selects it — the form edits its label and highlight, + tab inserts a ready-made sibling after it, arrows reorder it, delete removes it (the last tab of a block is refused with instructions instead). Hosted template collections: when the workbench is served from a web host, it fetches starters.json from beside the page (same directory as workbench/flowspec.html) and shows those entries in the welcome screen’s Start new project picker alongside the curated built-ins. The file is a JSON array (or a {"starters": [...]} wrapper) of {"name": ..., "spec": {...}} entries with an optional desc; malformed entries are skipped and counted rather than hiding the rest. Edit the file and reload the page to grow the collection — no rebuild. See workbench/starters.example.json for the shape.

Embedding one diagram in an iframe

To target a named view, use #d=&v=; step and embed links retain the selected view. For example, page.html#embed=front-door&v=home-story embeds that Home arrangement. See view selectors for legacy Home/Data flow aliases and their distinction from host profiles.

Any published page understands #embed= in its URL: it renders ONLY that section’s diagram, panels, and step controls — no page title, tabs, prose, or contract card — sized for an iframe (Confluence, wikis, dashboards). The ref is the section’s heading slug (the same vocabulary the deep-link d= selector and the section- element ids use) or its 1-based rendered number. &sk= picks the skin for the embed. A section living inside a tab is revealed automatically.


The flag composes with the other hash fields and is read once at load; everything else about the page (deep links, skins, the step player) works unchanged without it.

For a hosted HTML file embedded in Confluence, export the page from the updated workbench or regenerate it with tools/page_build.py, then replace the hosted file. Exported HTML embeds its spec, runtime, icons, styles and licensed fonts for every skin: after the document loads it needs no font CDN or spec fetch. Previously exported files do not update automatically. An explicit ?spec=... URL still asks the standalone bootloader to load that external spec when no spec is embedded; use an exported/injected page for a self-contained embed. The HTML host’s own login, CSP and iframe permissions remain deployment requirements.

For a portable picture instead of a reader, use Export → Download SVG or Copy SVG. SVG export is deliberately diagram-only: it takes the active section’s live rendered graph in its current View/step and skin, but omits prose, panels, playback controls and Workbench selection/editing chrome. The result has its styles and referenced icon symbols embedded and does not fetch page assets.

Validating a spec (the agent loop)

From a source checkout, build once after checkout/update before calling the validator or compatibility CLI. Downloaded folder-agent kits already include it.

python3 tools/build.py --runtime-only
node tools/validate.js my-page.spec.json          # errors + warnings + lint, field paths
node tools/validate.js --quiet my-page.spec.json  # errors + summary only
node tools/validate.js consumer.spec.json provider.spec.json  # resolve shared topology in memory, then check both

An authoring agent’s loop is: emit JSON → run the CLI → fix what it names → inject. Lint findings are advisory layout heuristics (label longer than its edge, crowded row corridors, unused declared protocols, 3+ rows); they never block rendering.

Pass every authored provider needed by an importing spec in the same invocation. The CLI never writes a flattened snapshot: diagnostics remain associated with the input filenames, and missing providers, broken exports, duplicate identities and import cycles fail with topology-specific errors.

Comparing spec revisions

tools/spec_diff.py reports the contract and diagram changes that matter in a review, without treating a difference as an error:

python3 tools/spec_diff.py old.spec.json new.spec.json
python3 tools/spec_diff.py old.spec.json new.spec.json --annotate review.spec.json
python3 tools/spec_diff.py old.spec.json new.spec.json --quiet

Contract cards are matched by their full section-heading/card-title identity; duplicate cards are disambiguated by shared row keys, and their rows are matched by k. Annotation marks new rows added, changed v/g rows changed, and inserts a copy of each disappeared old row with removed so the rendered contract card shows the break. Step-count changes, panel/node IDs, and page.generatedFrom.version are report-only. The input specs are never changed.

For the normal build loop, add --diff-prev:

python3 tools/page_build.py path/to/name.spec.json --diff-prev

In source-adjacent mode there is intentionally no prior destination copy: after every successful build the tool records the built state in a hidden flat ..page-build-prev.json snapshot beside the page. That snapshot seeds the next --diff-prev; a first-ever build has no comparison yet. Flat --root and nested builds instead compare with the prior destination spec copy before replacing it. The comparison prints after PAGE_BUILD OK, is advisory, and never annotates the authored or copied spec.

Comparing Confluence page versions

tools/confluence_diff.py fetches historical storage-format versions of one page and prints a unified diff of stable, readable text. Set CONFLUENCE_BASE to the site base and CONFLUENCE_TOKEN to an API or personal-access token. Set CONFLUENCE_EMAIL as well for Cloud basic authentication; with a token alone, the tool uses bearer authentication. A full Cloud page URL can supply the base and page id itself.

[email protected] CONFLUENCE_TOKEN=example-api-token \
  python3 tools/confluence_diff.py \
  https://example.atlassian.net/wiki/spaces/WD/pages/123456/Widget-Design-Doc --list

CONFLUENCE_BASE=https://example.atlassian.net/wiki \
[email protected] CONFLUENCE_TOKEN=example-api-token \
  python3 tools/confluence_diff.py 123456 --versions 7 9 --out widget-design.diff

The default comparison uses the two newest versions. Agents should read the diff output instead of loading both full documents; unchanged regions are intentionally omitted. --context N widens the unchanged lines shown around each change (default 3), and --raw diffs the storage XHTML verbatim instead of the readable text extraction.

No-credential file mode. When another tool already fetched the two versions — typically a Confluence MCP server available to the agent — save each version to a file and diff the files instead; no environment variables, tokens, or network involved:

# each file is either the raw storage XHTML or a saved JSON API/MCP response
# (body and version metadata are extracted from JSON automatically)
python3 tools/confluence_diff.py --files widget-v7.json widget-v9.json

--versions A B labels the sides when the files carry no version metadata.

Surgically patching a Confluence page

tools/confluence_patch.py makes one exact substring replacement in Confluence storage XHTML without parsing and re-serializing the document. Its trust model is mechanical: the anchor must be unique, every write is followed by a verification diff, and every live write prints an exact restore command.

Live mode fetches, patches, updates, and re-fetches the page:

[email protected] CONFLUENCE_TOKEN=example-api-token \
  python3 tools/confluence_patch.py \
  https://example.atlassian.net/wiki/spaces/WD/pages/123456/Widget-Design-Doc \
  --find-file widget-old-anchor.xhtml --replace-file widget-new-anchor.xhtml \
  --message "paired change WD-42"

Use file mode when an MCP tool supplied a saved JSON response or raw storage XHTML. The output is only the patched storage XHTML, ready for that tool to push:

python3 tools/confluence_patch.py --file widget-page.json \
  --out widget-page-patched.xhtml \
  --find "Old widget rule" --replace "New widget rule"

Add --dry-run in either mode to print the intended verification diff without writing a file or sending a PUT:

python3 tools/confluence_patch.py --file widget-page.json \
  --out widget-page-patched.xhtml --find-file old.xhtml \
  --replace-file new.xhtml --dry-run

--restore (live mode only, no find/replace) republishes that historical version’s body as a new version — the undo for a bad patch; every live patch also prints the exact restore command for its own undo.

Exporting a GIF

Export every step of the first stepped diagram, or select any stepped diagram by its 1-based rendered section number:

python3 tools/export_gif.py built-page.html
python3 tools/export_gif.py built-page.html --section 2 --width 1280 \
    --delay-ms 1600 --out walkthrough.gif
python3 tools/export_gif.py built-page.html --section 2 --out gifs/
python3 tools/export_gif.py built-page.html --section 2 --view home-story --out home.gif
python3 tools/export_gif.py built-page.html --skin daylight --dim-alpha 0.35
python3 tools/export_gif.py built-page.html --chrome /path/to/chrome

The GIF lands beside the page by default, named after it — with the section reference appended when --section picked one, so exporting several sections of one page never overwrites. --view also appends -view- to the derived name so captures of different views remain separate. --out takes an exact .gif path, or a directory (existing, or marked by a trailing /) to receive the derived name.

The tool locates Chrome/Chromium from --chrome, $CHROME, or common macOS and Linux install paths, opens each canonical heading-slug #d=delivery-flow&m=step&s=… deep link headlessly, and captures one frame per step clipped to the diagram itself — the board with its legend, the widget panels, and the step bar — with a --margin background border (default 16 px). Without --view, custom and named layouts use the page’s authored default view: the Home map, visible panels and attached or detached step controls are captured together in their arranged positions. Hidden tiles stay hidden and do not enlarge the crop. Use --view to select a named view, or home, flow, or layout for the corresponding legacy presentation. An explicit view exports its visible steps in authored path order, with shared stops once and alternate stops on their own paths. Unknown views and older HTML that cannot apply the selector fail clearly. See view links and captures. Section headings, prose, and whatever follows the diagram stay out of frame, and a diagram taller than the viewport is captured in full. --width still sets the layout viewport width the page renders at, and --scale (default 2) renders each CSS pixel as that many device pixels, so text in the GIF stays sharp without changing the layout. --skin renders with that theme instead of the page’s own default — the name is validated against the page’s skin list (window.dvSkins), so an unknown token fails with the valid names. --dim-alpha overrides the step-mode non-highlighted opacity with one uniform value; it sets the page’s --dv-dim CSS variable, which every dimmed step-mode element (edges, labels, nodes, coins) reads in place of its per-element default. The --margin background around the diagram is always the theme’s page ground color: section boxes tint their background with the section accent, so without a repaint two sections of one page would export with two different background colors — the exporter paints the captured section with the rendered page ground before every frame. It uses Pillow when installed and otherwise falls back to its bundled PNG reader and pure-Python GIF encoder, so the page itself remains zero-dependency and the exporter works with bare Python 3. Duplicate step ids or ids that collide with a positional fallback make the exporter use collision-safe positional references for every frame in that diagram; the success output calls out when this fallback was needed. Animated GIF is the format that renders inline in Confluence.

The --section CLI selector remains the 1-based rendered section number, while the generated d fragment uses that section’s canonical heading slug (or the rendered index when it has no heading), so later diagrams in the same tab are exportable without ambiguity. Duplicate heading slugs gain document-order suffixes (flow, flow-2, flow-3). The fallback GIF encoder uses a fixed 256-color palette and favors correctness over file size; install Pillow for better color reduction and smaller output. The complete composable fragment grammar and its backward-compatible legacy forms are documented under “Viewer features you get for free” in contract/authoring-contract.md.

Running the test suites locally (same invocations CI uses):

python3 -m unittest discover -s tests -v
node --test tests/*.test.js

Serving a spec without injection

An http(s)-served copy of template/flowview.html whose JSON block still holds the built-in demo also accepts ?spec=relative/path.json and fetches the spec from the adjacent file. file:// pages cannot fetch — for a page that opens anywhere, injection (tools/inject.py) stays the distribution mode.

Site integration: theming

Rendered pages read the dv_skin cookie on load, ahead of the spec’s skin default, and expose the six valid tokens as window.dvSkins. The consuming site owns the picker and persistence; the engine never writes cookies or uses localStorage. A same-origin host dropdown can be wired without hardcoding:

const select = document.querySelector('#design-theme');
for (const skin of window.dvSkins) select.add(new Option(skin, skin));
select.addEventListener('change', () => {
  document.cookie = `dv_skin=${select.value}; Path=/; SameSite=Lax`;
  window.dvSetSkin(select.value);
});

When the page sits in an iframe (the shell cannot reach window.dvSetSkin directly), post a message instead — every built page listens for it:

frame.contentWindow.postMessage({ type: 'dv_skin', skin: select.value }, '*');

Unknown skin names are ignored; the message changes styling only, so no origin check is applied on the receiving side.

An iframe shell can register its canonical page URL as the rendered page’s link base. Copy-link icons then compose the page fragment onto that shell URL, and subsequent in-page navigation is mirrored back to the shell. The page sends no fragment data until a valid dv_linkbase handshake, and replies only to the origin that sent it.

This complete shell example restores the iframe from the flat fragment grammar, registers https://example.test/#p=, and keeps the shell address bar in sync:


The skin channel can share this same load handler: send its dv_skin message beside dv_linkbase. A same-origin integration may instead call frame.contentWindow.dvSetLinkBase(linkBase); invalid, relative, and non-HTTP(S) bases are rejected.

Known limits / next steps

  • Layout auto-cleanup (label de-collision, parallel-edge offsets) is heuristic; the lint rules in tools/validate.js flag what it cannot save (labels far longer than their edge, heavily crowded corridors). Manual bend / labelDx / labelDy nudges remain available and documented.
  • Group members must be placed adjacently (one stack column works best); the boundary is a bounding box, not a layout constraint.
  • Spec comparison annotates contract rows for the existing delta badges; it does not produce a side-by-side whole-page visual diff.

examples/doorbell/ is the northstar acceptance proof: the two-chip doorbell page — containment group, WAKE_INT interconnect, five synchronized inspector panels including the viewfinder REC moment — generated entirely from doorbell.spec.json.

View this README on GitHub

推荐工具

换一个关键词,或者移除筛选条件。

安装

npx skillfish add reghartner/design-viz