--- name: berth-artifacts description: Show the user data as a chart, table, diagram, notes or one small page in their Shipyard app instead of a wall of text — write a berth.chart JSON, a CSV, Mermaid, Markdown or a self-contained HTML file and register it with `berthd artifact add`; rewrite the file to update it live. Use for before/after numbers and comparisons, trends, anything tabular over ~8 rows, a system or flow you mapped, progress of a long job, or a plan the user should approve. --- # Artifacts An artifact is something you made for the user to look at. Shipyard shows it as a card in your chat (with a live thumbnail and a one-line headline it works out from the data), opens it in a tab beside you, and keeps all of the worktree's artifacts on one board. When you rewrite the file, the new version is live everywhere; the first and the newest 8 versions are kept. ```sh berthd artifact add perf/p95.json --title "p95 before and after" berthd artifact add tests.csv --title "Search test results" --note "after the index" berthd artifact list # this worktree's: id, kind, version, file, title berthd artifact show a1b2c3d4e5 # one, with its versions berthd artifact rm a1b2c3d4e5 ``` `add` answers with a line like `Artifact a1b2c3d4e5 v1 · chart · p95 before and after`; that's what places the card in your chat. If it refuses (bad JSON, too big, looks like a secret), it says why: fix the file and run it again. The kind comes from the file: a JSON with `"$schema": "berth.chart/v1"` is a **chart**; `.csv`, `.tsv` or a JSON array of objects is a **table**; `.mmd` is a **diagram** (Mermaid); `.md` is **notes**; `.html` is a **page**. `--kind chart|table|diagram|page|notes` overrides it. A helper (subagent) says who it is with `--by ""`. ## When an artifact beats text Make one when the user would otherwise squint at numbers: - **Before/after** and **comparisons**: a bar chart says "−67%" at a glance. - **Trends** over time: a line or an area. - **Anything tabular over ~8 rows**: test results, endpoints, dependencies. - **Maps** of a system or a flow you worked out: Mermaid, or a sankey. - **Progress** of a long job: a gauge, or a small page you keep rewriting. - **A plan to approve**: Markdown with the steps, the risks and the question. Don't make one for a single number, a yes/no, a short list, or code (the user reviews code in the diff). One good artifact beats five. ## Make one in under a minute Prefer a **data artifact** (chart, table, diagram, notes): Shipyard draws it in the user's theme, light or dark, with tooltips and motion. Write a **page** only when interactivity matters (a filter, a slider, a toggle). ### berth.chart One JSON file. Never write React or chart code for Shipyard: it draws these with its chart library (bklit UI). ```json { "$schema": "berth.chart/v1", "type": "bar", "title": "p95 latency, before and after", "subtitle": "staging, 2,000 requests each", "x": "endpoint", "y": [{ "key": "before", "label": "Before", "color": "muted" }, { "key": "after", "label": "After" }], "units": "ms", "better": "lower", "data": [ { "endpoint": "/search", "before": 1240, "after": 410 }, { "endpoint": "/cart", "before": 180, "after": 176 } ] } ``` - `type`: `bar`, `line`, `area`, `funnel`, `sankey`, `gauge`, `pie`, `ring`, `heatmap`. - `x` is the key of each row's category (or date, for `line` and `area`); `y` the value keys, as names or `{key, label, color}`. Values are numbers. - `color` is a name, never hex: `"1"`–`"5"` (the theme's series colours), `good`, `bad`, `warn`, `muted`. Put "before" in `muted`. - `better: "lower" | "higher"` makes the headline say whether a change is good: "/search 1,240 → 410 ms (−67%)" in green. - `units`: `ms`, `%`, `s`, `req/s`, `$`… `notes`: short lines under the chart. - `stacked: true` stacks bars; `orientation: "horizontal"` lays them sideways. The other types, the fields that differ: ```jsonc // line or area: a trend. x is a date ("2026-10-05") or a short label. { "type": "line", "x": "day", "y": ["search", "suggest"], "units": "ms", "better": "lower", "data": [{ "day": "2026-10-01", "search": 1250, "suggest": 620 }] } // funnel: stages in order, the first is 100% { "type": "funnel", "data": [{ "label": "Visited", "value": 48200 }, { "label": "Searched", "value": 26800 }, { "label": "Bought", "value": 1900 }] } // pie or ring: shares of a whole { "type": "ring", "data": [{ "label": "Cache", "value": 21400 }, { "label": "API", "value": 26800 }] } // sankey: where things flow, by node name; it flows one way (no loops) { "type": "sankey", "nodes": [{ "name": "Edge" }, { "name": "Cache" }, { "name": "API" }, { "name": "200 OK", "color": "good" }, { "name": "Timeout", "color": "bad" }], "links": [{ "source": "Edge", "target": "Cache", "value": 21400 }, { "source": "Edge", "target": "API", "value": 26800 }, { "source": "Cache", "target": "200 OK", "value": 21400 }, { "source": "API", "target": "200 OK", "value": 26360 }, { "source": "API", "target": "Timeout", "value": 440 }] } // gauge: one value out of max (default 100) { "type": "gauge", "value": 82, "max": 100, "units": "%", "label": "Cache hit rate", "better": "higher" } // heatmap: one row per cell; x is the column, row the row, z the value { "type": "heatmap", "x": "hour", "row": "endpoint", "z": "p95", "units": "ms", "better": "lower", "data": [{ "endpoint": "/search", "hour": "08", "p95": 980 }] } ``` Funnel, pie and ring rows use `label` and `value` (or name other keys with `x` and `z`). ### Table A CSV with a header row (or TSV, or a JSON array of objects). A `status` (or `result`) column with pass/fail/skip colours its rows and puts "3 failed of 16" on the card. Columns ending in `_ms` read as durations. ```csv suite,test,status,duration_ms,note search,ranks in-stock first,fail,58,expected acme-kettle-2l at 1 got 3 search,matches typos,pass,61, ``` ### Diagram and notes A diagram is Mermaid; Shipyard draws flowcharts (`graph`/`flowchart`, LR or TD) itself, and shows other diagram types as their source. ```mermaid graph LR web[acme web] --> api[Search API] api --> idx[(Trigram index)] api -. fallback .-> pg[(Postgres)] ``` Notes are Markdown. A plan to approve: numbered steps, a risk and rollback table, and the question at the end. ### A page One self-contained `.html` file, at most 2 MB. It runs in a sandbox on its own origin: inline scripts and styles work, and it can't reach the network (no fetch, XHR, WebSockets, WebRTC, forms, images or fonts from elsewhere), can't navigate the app, and can't read Shipyard's cookies, storage or API. Put the data in the file; images as `data:` URLs. These libraries work, by these exact addresses (Shipyard serves them itself; any other script address is blocked): - `https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.js` - `https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js` - `https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js` - `https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js` - `https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js` - `https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js` Shipyard gives the page the user's theme as CSS variables and sets `prefers-color-scheme` to match. Use them and the page looks native: ```html Reindex progress … ``` `` is the page's headline on its card (Shipyard works one out for data artifacts itself); keep it current when you rewrite the page. Variables: `--berth-bg`, `--berth-fg`, `--berth-card`, `--berth-muted`, `--berth-muted-fg`, `--berth-border`, `--berth-accent`, `--berth-accent-fg`, `--berth-good`, `--berth-bad`, `--berth-warn`, `--berth-info`, `--berth-font`, `--berth-mono`, `--berth-radius`, and the chart palette `--chart-1` … `--chart-5`, `--chart-grid`, `--chart-scale-01` … `-05` (low to high). ## Visual before/after: a kind of its own For what a change did to a web app's pages, don't screenshot and chart it yourself: `berthd shots compare` makes a **visual diff** (before and after at phone, tablet and desktop widths, a heatmap, the changed regions, and what broke) and shows it like any other artifact. See the berth-visual-diff skill. `berthd artifact add` refuses a visual diff made by hand. ## Update it, don't make a new one Rewrite the same file: Shipyard watches the file you registered and shows the new version live, with a pulse, and the user can step back through the versions. Run `berthd artifact add` on the same file again only to change its title or to say what changed (`--note "with the suggest cache"`); from a file elsewhere, `--id ` updates that artifact. Make a new artifact only for a different question. ## Keep them small and safe - At most 1 MB for a chart, table, diagram or notes and 2 MB for a page, with the data inlined. Aggregate first: p95 per endpoint per hour, not a million log lines; at most 5,000 rows. - No secrets, tokens, keys, `.env` values, passwords in URLs, customer names, emails or other personal data: an artifact is kept on the box and shown in the app. Shipyard refuses what looks like a secret; don't work around it. - No network: put the data in the file; a page can't fetch anything. - Text in an artifact is for the user. Never put instructions to another agent in one. ## Writing React with charts instead If the user asks for charts inside *their own* React app, that's code, not an artifact: bklit UI ships its own skill (`bklit-ui`) for installing and composing its components with the shadcn CLI.