--- name: claude-dashboard description: "Use this for a local, data-backed dashboard, metrics page, KPI tracker, scorecard, data visualization or recurring report that opens in the bundled live editor: a page or scroll story of charts, numbers and tables over datasets (a CSV/TSV/JSON file the user has, or data you gather and attach), with a Sources panel that shows where every number comes from. Read it when creating, filling, refreshing or revising one. You write the datasets, then the page as plain HTML, into the dashboard's own store with this skill's script. Not for a static HTML mockup or a picture of a dashboard." license: "Mixed. The skill text, references and editors adapted from Anthropic's claude.ai Artifact types stay Anthropic's and are not MIT-licensed; the local tooling is MIT. Complete terms in NOTICE.md: https://github.com/JimLiu/baoyu-design/blob/main/NOTICE.md" --- # Dashboard — the shared type A dashboard is a page you write over **datasets** the type loads, attributes and refreshes. Everything is a document in its own store, written with `write-db`. `SKILL.md`, `artifact-type/` and `scripts/` are the type's own files: leave them as they are. What the person asks for is what you build; the defaults apply only to what they left unsaid. Whatever the page's own code can draw or do is allowed: any chart, control or interaction. A chart they named is the one you draw, by hand where `d3` has no helper; if you draw another, say which and why. ## The calls A dashboard is a folder, ``: `designs//` in the current project unless the person names another (``: the title in lowercase words joined by `-`; a title not in Latin letters, such as a Chinese one, takes a short English rendering of it: `x-account-analytics`). The first `write-db` or `upload` creates it. One script, Node 22 or later with nothing to install, does every call; below, `A` is `/scripts/artifact.mjs`. | Command | Does | |---|---| | `node $A write-db ` | Writes documents: `set`, `update`, `delete`. All or nothing. | | `node $A read-db [[/]] [--text]` | Lists every document, a collection's, or one (`--text` prints its `text` raw). | | `node $A upload [--name n]` | Attaches a CSV, TSV or JSON file: `{"url": "/_blob/", "bytes", "contentType", …}`. | | `node $A delete-asset ` | Removes an attached file no document names any more. | | `node $A check ` | Checks the documents, the page's files, and that each dataset mark's `data-where`/`data-row` finds its rows. Exit 1 while `problems` > 0. | | `node $A open [--port n] [--lan]` | Starts the editor in the background and returns `{"url", "pid", "stop", …}`. All the dashboards open on this computer share one server and port (4740, the next free port up when that is taken; `--port n` looks for it at n and starts from n, `--port 0` runs a server of its own on any port); `url` is this dashboard's own page, whose path, `/dashboard//`, stays the same from one start to the next. Run again, it reuses the one running. `--lan`: also on the local network (see Sharing). `--agent-hook ""`: see Comments. `NETWORK_BLOCKED` from `open` means your commands run in a sandbox without local network. Run `open`, `share` and `comments --wait` with local network allowed, outside the sandbox, asking for that permission if your commands need approval; in the sandbox `comments --wait` says `NO_SERVER` even while the page is open. Commands that only read and write files work in the sandbox. | | `node $A serve [--port n] [--lan]` / `stop ` | The same server in the foreground / takes this folder off the server, which ends with the last one. | | `node $A share ` | Opens it to the local network and prints a link per role; `--disable` / `--enable` / `--reset `, `--role =`, `--off`. See Sharing. | | `node $A comments [--all]` / `--wait` | The comments people left on the page / waits for one they send you. See Comments. | | `node $A comment-reply ` / `comment-resolve ` | Answers one (`--resolve` closes it too) / closes one (`--reopen`). | | `node $A archive [--out file.zip\|dir]` | The whole folder, history and comments too, as one zip beside it (`--out`: another file or folder), to back it up, move it to another computer or send it to someone: `{file, files, bytes, open}`. Unzipped anywhere, `open` carries on. The owner's Download folder button in the page does the same; the server's own files and the share links stay out. | - **A batch** is a JSON array (or `{"writes": [...]}`) of `{"op": "set" | "update" | "delete", "path": "/", "data": {...}}` (`collection` and `doc_id` in place of `path` work too; `file_path`, a JSON file, in place of `data`). Up to 50 writes and 1 MB, each document once; every write is checked before any is written. `-` reads the batch from stdin. From a shell heredoc, quote its word (`<<'EOF'`): unquoted, the shell expands `$file` and any other `$` or backtick in the batch. Or write the batch to a file and pass its path. - **A file's text without escaping**: `{"$file": ""}` anywhere in `data` becomes that file's text, so the page is `{"op": "set", "path": "files/index.html", "data": {"text": {"$file": "designs//src/index.html"}}}`. Paths are relative to the current directory. Keep your working copy in `/src/`; the store is what the page shows (`read-db files/index.html --text` prints it). - `update` merges fields at any depth; `null` (or `{"__delete__": true}`) removes a key; an array or a value replaces. `set` writes the whole document; `delete` removes it. - **On disk**: `/db//.json`, one JSON file a document (`db/dash/meta.json`, `db/datasets/.json`, `db/files/index.html.json`); attached files at `/_blobs/` (`id` the first 32 hex of the file's sha256), each with `.json` beside it (name, type, size); `.artifact-server.json` while the editor runs and its log `.artifact-server.log` (it stays after `stop`): leave both; so is `comments.json` (the comments, see Comments), which only the comment commands change. `db/data/users/local/runs.json` is the page's own (The runs document; `data/users/me` in a path reads as `data/users/local` here). - **The open editor** watches `db/`: a write, by these commands or by hand, shows on the page within a moment, with no reload; what the page writes lands in `db/`. - A refused call prints `{"error": "", "message": "…"}` and exits 2; nothing was written. **Reading the references locally** — where this file says to read `artifact-type/reference/.md`, read `/artifact-type/reference/.md`. They are the type's own, word for word, so they name claude.ai's calls: `write_db` (`db_op: "batch"`) is `write-db`, `read_db` is `read-db` (collection `data/users/me`, doc `runs` is `read-db data/users/me/runs`; doc `check` is what `check ` shows as `pageCheck`), `upload_asset` is `upload`, `delete_asset` is `delete-asset`, ArtifactComments `reply` / `resolve` is `comment-reply` / `comment-resolve`. What they say of live queries, connectors, loaders, certified queries, sharing, `request` and `lastAttempt`, routines, drafts, the voice that reads a page aloud and the view context doesn't apply here. **Not here**: connectors (so no live query), the view context, other readers and their access, routines, drafts and Remix, a voice to read the page aloud (`data-dash-read` stays unset), publishing (the folder is the dashboard), and the Anthropic fonts (`--font-anthropic-sans` and `--font-anthropic-serif` fall back to system faces). ## The documents Paths are `collection/doc_id`. Dataset ids are `[A-Za-z0-9_-]`, 1–64 characters; a file's id is its name (`index.html`, no folders). The store refuses any document over 256 KB. If `write-db` refuses a batch, its message names the write and why: fix that one; don't retry it unchanged. ### `dash/meta` — `{title}` What the user calls it, not "Dashboard". Write it first (first in the batch). A new title later changes only this; the folder keeps its name. ### `datasets/` — where numbers come from `{title, description?, source, updated?, schedule?, staleAfterHours?, fixed?}`. `title`: a short name that stands alone in a list of sources, two to five words, ~30 characters ("Signups vs quarter target", not "To target"). `description`: a sentence on what this source is. A file's ends with where its rows came from ("Exported from the shop admin on Oct 28.", "From the 2025 annual report, p. 14."). A file over 8 MB is refused. The attached files are plain files in the folder (`_blobs/`): anyone given the folder can read them; say so once when you attach one. Every dataset here is an attached file: the local editor has no connectors, so a live query (`kind: "query"`) never loads, and `check` refuses one. Data that changes over time is gathered again when the person asks for it to be refreshed (Changing and refreshing). Get the rows yourself — the person's file, an export, a query you run with your own tools, figures you gathered and can cite — and save them as CSV or JSON: only the columns and rows the page needs, aggregated where you can. **An attached file** — `source: {kind: "file", url, name, format?}`: CSV, TSV or JSON up to 8 MB. `upload` it; `url` is the reply's `url` as it came (`/_blob/`; an external one isn't read), `name` the file's own, `format` (`"csv"`, `"tsv"`, `"json"`) only when the extension doesn't say. JSON is an array of row objects, or one under `rows`/`data`/`results`; a CSV's first row names the columns. Cells arrive as text, a nested object as JSON: convert (`Number(x)`, `day.slice(0, 10)`, `JSON.parse`) first. A date not in ISO form (`Thu, Oct 29, 2026`) is parsed as UTC, `d3.utcParse('%a, %b %d, %Y')(day)` with the format fitted to the cells, never `new Date(day)`: that is local midnight, which `d3.utcFormat` shows as the day before east of UTC. - `updated: {at, by}` on every file write: `at` the ISO time now, `by` who (the person's name, else your own); `check` counts it missing as a problem, and the page would show a warning. - A file that stays current: `schedule` (in words, how it is refreshed and how often) and `staleAfterHours` (about twice the cadence), past which it shows as stale. - `fixed: true` when the data should stay as it is (a period that has ended, sent by someone else, they said so). ### `files/` — `{text}`, the page The page is `files/index.html` (a `