# Elementy a druhy karet (https://docs.vitrinka.ai/cs/reference/elements)



{/* Code generated by tools/gen-docs-elements from internal/element and internal/boarddocs. DO NOT EDIT. Regenerate: go run ./tools/gen-docs-elements */}

Reference slouží pro `compose_board`, HTTP endpoint pro skládání a dokumenty publikované přes `vitrinka board push`. Každé téma popisuje formát dat a podporované použití; karty nástěnky a elementy dokumentů nejsou zaměnitelné. Názvy a popisy přebíráme přímo ze serverové dokumentace, proto zůstávají anglicky. Stejný obsah vrací `docs {topic}` přes MCP nebo `vitrinka docs <topic>`.

## Druhy elementů [#elements]

### `chart` — data visualization — series charts, proportions, KPI stats, timelines, heatmaps [#chart]

One charting language for boards, docs and artifacts. Pick a viz, then fill the data slot that viz reads: data.series (line|area|bars|scatter), data.items (pie|donut|bars|stat|timeline|flow|matrix) or data.x/y/values (heatmap). Series points are numbers or \[x, y] pairs; scatter is pairs only. Bulk data may live behind the envelope's dataRef instead of inline — compose inline, the server spills past the threshold. Tone is explicit meaning, never inferred from a number's sign. Three optional upgrades: data.refs draws reference lines (a constant value or one point per x) over line|area|bars|scatter; data.calendar turns a heatmap into a calendar of day values (week columns × weekday rows); data.rank turns bars items into ranked share bars with an "other" row. viz:"stat" is a single-stat CHART (board-viz register); the sibling `stat` KIND is the document register's KPI strip — prefer the kind for headline rows. viz:"compare" is the before/after benchmark: data.items\[\{label, before, after, note?, tone?}] with data.better ("lower" | "higher", REQUIRED — it declares what an improvement is), data.unit and the optional series names data.before / data.after (default Before / After). Each row paints a muted before bar and an accent after bar on one shared scale plus a delta chip (percent change · ratio, e.g. −83% · 5.9×): an item's tone wins, else moving the better way is good, the worse way bad, unchanged neutral — e.g. \{"viz":"compare","title":"API latency, p95","data":\{"unit":"ms","better":"lower","items":\[\{"label":"GET /slots","before":820,"after":140}]}}. A chart that mixes metrics gives an item its own unit — items\[].unit ("KB", "%") overrides data.unit for that row's values — and bars share a scale only within one unit: each unit's rows scale to that unit's largest value, never against another unit's.

| Vlastnost | Typ                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Poznámky                                                                |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| `viz`     | `"line" \| "area" \| "bars" \| "scatter" \| "pie" \| "donut" \| "heatmap" \| "stat" \| "matrix" \| "flow" \| "timeline" \| "compare"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | *povinné* which visualization to render; decides the required data slot |
| `title`   | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | heading rendered above the chart                                        |
| `caption` | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | mono caption under the chart; inline markup allowed                     |
| `data`    | `{ series?: ({ label?: string; points?: (number \| [number, number])[]; values?: (number \| [number, number])[] })[]; items?: ({ label?: string; value?: string \| number \| boolean \| null; display?: string; delta?: string \| number \| boolean \| null; unit?: string; tone?: "good" \| "warn" \| "bad" \| "neutral"; when?: string; x?: number; y?: number; spark?: number[]; before?: number; after?: number; note?: string })[]; x?: (string \| number)[]; y?: (string \| number)[]; values?: number[][]; min?: number; max?: number; stack?: boolean; unit?: string; refs?: ({ label?: string; value?: number; points?: number[] })[]; calendar?: { from: string; values: number[] }; rank?: number; better?: "lower" \| "higher"; before?: string; after?: string }` | the viz's data — replaceable wholesale by the envelope's dataRef        |

Příklad:

```json
{"kind":"chart","payload":{"viz":"line","title":"Weekly actives","data":{"x":["W1","W2","W3"],"series":[{"label":"web","points":[120,180,240]},{"label":"cli","points":[40,90,160]}]}}}
```

### `chips` — meta chip row — small key·value facts in quiet pills [#chips]

items are \{k, v} pairs rendered as outlined pills in order — counts, spans, versions, owners. Facts, not prose: keep both sides short.

| Vlastnost | Typ                            | Poznámky                      |
| --------- | ------------------------------ | ----------------------------- |
| `items`   | `({ k: string; v: string })[]` | *povinné* the chips, in order |

Příklad:

```json
{"kind":"chips","payload":{"items":[{"k":"journeys","v":"9"},{"k":"window","v":"Jun 4 – Aug 28"}]}}
```

### `code` — code listing — syntax-highlighted source with an optional caption [#code]

source is the code text verbatim; lang picks the highlighter grammar (omit to autodetect). caption renders as a small line under the block.

| Vlastnost | Typ      | Poznámky                                                     |
| --------- | -------- | ------------------------------------------------------------ |
| `source`  | `string` | *povinné* the code text, verbatim                            |
| `lang`    | `string` | highlighter language id (go, ts, sql, …); omit to autodetect |
| `caption` | `string` | small caption under the listing                              |

Příklad:

```json
{"kind":"code","payload":{"lang":"go","source":"func main() {\n\tfmt.Println(\"hello\")\n}","caption":"the whole program"}}
```

### `columns` — columns — 2–3 side-by-side columns of nested elements; collapses when narrow [#columns]

cols is an ordered list of 2–3 columns, each \{elements} — full child elements validated recursively (the same nesting cap as doc). ratio sets the widths: 1:1 (default), 1:2, 2:1 for two columns, 1:1:1 for three; a ratio's part count must equal the column count. Below the renderer's narrow threshold the columns stack in order. Use it for a score strip beside its verdict or a meter beside its chart — not for prose, which reads best in one measure.

| Vlastnost | Typ                                  | Poznámky                                                                      |
| --------- | ------------------------------------ | ----------------------------------------------------------------------------- |
| `ratio`   | `"1:1" \| "1:2" \| "2:1" \| "1:1:1"` | column widths: 1:1 \| 1:2 \| 2:1 (two columns), 1:1:1 (three); default 1:1    |
| `cols`    | `({ elements: Element[] })[]`        | *povinné* the columns, left to right — 2 or 3, each with at least one element |

Příklad:

```json
{"kind":"columns","payload":{"ratio":"2:1","cols":[{"elements":[{"kind":"stat","payload":{"items":[{"label":"score","value":"86 %","tone":"good"}]}}]},{"elements":[{"kind":"verdict","payload":{"state":"Recommend with reservations","tone":"warn"}}]}]}}
```

### `compare` — side-by-side comparison — two labeled columns of nested elements [#compare]

Two sides, a and b, each \{label, elements}. The sides render as columns; each side's elements are full child elements (prose, chart, table, …), validated recursively. Label both sides — the reader can't tell unlabeled columns apart.

| Vlastnost | Typ                                       | Poznámky                              |
| --------- | ----------------------------------------- | ------------------------------------- |
| `a`       | `{ label?: string; elements: Element[] }` | *povinné* the left side               |
| `b`       | `{ label?: string; elements: Element[] }` | *povinné* the right side              |
| `title`   | `string`                                  | heading rendered above the comparison |

Příklad:

```json
{"kind":"compare","payload":{"a":{"label":"Before","elements":[{"kind":"prose","payload":{"md":"Two painters."}}]},"b":{"label":"After","elements":[{"kind":"prose","payload":{"md":"One painter."}}]}}}
```

### `deploy` — deploy tile — what reached an environment, and whether it runs there now [#deploy]

The newest deploy to env (sha, at); count is how many landed in the period; live marks the one the environment runs now.

| Vlastnost | Typ       | Poznámky                          |
| --------- | --------- | --------------------------------- |
| `env`     | `string`  | *povinné* environment, e.g. prod  |
| `sha`     | `string`  | *povinné* deployed commit         |
| `at`      | `string`  | when it landed (RFC 3339)         |
| `count`   | `number`  | deploys in the period             |
| `live`    | `boolean` | the environment runs this one now |

Příklad:

```json
{"kind":"deploy","payload":{"env":"prod","sha":"3f9a1c2e","at":"2026-10-12T08:12:00Z","count":4,"live":true}}
```

### `diagram` — architecture diagram — semantic nodes/edges/lanes; the server computes all layout [#diagram]

Send SEMANTICS ONLY: nodes (typed: box|entity|endpoint|service|note), groups (nestable), swimlanes, edges (from/to by id or node.port), a stat strip and a legend. The server lays out every rectangle and route deterministically and stamps geo/overrides/metrics into the payload — those fields are server-owned output: accepted on round-trip, never authored. Tones/shapes/edge styles are an open vocabulary — unknown values degrade to defaults, so misspellings warn with a fix instead of blocking.

| Vlastnost     | Typ                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Poznámky                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dir`         | `"TD" \| "LR"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | flow direction: top-down (default) or left-right                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `kicker`      | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | small eyebrow line above the title                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `title`       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | the diagram's title — required unless a mermaid import supplies one                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `subtitle`    | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | supporting line under the title                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `caption`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | mono caption under the plate; inline markup allowed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `stats`       | `({ value: string; label: string })[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | headline stat strip above the content                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `nodes`       | `({ id?: string; type?: string; title: string; body?: string; chips?: ({ text: string; tone?: string })[]; icon?: string; shape?: string; tone?: string; ports?: ({ id: string; label?: string; side?: string; order?: number })[]; columns?: ({ name: string; type?: string; pk?: boolean; fk?: string; unique?: boolean; nullable?: boolean })[]; method?: string; path?: string; codes?: number[]; tag?: string; image?: string; replicas?: number; mappings?: ({ host: string; cont: string; proto?: string })[]; doc?: number; docAnchor?: string })[]`                                                                   | loose nodes (laneless diagrams)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `groups`      | `unknown[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | top-level groups \{id?, title, meta?, tone?, nodes?, groups?} — nest freely                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `lanes`       | `({ label: string; note?: string; nodes?: ({ id?: string; type?: string; title: string; body?: string; chips?: ({ text: string; tone?: string })[]; icon?: string; shape?: string; tone?: string; ports?: ({ id: string; label?: string; side?: string; order?: number })[]; columns?: ({ name: string; type?: string; pk?: boolean; fk?: string; unique?: boolean; nullable?: boolean })[]; method?: string; path?: string; codes?: number[]; tag?: string; image?: string; replicas?: number; mappings?: ({ host: string; cont: string; proto?: string })[]; doc?: number; docAnchor?: string })[]; groups?: unknown[] })[]` | swimlane bands; contain their members                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `edges`       | `({ from: string; to: string; style?: string; label?: string; card?: string })[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | connections between nodes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `legend`      | `({ style: string; label: string })[]`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | explains the edge styles in use                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `footer`      | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | footnote under the diagram                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `quality`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | per-WRITE strict tier ("showcase" fails on composition findings) — consumed at write time, never stored                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `mermaid`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | mermaid source to IMPORT when the payload has no nodes, groups or lanes: the server converts it at write time, then lays out as usual. Types: flowchart/graph (subgraphs become groups), sequenceDiagram (participants in one lane; alt/else, opt, loop, par/and, critical/option and break blocks prefix their message labels, e.g. "\[alt intake disabled] save") and stateDiagram/stateDiagram-v2 (\[\*] becomes Start/End, a composite state block a group, notes skipped). Any other type (class, ER, gantt, pie) is refused with the supported list; rewrite it as a flowchart or author nodes/edges |
| `source`      | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | living-source pointer (\{kind, ref, rev}) — refreshed server-side via POST /cards/\{id}/refresh                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `geo`         | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED layout output (boxes/ports/order/routes) — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `render`      | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED stamped theme renders (\{svgLight, svgDark}) — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `overrides`   | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED human ⊕ edit layer, survives re-layout — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `metrics`     | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED quality score output — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `repairs`     | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED mechanical-repair log — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `diagnostics` | `unknown`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | SERVER-OWNED scored findings — never author it                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

Příklad:

```json
{"kind":"diagram","payload":{"title":"Ingest path","dir":"LR","nodes":[{"id":"cli","title":"CLI","type":"box"},{"id":"api","title":"API","type":"service"}],"edges":[{"from":"cli","to":"api","label":"push"}]}}
```

### `doc` — composite document — an element composing child elements, the artifact register [#doc]

elements is an ordered list of full child elements (prose, chart, table, diagram, compare, …), validated recursively — findings carry the nested path (payload.elements\[2].payload…). direction seeds the design register — release is the release-notes register (chapter-style area sections, KPI tiles, a gradient display title); accentColor — a named accent or a \{light, dark} pair of #rrggbb hexes — is the document's own colour, stored as the pair; without it the workspace accent applies. A pushed doc.json IS this element, the same shape a board composes; nesting caps at 6 levels: flatten instead of matryoshka-ing docs in docs. One document can serve several readers: any element (a doc.json block likewise) may carry an envelope audience — 1–3 of public|technical|internal, beside kind and payload — and the viewer's audience switch and the exports (?audience=) show an element only to the audiences it names; untagged means everyone, a table row may carry its own (\{cells, audience}), and a container (doc, section, columns, compare, gallery, a table's rows) whose children are all filtered out disappears with them.

| Vlastnost     | Typ                                                                                                                       | Poznámky                                                                                                                                                                    |
| ------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`       | `string`                                                                                                                  | the document's title                                                                                                                                                        |
| `kicker`      | `string`                                                                                                                  | small eyebrow line above the title                                                                                                                                          |
| `direction`   | `"editorial" \| "dossier" \| "terminal" \| "gallery" \| "release"`                                                        | the design register the renderer leans into                                                                                                                                 |
| `typeset`     | `"house" \| "editorial" \| "bookish" \| "swiss" \| "gallery" \| "hyperlegible"`                                           | the document's voice: its default typeset of four faces (Heading · Body · Data · Code) — a reader's own pick wins on their screen; absent = the board's typeset, else house |
| `accent`      | `string`                                                                                                                  | a phrase of the title the display type highlights                                                                                                                           |
| `intro`       | `string`                                                                                                                  | the lede under the title — inline markdown                                                                                                                                  |
| `chips`       | `({ k: string; v: string })[]`                                                                                            | quiet key · value facts under the lede — window, branch, status                                                                                                             |
| `accentColor` | `"red" \| "coral" \| "amber" \| "olive" \| "teal" \| "azure" \| "violet" \| "magenta" \| { light: string; dark: string }` | the document's accent: a named accent or a \{light, dark} pair — the push stores the pair; absent = the workspace accent                                                    |
| `project`     | `string`                                                                                                                  | the project the document reports on — with no accentColor the set's raw page hashes it to one of the named accents (the card keeps the workspace accent)                    |
| `sources`     | `{ [key: string]: { repo: string; ref: string } }`                                                                        | repository aliases a link target may cite as alias:path#L3-L9 — a push expands each to its GitHub URL and verifies it when the deployment has a GitHub credential           |
| `elements`    | `Element[]`                                                                                                               | *povinné* the document's content, in order — full child elements validated recursively                                                                                      |

Příklad:

```json
{"kind":"doc","payload":{"title":"Weekly readout","direction":"editorial","elements":[{"kind":"prose","payload":{"md":"## Shipped\n\nThe element contract."}},{"kind":"stat","payload":{"items":[{"label":"PRs","value":4}]}}]}}
```

### `epic` — feature tile — a feature with its phase, progress and result screenshot [#epic]

A feature as a person reads it: its result (title), the phase it reached (planned · built · verified · shipped — History's palette), done/total of its work and a cover screenshot of the result. live pulses it amber while someone works on it; who names them. mark says why a timeline shows it on a week.

| Vlastnost | Typ                                                                     | Poznámky                                       |
| --------- | ----------------------------------------------------------------------- | ---------------------------------------------- |
| `title`   | `string`                                                                | *povinné* the feature's result, else its title |
| `phase`   | `"planned" \| "built" \| "verified" \| "shipped"`                       | phase reached; default planned                 |
| `mark`    | `"built" \| "verified" \| "shipped" \| "building" \| "due" \| "pinned"` | why a timeline shows it on a week              |
| `done`    | `number`                                                                | work children done                             |
| `total`   | `number`                                                                | work children in all                           |
| `cover`   | `string`                                                                | result screenshot URL                          |
| `live`    | `boolean`                                                               | someone works on it right now                  |
| `who`     | `string[]`                                                              | who works on it right now                      |
| `id`      | `number`                                                                | task id                                        |
| `url`     | `string`                                                                | task URL as the server printed it              |
| `page`    | `string`                                                                | feature page URL                               |
| `type`    | `string`                                                                | task type word                                 |
| `at`      | `string`                                                                | when it reached the mark (RFC 3339)            |

Příklad:

```json
{"kind":"epic","payload":{"title":"Checkout works offline","phase":"built","done":5,"total":8,"cover":"https://app.vitrinka.ai/w/acme/uploads/qa/checkout.png"}}
```

### `figure` — image figure — a picture with caption and alt text [#figure]

Reference the image by src (URL), ref (a blob/upload reference) or shot (an index into the owning set's data.json shots — only meaningful inside a doc minted from a semantic push). caption renders under the figure; alt is the accessible description — write it for a reader who can't see the image.

| Vlastnost | Typ      | Poznámky                                                    |
| --------- | -------- | ----------------------------------------------------------- |
| `src`     | `string` | image URL                                                   |
| `ref`     | `string` | blob/upload reference (server-resolved)                     |
| `shot`    | `number` | index into the owning set's data.json shots (doc-card form) |
| `caption` | `string` | caption rendered under the figure                           |
| `alt`     | `string` | accessible description of the image                         |

Příklad:

```json
{"kind":"figure","payload":{"src":"/uploads/flow.png","caption":"The ingest flow","alt":"Flowchart: CLI pushes to the API, which fans out to layout and search"}}
```

### `finding` — one review finding — severity rail, locator, cause/cost, trace, fix and evidence receipt [#finding]

sev (high|medium|low) colors the left rail; tag is a free category ("perf", "a11y"); status is the verification chip (verified|confirmed|fixed read green, suspected|likely|open amber, refuted red, wont-fix gray). title, where (a mono locator: file:line, route, query), body, cause and cost cards, summary (the one bold conclusion), fix (one line) and trace (before/after resolution) are all optional. evidence is the mono receipt footer — one line or a list; a finding without evidence reads as an opinion (warned).

| Vlastnost  | Typ                                                                                                             | Poznámky                                                                                           |
| ---------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `sev`      | `"high" \| "medium" \| "low"`                                                                                   | severity: high\|medium\|low — colors the rail                                                      |
| `tag`      | `string`                                                                                                        | free-text category chip ("perf", "a11y", "race")                                                   |
| `status`   | `string`                                                                                                        | verification state chip: verified\|confirmed\|fixed · suspected\|likely\|open · refuted · wont-fix |
| `title`    | `string`                                                                                                        | one-line finding title                                                                             |
| `where`    | `string`                                                                                                        | mono locator: file:line, route, query                                                              |
| `body`     | `string`                                                                                                        | body prose; blank lines split paragraphs                                                           |
| `cause`    | `string`                                                                                                        | what produced the defect — a labeled "cause" card                                                  |
| `summary`  | `string`                                                                                                        | the one bold conclusion sentence                                                                   |
| `cost`     | `string`                                                                                                        | what the defect costs (time, ms, money) — a labeled "cost" card                                    |
| `fix`      | `string`                                                                                                        | the fix, one line                                                                                  |
| `trace`    | `{ before: { key: string; dest: string; note?: string }; after: { key: string; dest: string; note?: string } }` | before/after resolution trace                                                                      |
| `evidence` | `string \| string[]`                                                                                            | the receipt footer — one line or a list of lines citing the exact log, file, query or check        |

Příklad:

```json
{"kind":"finding","payload":{"sev":"high","tag":"perf","status":"fixed","title":"Cart cache never invalidates after a coupon edit","where":"internal/cart/cache.go:141","cause":"The coupon write path bypasses cart.Invalidate.","fix":"Route the write through the repository.","evidence":["cart_cache_miss_total{path=\"coupon\"} 1.0 → 0.02"]}}
```

### `gallery` — image gallery — 1–24 images as a grid, bento mosaic, masonry wall or carousel [#gallery]

items are 1–24 images, each referenced like a figure: src (URL), ref (a blob/upload reference) or shot (an index into the owning set's data.json shots — only inside a doc minted from a semantic push); alt is the accessible description (a missing one warns) and caption a line of inline markdown under the image (links render). layout picks the arrangement: grid (default) — uniform 2–3 columns of 16:10 tiles; bento — a 6-column mosaic, the first item large, other spans by count, span (wide|tall|large, bento only) overriding an item's tile; masonry — 2–3 columns at each image's natural aspect; carousel — one slide at a time with prev/next and position dots. Every layout prints and exports as a 2-column grid of ALL items, so a carousel never hides a slide on paper. Clicking an image opens the lightbox stepping through the gallery. An item may carry its own audience (like an element's) — a gallery left with no item for the reader disappears. Use a figure for one image.

| Vlastnost | Typ                                                                                                                                                                           | Poznámky                                                                                        |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `layout`  | `"grid" \| "bento" \| "masonry" \| "carousel"`                                                                                                                                | grid (default) \| bento \| masonry \| carousel — the arrangement; the painter owns the geometry |
| `caption` | `string`                                                                                                                                                                      | caption under the whole gallery; inline markup allowed                                          |
| `items`   | `({ src?: string; ref?: string; shot?: number; alt?: string; caption?: string; span?: "wide" \| "tall" \| "large"; audience?: ("public" \| "technical" \| "internal")[] })[]` | *povinné* the images, in order — 1 to 24, each with src, ref or shot like a figure              |

Příklad:

```json
{"kind":"gallery","payload":{"layout":"bento","caption":"The new booking flow","items":[{"src":"/uploads/slots.png","alt":"Slot picker with three free times","caption":"Pick a slot"},{"src":"/uploads/cart.png","alt":"Cart with one booking"},{"src":"/uploads/paid.png","alt":"Payment confirmation","span":"wide"}]}}
```

### `goal` — goal tile — a sprint's outcome with its dates and progress [#goal]

A goal (sprint) as a person reads it: title, the outcome it promises, its status word and its work done of total.

| Vlastnost | Typ                                             | Poznámky                     |
| --------- | ----------------------------------------------- | ---------------------------- |
| `title`   | `string`                                        | *povinné* the goal's name    |
| `outcome` | `string`                                        | the outcome it promises      |
| `status`  | `"running" \| "overdue" \| "planned" \| "done"` | status word; default planned |
| `starts`  | `string`                                        | start (RFC 3339)             |
| `ends`    | `string`                                        | end (RFC 3339)               |
| `percent` | `number`                                        | work done, 0-100             |
| `done`    | `number`                                        | work done                    |
| `total`   | `number`                                        | work in all                  |
| `url`     | `string`                                        | the goal's card              |

Příklad:

```json
{"kind":"goal","payload":{"title":"Offline checkout","outcome":"A cashier can sell without a network","status":"running","percent":62,"done":13,"total":21}}
```

### `html` — custom HTML island — sandboxed escape hatch when no hard component fits [#html]

Provide src (a complete inline HTML document) or files (a flat path → content map that must include index.html); a pushed file set's index.html arrives as href, written by the server, never authored. The island renders in the sandboxed iframe with the unified injector stack — theme sync, size beacon, capture, wheel chaining — so it behaves like a native face. The card takes the document's natural size — any width, 1:1 up to 1600px, wider scales down — so a desktop or multi-phone mock is as welcome as a widget; width/height pin the box instead of measuring. Reach for a hard component first: an island is opaque to search, distillation and in-place editing.

| Vlastnost | Typ                         | Poznámky                                                                                |
| --------- | --------------------------- | --------------------------------------------------------------------------------------- |
| `src`     | `string`                    | a complete HTML document, inline                                                        |
| `files`   | `{ [key: string]: string }` | flat path → content map; must include index.html                                        |
| `href`    | `string`                    | served document URL (a pushed set's raw index.html) — written by the push, not authored |
| `title`   | `string`                    | the island's label (the face's kicker and the frame's accessible name)                  |
| `width`   | `number`                    | pinned document width in px (omit to auto-measure)                                      |
| `height`  | `number`                    | pinned render height in px (omit to auto-measure)                                       |
| `natW`    | `number`                    | measured document width in px — written by the board's size beacon, not authored        |
| `natH`    | `number`                    | measured document height in px — written by the board's size beacon, not authored       |

Příklad:

```json
{"kind":"html","payload":{"src":"<!doctype html><html><body><h1>Custom island</h1></body></html>"}}
```

### `list` — marked list — bullet, check or numbered lines [#list]

items are the lines (inline markup allowed); style picks the marker: bullet (em-dash, default) | check (green ✓) | number (mono decimal). Prose markdown lists are for running text; this kind is a standalone checklist or ordered plan.

| Vlastnost | Typ                               | Poznámky                                             |
| --------- | --------------------------------- | ---------------------------------------------------- |
| `style`   | `"bullet" \| "check" \| "number"` | marker style: bullet (default) \| check \| number    |
| `items`   | `string[]`                        | *povinné* the lines, in order; inline markup allowed |

Příklad:

```json
{"kind":"list","payload":{"style":"check","items":["Cache fix merged and verified in prod","Alert threshold lowered to 380 ms"]}}
```

### `media` — media file — video, audio, PDF or download, by URL or blob ref [#media]

Reference the file by src (URL) or ref (a blob/upload reference); mime hints the renderer when the URL doesn't say. PDFs embed as live documents, video/audio get players, anything else renders as a download chip. alt describes the content accessibly.

| Vlastnost | Typ      | Poznámky                                      |
| --------- | -------- | --------------------------------------------- |
| `src`     | `string` | file URL                                      |
| `ref`     | `string` | blob/upload reference (server-resolved)       |
| `mime`    | `string` | MIME type hint when the URL doesn't reveal it |
| `caption` | `string` | caption rendered under the media              |
| `alt`     | `string` | accessible description of the content         |

Příklad:

```json
{"kind":"media","payload":{"ref":"blob:demo-walkthrough","mime":"video/mp4","caption":"The 90-second walkthrough"}}
```

### `meter` — meters — labeled value/max bars with optional thresholds [#meter]

Each item draws value against max (the scale is the point — max is mandatory and positive). label and a numeric value are required; threshold draws a tick; tone states explicit meaning (good|warn|bad|neutral), never inferred from the numbers.

| Vlastnost | Typ                                                                                                                                                     | Poznámky                           |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `items`   | `({ label?: string; value?: number; max?: number; threshold?: number; unit?: string; note?: string; tone?: "good" \| "warn" \| "bad" \| "neutral" })[]` | *povinné* the meters, one bar each |
| `title`   | `string`                                                                                                                                                | heading rendered above the meters  |

Příklad:

```json
{"kind":"meter","payload":{"items":[{"label":"Blob storage","value":41,"max":100,"threshold":80,"unit":"GB","tone":"good"}]}}
```

### `milestone` — milestone tile — a dated mark, reached or still ahead [#milestone]

due is the date it is set for; reached, once set, is when it was met.

| Vlastnost | Typ      | Poznámky                       |
| --------- | -------- | ------------------------------ |
| `title`   | `string` | *povinné* the milestone's name |
| `due`     | `string` | due date (RFC 3339)            |
| `reached` | `string` | when it was reached (RFC 3339) |

Příklad:

```json
{"kind":"milestone","payload":{"title":"Public beta","due":"2026-11-02T00:00:00Z"}}
```

### `mockup` — UI mockup — frames of primitive trees; sketch screens cheaply, edit by direct manipulation [#mockup]

Frames hold primitive trees rendered top-to-bottom (stacks nest row/col layout). The vocabulary is deliberately lo-fi — sketching a proposed screen costs \~100-250 tokens, and the armed editor moves/resizes/restyles primitives directly. Style tokens (palette/radius/font) tint the whole mockup. A board wireframe payload maps in as one frame. Vectors and component variants are a later initiative.

| Vlastnost | Typ                                                                                                                                                                                                     | Poznámky                              |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `frames`  | `({ name: string; w?: number; h?: number; tree: ({ t: string; label?: string; primary?: boolean; h?: string \| number; n?: number; dir?: "row" \| "col"; name?: string; children?: unknown[] })[] })[]` | *povinné* the screens, side by side   |
| `tokens`  | `{ palette?: string; radius?: number; font?: string }`                                                                                                                                                  | style tokens tinting the whole mockup |

Příklad:

```json
{"kind":"mockup","payload":{"frames":[{"name":"phone","tree":[{"t":"nav","label":"Inbox"},{"t":"list","n":4},{"t":"button","label":"Compose","primary":true}]}]}}
```

### `page` — document page — markdown truth with a collaborative editor mirror [#page]

md is the durable truth and the only field an agent writes. The editor keeps its own ProseMirror mirror (pm) with pmStale mediating — both are server-owned: accepted on round-trip, never authored. source \{kind:"mdfile", ref, rev?} ties the page to a repo file the push path refreshes.

| Vlastnost | Typ                                           | Poznámky                                                     |
| --------- | --------------------------------------------- | ------------------------------------------------------------ |
| `md`      | `string`                                      | *povinné* the page's markdown — the document's durable truth |
| `source`  | `{ kind: string; ref: string; rev?: string }` | repo-file tie; the push path refreshes md from it            |
| `pm`      | `unknown`                                     | SERVER-OWNED ProseMirror mirror — never author it            |
| `pmStale` | `boolean`                                     | SERVER-OWNED mirror-freshness flag — never author it         |

Příklad:

```json
{"kind":"page","payload":{"md":"# Element contract\n\nOne vocabulary, every surface."}}
```

### `prose` — markdown text — headings, lists, links, icons, inline math [#prose]

Markdown is the truth format. The read face renders the shared inline vocabulary (links, \[!icon name], wikilinks, $math$, ==highlight== for a marked phrase — no == inside it); write characters directly (→, never →). Headings get stable anchor slugs.

| Vlastnost | Typ      | Poznámky                      |
| --------- | -------- | ----------------------------- |
| `md`      | `string` | *povinné* the markdown source |

Příklad:

```json
{"kind":"prose","payload":{"md":"## Findings\n\n- The cache **never** invalidates\n- See [[element-contract]]"}}
```

### `ref` — ref — a reference to another document card: link chip, preview or full embed; the server stamps the target and draws the edge [#ref]

Exactly one of card (a card id on this workspace), page (a slug of a sibling page in the same push batch; the server resolves it to a card id and rewrites the ref) or section (the id of a section in THIS document — an in-page anchor link, rejected at push when no section carries that id) is required. mode is link (default) | preview | embed; a section ref is always a link. label overrides the chip text. target is SERVER-OWNED: the resolved card's summary (id, kind, title, kicker, board) stamped on write and refreshed on the target's change — never author it; an authored value is overwritten.

| Vlastnost | Typ                              | Poznámky                                                                                          |
| --------- | -------------------------------- | ------------------------------------------------------------------------------------------------- |
| `card`    | `number`                         | the target card id (this workspace)                                                               |
| `page`    | `string`                         | a sibling page slug inside the same push batch — resolved server-side to card                     |
| `section` | `string`                         | a section id in this document — renders as an in-page anchor link                                 |
| `mode`    | `"link" \| "preview" \| "embed"` | link (inline chip, default) \| preview (the target's face at LOD height) \| embed (the full face) |
| `label`   | `string`                         | chip text override; default is the target's title                                                 |
| `target`  | `unknown`                        | SERVER-OWNED resolved target summary (\{id, kind, title, kicker, board, …}) — never author it     |

Příklad:

```json
{"kind":"ref","payload":{"card":22351,"mode":"preview"}}
```

### `release` — release tile — a cut release, or the open one gathering what ships next [#release]

status cut is a release that went out (at = its cut); next is the open release and its merged PR count.

| Vlastnost | Typ               | Poznámky                                   |
| --------- | ----------------- | ------------------------------------------ |
| `title`   | `string`          | *povinné* the release's name               |
| `status`  | `"next" \| "cut"` | cut, or the open next release; default cut |
| `prs`     | `number`          | merged pull requests in it                 |
| `at`      | `string`          | when it was cut (RFC 3339)                 |
| `url`     | `string`          | the release board                          |

Příklad:

```json
{"kind":"release","payload":{"title":"Release 14","status":"cut","prs":23,"at":"2026-10-09T15:00:00Z"}}
```

### `section` — titled section — eyebrow label, title, note and nested child elements [#section]

label is the mono uppercase eyebrow ("01 · Findings"), title the display heading (inline markup allowed), note the muted line under it. elements are full child elements validated recursively — the same 6-level nesting cap as doc. An empty section renders as a bare heading (warned). Every section is addressable: id is its anchor (the \<section> element's id, so `#<id>` deep-links and `{kind:"ref", section:"<id>"}` cross-references land on it). Author it explicitly (^\[a-z0-9]\[a-z0-9-]\{0,63}$) or leave it out — canon derives it from the label (falling back to the title) with SectionSlug — diacritics folded to ASCII ("02 · Pozvánka" → 02-pozvanka) — suffixing -2, -3 on collisions in document order, so an unchanged label keeps its anchor across pushes.

| Vlastnost  | Typ         | Poznámky                                                                                       |
| ---------- | ----------- | ---------------------------------------------------------------------------------------------- |
| `id`       | `string`    | the section's anchor id — authored (lowercase, digits, hyphens; ≤64) or derived from the label |
| `label`    | `string`    | mono uppercase eyebrow label ("01 · Findings")                                                 |
| `title`    | `string`    | display-type section title; inline markup allowed                                              |
| `note`     | `string`    | muted note line under the title                                                                |
| `elements` | `Element[]` | *povinné* the section body, in order — full child elements validated recursively               |

Příklad:

```json
{"kind":"section","payload":{"label":"01 · Findings","title":"What broke, and what it cost","elements":[{"kind":"prose","payload":{"md":"Two regressions, one fixed in-flight."}}]}}
```

### `stat` — KPI stat strip — headline numbers with tones, deltas and sparklines [#stat]

3-6 stats read as headlines; more reads as a table (warned). label and value are required; value is any scalar (put formatted text in display when the raw value drives nothing); tone states MEANING (good|warn|bad|neutral) and is never inferred from the sign; spark draws an inline sparkline from plain numbers. Distinct from chart viz:"stat", which is a single-stat chart in the board-viz register — this kind is the document register's KPI strip.

| Vlastnost | Typ                                                                                                                                                                                                 | Poznámky                           |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `items`   | `({ label?: string; value?: string \| number \| boolean \| null; display?: string; delta?: string \| number \| boolean \| null; note?: string; unit?: string; tone?: string; spark?: number[] })[]` | *povinné* the stats, left to right |
| `title`   | `string`                                                                                                                                                                                            | heading rendered above the strip   |

Příklad:

```json
{"kind":"stat","payload":{"items":[{"label":"Weekly actives","value":1240,"delta":180,"tone":"good","spark":[720,890,1060,1240]}]}}
```

### `table` — typed-column data table — filter/sort/group views, aggregates, in-place editing [#table]

Typed columns carry the cell vocabulary (text|number|date|select|check|link); rows are objects keyed by column key. A cell is a scalar, or \{v, href?, tone?}: tone when its meaning needs stating (good|warn|bad|neutral — explicit, never sign-inferred), href when the cell is a link (https, mailto, #section-id, /path or a doc.json meta.sources alias — the cell renders as an anchor whose text is v). String cells render inline markdown exactly like prose and list items (\*\*bold\*\*, `code`, [text](url)). Saved views hold filter/sort/group state; aggregates render in the footer. Bulk rows may live behind the envelope's dataRef. The formula column type is reserved for a future formula engine — the validator names it, so don't emit it yet. A row written for some readers only wraps its cells: \{cells: \{…keyed…}, audience: \[public|technical|internal, …]} (a doc.json row: \{cells: \[cell, …], audience}) — the viewer's audience switch and the exports drop it for everyone else, like an element's audience; a table left with no row for the reader disappears, and a bare row shows to every audience.

| Vlastnost    | Typ                                                                                                                                                                                                                                                                                                                                                                                                            | Poznámky                                                                                                                                                         |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`      | `string`                                                                                                                                                                                                                                                                                                                                                                                                       | heading rendered above the table                                                                                                                                 |
| `caption`    | `string`                                                                                                                                                                                                                                                                                                                                                                                                       | mono caption under the table; inline markup allowed                                                                                                              |
| `cols`       | `({ key: string; label?: string; type?: "text" \| "number" \| "date" \| "select" \| "check" \| "link"; options?: string[]; width?: number })[]`                                                                                                                                                                                                                                                                | *povinné* the column definitions, in display order                                                                                                               |
| `rows`       | `({ [key: string]: string \| number \| boolean \| null \| { v: string \| number \| boolean \| null; href?: string; tone?: "good" \| "warn" \| "bad" \| "neutral" } } \| { cells: { [key: string]: string \| number \| boolean \| null \| { v: string \| number \| boolean \| null; href?: string; tone?: "good" \| "warn" \| "bad" \| "neutral" } }; audience: ("public" \| "technical" \| "internal")[] })[]` | row objects keyed by column key; a cell is a scalar or \{v, href?, tone?}; sparse rows are fine; a row for some readers only is \{cells: \{…}, audience: \[...]} |
| `views`      | `({ name?: string; filter?: ({ col: string; op: "eq" \| "ne" \| "lt" \| "gt" \| "contains" \| "empty" \| "nonempty"; value?: string \| number \| boolean \| null })[]; sort?: ({ col: string; dir?: "asc" \| "desc" })[]; group?: string })[]`                                                                                                                                                                 | saved filter/sort/group views                                                                                                                                    |
| `aggregates` | `{ [key: string]: "sum" \| "avg" \| "count" }`                                                                                                                                                                                                                                                                                                                                                                 | footer aggregates, column key → function                                                                                                                         |

Příklad:

```json
{"kind":"table","payload":{"cols":[{"key":"task","label":"Task"},{"key":"pts","label":"Points","type":"number"},{"key":"state","type":"select","options":["todo","doing","done"]}],"rows":[{"task":"Design the contract","pts":3,"state":"done"}],"aggregates":{"pts":"sum"}}}
```

### `timeline` — timeline — dated cards along a spine, each with status and evidence [#timeline]

items are the dated cards in story order: when (the date/time label) and title are the spine; status is a free chip (done|good|warn|bad, or the finding vocabulary); text is the body, summary the one bold line; evidence is the receipt — one line or a list. axis picks the form: spine (default, vertical, any when label) or time (a proportional horizontal axis; needs parseable dates, falls back to spine otherwise).

| Vlastnost | Typ                                                                                                                      | Poznámky                                                                                                                                                                              |
| --------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`   | `({ when?: string; title?: string; status?: string; text?: string; summary?: string; evidence?: string \| string[] })[]` | *povinné* the dated cards, in story order                                                                                                                                             |
| `title`   | `string`                                                                                                                 | heading rendered above the timeline                                                                                                                                                   |
| `axis`    | `"spine" \| "time"`                                                                                                      | spine (default): a vertical dated spine that takes any when label; time: a proportional horizontal time axis when every when parses as a date — falls back to spine when one does not |

Příklad:

```json
{"kind":"timeline","payload":{"items":[{"when":"Jul 18","title":"v3.5.2 regresses checkout p95","status":"bad","text":"Alert fires at 14:02 UTC.","evidence":"alertmanager: CheckoutP95High firing 2026-07-18T14:02Z"},{"when":"Aug 28","title":"v3.6 ships the fix","status":"good"}]}}
```

### `verdict` — the call — one line of state with its reasoning on a toned rail [#verdict]

state is the one-line call ("Ship it", "Hold the release"); text is the reasoning under it (blank lines split paragraphs). At least one of the two is required. tone colors the rail: good|warn|bad|info (default info) — explicit meaning, never inferred. Put it where the reader decides: top or bottom of the document.

| Vlastnost | Typ                                   | Poznámky                                                   |
| --------- | ------------------------------------- | ---------------------------------------------------------- |
| `state`   | `string`                              | the one-line call (required unless text is given)          |
| `text`    | `string`                              | the reasoning under the call; blank lines split paragraphs |
| `tone`    | `"good" \| "warn" \| "bad" \| "info"` | rail color: good\|warn\|bad\|info (default info)           |

Příklad:

```json
{"kind":"verdict","payload":{"state":"Ship v3.6 with the cache fix","text":"The p95 regression is explained and fixed on main.","tone":"warn"}}
```

## Druhy karet nástěnky [#cards]

### `text` — A markdown card: the basic thought unit, with a role that styles it [#card-text]

Full GFM markdown: headings, emphasis, ordered/unordered/task lists, tables, blockquotes, --- rules, images, links, fenced code with language highlighting, $…$/$$…$$ katex math, and \[!icon \<name>] inline Lucide glyphs. Raw HTML renders as literal text; unsafe link/image schemes are neutralized. The same renderer serves callout md, doc-card leaves, diary and personal boards.

### `note` — A bare text note without markdown chrome [#card-note]

### `shape` — A colored geometric shape [#card-shape]

### `callout` — A toned emphasis block [#card-callout]

### `checklist` — An interactive checklist — users tick items live and state persists [#card-checklist]

### `cluster` — A grouping frame; untitled = invisible row/col group, pure flow intent [#card-cluster]

List member refs in children and they lay out inside it. \{title, role:"kanban"} renders a status column. An UNTITLED cluster renders no chrome — 'these three on one row' is \{kind:"cluster", payload:\{layout:\{mode:"row"}}, children:\[…]}. Use it liberally instead of letting cards stack into a column. Frames nest depth ≤5.

Viz také: `composing`

### `section` — A named journey frame: always visible, pill-navigable, the unit of scoping and iteration [#card-section]

Renders as a HEADED GROUP, not a box — kicker title plus optional one-or-two-line description (border only on hover/selection); give a description whenever the title alone doesn't orient a newcomer. Membership is spatial. Every tool scopes to sections; iterations are new sibling sections. A composed section lays its children out itself — the default is layout \{mode:"grid", cols:2}: children fill rows left→right in compose order and wrap every `cols` cards, so a figure+finding pair stays side by side only when cols is even and a document flow (prose, findings, verdicts) reads best at cols:1 or 2; layout:null hands placement back to you.

Viz také: `sections`

### `step` — A numbered journey step that embeds its screen — the canonical walkthrough shape [#card-step]

image names a file inside a set you already pushed (vitrinka board capture/push) — one steps-only section, no separate flow of shot cards; the server sizes the card to the screen's true aspect. cardId instead references an EXISTING board card for a live thumbnail. Use one or the other; never re-upload a screen you can reference.

### `compare` — Two existing cards' faces behind a wipe/ghost slider — iteration pixel-diff [#card-compare]

### `doc` — A fractal document in one card — child elements composed top-to-bottom, \~300 tokens for a dense readout [#card-doc]

Důležité poznámky:

* inner elements are pixels (not annotatable/wirable) — use real cards when structure matters
* the legacy \{nodes:\[\{t,…}]} grammar is retired (migrate-and-drop): stored node docs were canonicalized by the v95 sweep; new writes reject it

Viz také: `element:doc`

### `link` — A website card: server-unfurled metadata, screenshots, optional live embed [#card-link]

The server unfurls title/og:metadata/favicon async, probes iframe embeddability, and (with the capture sidecar) takes real 16:10 desktop + phone-viewport screenshots. Previews fetch public internet addresses only: a loopback, private or link-local target (the redirect chain included), or any link on an air-gapped install, keeps the bare clickable URL with the reason in payload.error (and in the reply's lint when the host itself is refused). preview = 16:10 face · phone = mobile frame · widget = compact favicon+title row · live = embedded iframe when the site allows framing.

### `board` — A portal card: a live minimap window into another board [#card-board]

Důležité poznámky:

* the target board must exist; self-portals 400

### `diagram` — An architecture page in one card: lanes, groups, typed nodes, ports, edges — server-laid-out [#card-diagram]

The agent sends semantics; the server computes ALL geometry (Sugiyama layout + orthogonal routing + ports) into payload.geo and the client scales it uniformly. Nodes take icon?: \<lucide-name>. Living sources (payload.source) refresh via POST /cards/\{id}/refresh; human overrides survive the refresh. Full vocabulary: the artifact skill's diagram reference.

Viz také: `api`

### `api` — An OpenAPI reference card: the whole raw spec, rendered as a browsable grouped-endpoints reference [#card-api]

Send the WHOLE spec object — no conversion, no summarizing. Must carry openapi/swagger + paths and re-marshal under 512 KB; title defaults to info.title. Complements the openapi→diagram importer (vitrinka board diagram import \<spec> --board \<slug>): the diagram face shows shape, the api face shows the endpoint contract — one spec, two faces.

Viz také: `diagram`

### `page` — A Confluence-grade living document card: markdown truth, block editor, repo-backed refresh [#card-page]

payload.md (markdown) is the durable agent-native truth; payload.pm is the human editor's ProseMirror mirror; the server never converts — it maintains pmStale. Agents write markdown; human block edits round-trip. #E\<elemNo> anchors deep-link. Full contract: the artifact skill's pages reference.

### `artifact` — A self-contained HTML bundle in a sandboxed iframe — the custom-HTML escape hatch [#card-artifact]

Send ONE shape, never both. html = full document (must start with \<!doctype or \<html>, ≤8 MiB; sandbox CSP blocks external network — no CDNs, no fetch/XHR; inline your assets). The pinned /vendor/… runtime shelf is the one exception: the vitrinka host serves React, Recharts and friends so a full document imports them instead of inlining. body+runtime "tw4" = markup only, server-wrapped with the vendored Tailwind v4 build — dramatically cheaper; prefer it whenever the design fits Tailwind utilities.

Důležité poznámky:

* prefer chart/table/mockup/doc first — an HTML artifact is the LAST resort, not the default

Viz také: `chart`, `mockup`, `uploads`

### `chat` — A multi-turn AI conversation as a thread of filled bubbles with forensic evidence [#card-chat]

Always send messages INLINE — the server persists them to a board data blob and stamps dataRef + a summary (never construct dataRef yourself). Under 1 MiB serialized; split longer runs. text is markdown-lite and survives verbatim — a transcript is evidence. references:\[cardId] draw bubble-anchored wires to existing cards. Transcripts are immutable — replace the whole payload via update\_cards. Importing beats composing: `vitrinka board import-chat --eve <sessionId>|--ndjson <file> --board <slug>` runs the pre-publish redaction gate (emails, keys, JWTs, IBANs) and refuses sensitive findings unless you choose --redact or --allow-unredacted. The chat-evidence template composes the readout layout.

Důležité poznámky:

* compose by hand only when you built the transcript yourself — imports must pass the redaction gate

## Průvodci skládáním [#guides]

### `composing` — How to place work on a board: batching, layout intent, frames, templates, questions, undo [#composing]

One batch per coherent unit — a section, a readout, a comparison — because placement and wiring are computed per batch; a single card is a batch of one. Anchor by relation: anchor \{cardId|ref, relation: after|before|under|beside|into} at batch level or per card (a card's own anchor overrides the batch's and may target a ref in the same call); after/before insert in reading order inside the anchor's container, under is the support row below (after, inside a managed layout), beside is a peer to the right, into appends to a frame; the receipt reports placedAs and container; an unsatisfiable relation is a 400 relation\_unsupported and nothing is placed. Never send coordinates; express intent (anchor, section, layout mode column|row|grid, order) and the server guarantees collision-free placement (occupancy scan) with geometry-true image aspect. Use `arrange` to tidy — choose the mode by meaning: graph follows document ref wires, journey follows flow wires, compare aligns two sections pairwise, row/grid/flow are for peers; scope it to what you placed (\{ids} or \{scope:"section:Name"}, gap/padding/cols knobs) and never reflow a shared board to fix your own addition; save:true persists a section layout (graph/journey refuse it). A receipt proves placement, not readable fit: read back what you placed and report unchecked fit honestly. Frames nest (depth ≤5) and any frame payload may carry layout \{mode: column|row|grid|flow|lanes|masonry, gap: S|M|L|px, cols, padding}; any card may carry payload.lane for the lanes mode. Anchored multiple-choice questions ride the same call — options carry \{label,hint,detail,recommended,cardId}; hint is the visible why (≤ 300 characters), detail the long rationale (markdown, ≤ 4000) folded behind "more", and a longer hint continues into detail instead of failing the batch (the receipt's lint names it); cardId wires the option to the card it names and answers arrive as staged choice items, echoing the picked option's hint/detail as `chosen`. Templates: get\_templates returns compose-ready skeletons (QA session, iteration review, decision map, metrics dashboard, presentation deck, finding triage, UI proposal, architecture map, data story, testing-journey suite, page, importers, docs board, chat evidence) — start from one instead of inventing structure. Presentation decks: sections are slides (one heading + 2-3 supporting cards each, arrange each with save:true; the user presents with shift-P). Every mutation is undoable per actor: POST /api/v1/boards/\{slug}/undo reverts YOUR last batch; the user's ⌘Z never touches your work. Everything you place lives under the board's ✦ eve toggle unless it is shared structure (section frames). OVER HTTP the same call is POST /api/v1/boards/\{slug}/compose with the envelope \{cards:\[\{kind, payload, ref?, children?:\[refs], anchor?}], edges?, questions?, section?, layout?, flow?, anchor?:\{cardId|ref, relation}} — a card may carry its own anchor; section (frame NAME) and layout ("column"|"row"|"grid") are batch-level strings, never card fields; a frame's own layout object lives in ITS payload.layout; a frame card lists its members as children:\[refs] of cards in the same batch. Every element kind except media and section is ALSO a standalone card (prose, stat, meter, list, timeline, chips, verdict, finding, …) — compose it top-level when it stands alone, or ride the same payload inside a doc element's payload.elements when several belong together as one readout (section is a doc block only; the board `section` is the journey frame). To read back what you placed: scrape is a token-economical digest (ids, kinds, sections, text summaries — never payloads); GET /api/v1/boards/\{slug} returns every card with its full payload. Edit or remove what you placed with PATCH / DELETE /api/v1/cards/\{id} (your own layer plus section frames).

Arguments in full (moved out of the tool schemas):

* `ask_board` — Ask the operator a multiple-choice (or kind:"text") question on a board, rendered as one-click pills, optionally anchored to a card. Answers are STAGED until ↑ Send releases the batch on the work wire (wait\_for\_work choices\[]); get \{kind: "questions"} is the durable record. Pass only real options — "Other" is added by the engine. Doctrine: docs topic "composing".
* `compose_board` — Place a BATCH of AI-authored content on a board — cards, frames, wires, anchored questions — one batch per coherent unit. The server owns placement: express intent (anchor \{cardId|ref, relation}, section, layout), never coordinates. All-or-nothing; a 400 carries \{code, index}. Contracts: docs \{topic: "composing"} and docs \{topic: "\<kind>"}.
* `update_cards` — Batch-edit board cards: PATCH payload/geometry on your OWN AI-layer cards and section frames, REMOVE cards (soft, restorable 30 days), RESTORE your trashed ones. Removing anyone else's card or a batch above the cap answers 409 needs\_confirm with a token and preview — repeat the same ids with confirm and a reason. A payload patch re-sizes a content-sized card (text, callout, checklist, finding, verdict, list, chips, timeline, doc, …) to its new content unless the same patch sets h or the card is user-sized. Never compose duplicates to fix a card.
* `ask_board · stakes` — What this decision constrains downstream — rendered under the question while answering and returned on the choice work item. Always pass it for decision-shaped questions.
* `ask_board · options` — 1-12 answer options — strings, or \{label, hint?, detail?, recommended?, cardId?} to carry tradeoffs (hint is the visible why under the label, ≤ 300 characters; detail the long rationale, markdown, ≤ 4000, folded behind "more"; recommended shows as ★). The "Other" escape is appended automatically.
* `ask_board · pivot` — This QUESTION is a pivot: its answer reshapes the rest of the map. Answers release only on the operator's ↑ Send / ↻ Regenerate — the flag marks the fork; nothing streams on its own.
* `compose_board · requestKey` — Stable identity of this logical composition. Retrying unchanged input returns the original receipt; changed input with the same key conflicts. Omit for a fresh operation.
* `compose_board · layout` — How the batch flows: omit for the default (aspect-packed shelf rows — the batch fills a screen-shaped 2D block, reading left→right in compose order), or force column (strict top-to-bottom), row, or grid (3 per row). USE THE FLOW VOCABULARY: for shelves of screenshots and readouts don't settle for one tall column — group cards that belong side-by-side in an UNTITLED cluster with layout \{mode:"row"|"grid", cols} (renders NO chrome — the invisible row/col group), give sections layout + an optional payload.description line, and pick a batch aspect that uses the canvas. A document flow (prose, findings, verdicts, figures with their finding) is the exception: one or two columns read, three clip — and a section grid wraps every `cols` cards in compose order, so keep pairs aligned to the column count
* `compose_board · aspect` — Target block aspect (w/h) for the default shelf pack: omit or 0 = 16:10 (1.6); clamped to \[1, 3.2]. Pick wider (2.2-3) for shallow wide narratives, squarer (1-1.3) for deep column-ish batches — the knob that spreads a batch across the canvas instead of one tall column
* `compose_board · flow` — Reading flow "document" makes the board read top-to-bottom like a chaptered document: this batch's top-level blocks stack in compose order (layout defaults to column; sections are the chapters, each still laying its members out inside), and the mode STICKS on the board — every later free batch starts a new row below instead of continuing shelf rows. The decision-map template sets it automatically. Use for brainstorms, decision logs and any board a human should READ in order; omit for spatial boards (screenshot shelves, journey maps).
* `compose_board · anchorCardId` — Alias for anchor \{cardId, relation: "beside"}: place the batch beside this existing card (omit both for free space beside the board)
* `compose_board · section` — Journey section name (see scrape\_board sections\[]): the batch lands INSIDE that frame, below its existing content, and the frame grows to fit. Mutually exclusive with journey and with a beside anchor; with a relation anchor it must name the anchor's own container.
* `compose_board · createSection` — Insist on creating `section` as a NEW frame. A missing section is created on demand on every board type; when the name is a near match of an existing section the call is refused with 409 section\_near\_match and a didYouMean list — re-run with the exact name, or set this to true.
* `compose_board · journey` — Pass-chain key ("checkout"): target a journey's ITERATION chain instead of a named section. Alone = the batch lands in the chain's LATEST pass (creates pass 1 if none — a plain section whose title matches is adopted as pass 1). This is THE way to present round 2/3 of a journey: one call opens the next pass and fills it.
* `compose_board · pass` — With journey: "next" CREATES the next pass section (auto-named "Checkout — pass N", placed beside pass N−1, layout inherited) and fills it in this same call; a number targets that existing pass. Omit = latest.
* `compose_board · template` — Registry template name (save\_template / get\_templates): its cards/edges/questions are instantiated FIRST, then any inline ones — one call renders the whole skeleton
* `compose_board · params` — Template \{\{slot}} substitutions, e.g. \{version: "2.4"}; unfilled slots render literally. The BUILT-IN template "decision-map" takes structured params instead: \{title, context, decisions: \[\{key, title, stakes, options:\[\{label,hint,detail?,recommended,pivot?}], multiSelect?, kind?:"text", pivot?}], extend?} — the server lays the whole map out as ONE OUTLINE DOCUMENT column (context strip + a one-line dotted-leader entry per decision; answering lives in the operator's docked question brief, the entry shows state + call). ALWAYS use it for brainstorm decision maps. Decisions whose answer reshapes the rest of the map take pivot:true (question-level, or per-option). \{extend:true} composes the entries INTO the existing map section named by title instead of creating a new heading+section — the way to grow ONE living map round by round, on the operator's ↑ Send / ↻ Regenerate, never off unreleased picks (the batch 404s cleanly if no such section exists).
* `compose_board · cards` — Any number of cards in one atomic batch (the 8 MiB body is the only bound). kind is the CLOSED vocabulary in the kind enum below — generated from the server's registry for THIS deploy; the server validates loudly (unknown kind → 400 with the allowed list; retired viz/wireframe → 400 naming the canonical kind; payload mismatch → 400 with \{code,index} and details). PAYLOAD CONTRACTS ARE NOT REPEATED HERE: docs \{topic:"\<kind>"} answers one kind's exact shape (docs with no topic = the index). Two anchors: text \{md: full GFM markdown, role?: idea|note|pro|con|risk|heading|caption, level?: 1|2|3 for headings}; every element kind (chart, table, diagram, page, stat, …) takes the canonical element payload the docs tool describes. Give a card a ref so edges/questions in the same batch can point at it.
* `compose_board · cards[].children` — Ordered child refs for a cluster/section. May include nested frames (max depth 5). Overrides cards array order.
* `compose_board · cards[].kind` — Card kind — one of the enum, which is generated from the server's registry (tools/gen-mcp-tools). Call docs \{topic:"\<kind>"} for the payload contract; a retired name (viz, wireframe) 400s naming its canonical replacement.
* `compose_board · cards[].anchor` — This card's own anchor, overriding the batch anchor: \{cardId | ref, relation}. ref targets a card of the SAME call (resolved in dependency order; a cycle 400s relation\_unsupported). Only on top-level cards — a child's place is its frame's children order.
* `compose_board · edges` — Wires between cards. from/to: a ref string from this batch, or an existing card id (number). Region anchors (journey signposts): fromRegion/toRegion pin the wire's end to a SPOT on the screen — the clicked element — in the card image's natural px (same space as annotation regions; measure with getBoundingClientRect at capture time, never eyeball). A screen with several outgoing region-anchored wires renders as a signpost: each wire leaves from its own outlined click-target. Tidy the whole flow afterwards with arrange \{mode:"journey"}.
* `compose_board · edges[].fromRegion` — \{x,y,w,h} in the from-card image's natural px: the clicked element the wire leaves from (outlined on the screen, glows with the wire)
* `compose_board · edges[].toRegion` — \{x,y,w,h} in the to-card image's natural px: what appeared / where the flow arrives (needs a card "to", not a point)
* `compose_board · questions` — Questions, optionally anchored to a batch card (cardRef) or existing card (cardId). Same staged-answer mechanics as ask\_board.
* `compose_board · questions[].stakes` — What this decision constrains downstream — rendered under the question while answering. Always pass it for decision-shaped questions.
* `compose_board · questions[].pivot` — Pivot question: its answer reshapes the rest of the map (live boards flush it to the wire immediately).
* `compose_board · anchor` — Place the batch IN RELATION to an existing card: \{cardId, relation}. after = joins the anchor's row right after it (same-row neighbours move right, never wraps); before = takes the anchor's spot, the anchor and its row move right; under = a support row below the anchor at its x (content below moves down); beside = right of the anchor in free space (the default); into = appended as the last child of the anchor FRAME (section/cluster; a non-frame anchor 400s relation\_unsupported). The anchor's container is the container: inside a managed section the relation becomes an insertion in its order (under reads as after there) and the frame reflows and grows. A multi-card batch follows the relation in order (card 2 lands after card 1). Each receipt entry reports placedAs and container.
* `update_cards · requestKey` — Stable identity of this atomic batch. Unchanged retries return the original receipt; changed input with this key conflicts. Omit for a fresh operation.
* `update_cards · remove` — Card ids to TRASH (soft delete — restorable). Free for your own cards (AI layer, section frames, cards you created); anyone else's, or more than 25 / 30% of the board, needs the confirm handshake
* `update_cards · reason` — Why these cards go — optional on your own cards, REQUIRED (≥ 20 characters, ≤ 1000) with confirm; shown to the operator in HISTORY and Trash
* `update_cards · confirm` — The token from a 409 needs\_confirm answer (rm\_…); repeat the exact same remove ids with it, plus reason
* `compose_board · questions[].options[].cardId` — Card this option names — the board draws a trace wire, hover glows it, picking flies there.
* `compose_board · questions[].options[].pivot` — This option is the pivot: picking it reshapes the rest of the map. Answers still release only on the operator's ↑ Send / ↻ Regenerate — the flag marks the fork; nothing streams on its own.
* `compose_board · questions[].options` — 1-12 options — strings or \{label, hint?, detail?, recommended?, cardId?} (the "Other" escape is appended automatically)
* `compose_board · questions[].kind` — "text" = free-text prompt (no options; the typed answer is the choice item's option).
* `compose_board · edges[].rel` — Typed canvas relationship; omitted means a plain flow arrow. Displayed alongside an optional label.
* `compose_board · edges[].from` — Card ref (string) or existing card id (number)
* `compose_board · edges[].to` — Card ref (string) or existing card id (number)
* `compose_board · cards[].payload` — Kind-specific payload — contracts in the vitrinka plugin skills (see cards description)
* `compose_board · cards[].anchor` — This card's own anchor \{cardId | ref, relation}, overriding the batch anchor; top-level cards only.
* `compose_board · cards[].children` — Ordered child refs for a cluster/section (nested frames, depth ≤5); overrides array order.
* `compose_board · cards[].ref` — Batch-local handle for edges/questions/children (e.g. "idea1")
* `compose_board · requestKey` — Stable identity of this composition: unchanged retries return the original receipt; changed input conflicts.
* `compose_board · layout` — Batch flow: omit for shelf rows, or force column, row or grid (3 per row).
* `compose_board · aspect` — Target block aspect (w/h) for the shelf pack: 0 = 1.6, clamped to \[1, 3.2].
* `compose_board · section` — Journey section name: the batch lands inside that frame; exclusive with journey and beside anchors.
* `compose_board · createSection` — true creates `section` as a new frame even beside a near match (409 section\_near\_match otherwise).
* `compose_board · pass` — With journey: "next" creates and fills the next pass section; a number targets that pass.
* `compose_board · template` — Registry template name: its cards/edges/questions are instantiated first, then the inline ones.
* `compose_board · flow` — "document" makes the board read top-to-bottom in compose order and sticks on the board.
* `compose_board · journey` — Pass-chain key ("checkout"): target a journey's iteration chain; alone = its latest pass.
* `compose_board · anchor` — shape \{cardId: number, relation?: after|before|under|beside|into} — Place the batch relative to a card: \{cardId, relation: after|before|under|beside|into}.
* `compose_board · cards[].anchor` — shape \{cardId?: number, ref?: string, relation?: after|before|under|beside|into} — This card's own anchor \{cardId | ref, relation}; top-level cards only.
* `compose_board · edges[].fromRegion` — shape \{x: number, y: number, w: number, h: number} — \{x,y,w,h} natural px of the from-card: the clicked element.
* `compose_board · edges[].toRegion` — shape \{x: number, y: number, w: number, h: number} — \{x,y,w,h} natural px of the to-card: where the flow arrives.
* `compose_board · questions` — shape \[\{prompt: string, stakes?: string, options: \[string | \{label: string, hint?: string, detail?: string, recommended?: boolean, cardId?: number, pivot?: boolean}], multiSelect?: boolean, cardRef?: string, cardId?: number, kind?: choice|text, pivot?: boolean}] — Staged questions (cardRef | cardId anchors); as ask\_board.
* `update_cards · patch` — shape \[\{id: number, payload?: object, x?: number, y?: number, w?: number, h?: number}]

Viz také: `organization`, `sections`, `cluster`, `section`

### `organization` — Sidebar routing: every board gets a project, a subgroup, and optional cross-cutting tags [#organization]

Every board belongs to a project and, within it, a subgroup — canonical vocabulary: reviews (PR-review boards) | testing (QA/set boards) | sessions (recorded journey-recorder sessions, nested under the project's \<project>-sessions hub) | brainstorming | design (mockups/design directions) | journeys (testing-journey suites). Pass subgroup on create \{kind: "board"} whenever the board fits a family; the server infers reviews/testing/sessions/brainstorming/journeys from slug or board\_type when omitted, but design must always be explicit. TAGS are a third, additive axis: freeform, many per board, the way to group boards across projects (an area, a client, a release). Call list \{kind: "tag"} FIRST to reuse existing tags instead of minting typo-duplicates; create \{kind: "board"} \{tags} and set\_board\_meta \{tags} attach them (unknown slugs autocreate and return in a `created` array — always check it); list \{kind: "board"} \{tags, match} queries (AND default, match:"any" for OR, '-slug' excludes). Slugs are folded — case and diacritics never fork a tag.

Arguments in full (moved out of the tool schemas):

* `set_board_meta` — Update a board's subgroup, theme, tags, archived flag and/or meta bag. meta is a MERGE-PATCH (passed top-level keys shallow-merge, others survive) — stamp \{commitSha, tracedAt} incrementally; the server's own stamps (task, composed, planComposed, sessionId, sessionHub, bugBoard, lane) go back unchanged or not at all — name a board's task with add\_task\_ref, keep a free note of it as meta.taskRef; \{index: true} makes the board an index (its board cards fold their boards under it in the sidebar). File a board into an index with top-level parentBoard + section on create \{kind: "board"} or set\_board\_meta: the server places its board card in that section (a raw meta.parentBoard only nests). Tags are additive and autocreate.
* `share_board` — Mint a capability share link for outside people — the URL is the credential. Every link shares the WHOLE board; cardId only chooses what opens first. Never mint on a board carrying drafts the recipient must not see. Returns \{id, mode, url}: hand the url out bare. Personal boards never mint.
* `set_board_meta · theme` — Per-board visual theme token — open vocabulary matching \[a-z0-9]\[a-z0-9\_-]\{0,31}; "" restores the house default; omit to leave unchanged. Known bundles: sketch (hand-drawn rendering — rough geometry, Caveat hand type, hachure fills; SVG/PNG exports follow the look), light-paper, dark-paper (the house look pinned light or dark whatever the reader's app theme), playful, technical, diary, girlies, release. Open tabs restyle live.
* `set_board_meta · tags` — Tags to attach. Unknown tags are created automatically and reported in the response's `created` array. Adds by default — pass tags\_mode:"set" to replace the board's whole tag set instead.
* `set_board_meta · tags_mode` — "add" (default) attaches the given tags; "set" makes them the board's complete tag set, detaching any others
* `set_board_meta · archived` — true retires the board (it leaves the board lists; nothing is deleted), false restores it; omit to leave unchanged
* `share_board · mode` — view (read-only, default) or annotate (link holders can leave feedback that routes back like any annotation)
* `share_board · cardId` — Open the link on this element as a document. The link still grants the whole board — this is a landing choice, not an access boundary. Omit to open on the canvas.

### `lanes` — Device tabs: one run board whose lanes are child boards, minted by ONE create call, each lane a whole board under a tab [#lanes]

A run that must read per device (desktop · tablet · phone · the iOS Simulator) is ONE run board with LANES: create \{kind: "board", slug: "\<branch>-test-run-\<n>", lanes: \[\{key: "desktop", label: "Desktop 1440×810"}, \{key: "phone"}, \{key: "ios-safari", label: "iOS Simulator · Safari"}]} mints the run board (the Overview tab) AND one child board per lane in the same call — slug \<slug>-\<key>, meta.parentBoard = the run board, meta.lane = the key, the same board\_type/project/subgroup/task stamp — and stamps meta.lanes on the parent (the tab order). The receipt answers lanes: \[\{key, label, slug, url}]; a run slug that already carries lanes answers the same receipt again (200), so a retried create never half-builds a run. Keys are \[a-z0-9]\[a-z0-9-]\{0,31}, unique, at most 12, never "overview". Each lane IS a board: compose its passes there as sections ("Pass 1", "Pass 2") with compose\_board \{board: \<lane slug>, section}, annotate it, scrape it — its own history, its own annotation queue. The parent holds the run summary and one verdict per lane (a `board` portal card per lane is the link). The board page renders the same tab strip on the parent and on every lane (Overview first, then the lanes); the sidebar nests the lanes under the run board. scrape\_board on the parent lists lanes\[] with each lane's openAnnotations; on a lane it lists its siblings with active on itself. set\_board\_meta \{meta: \{lanes: \[...]}} edits the order by hand; a lane is never deleted, only archived like any board.

Viz také: `organization`, `sections`, `board`

### `sections` — Journey sections: named frames as navigation, iteration surface, and slides [#sections]

Name board regions after customer journeys with kind "section" frames ("Onboarding", "Checkout") — always visible, auto-indexed (list \{kind: "section"}, pill navigation, scrape\_board sections\[]), and every tool scopes to them: compose\_board \{section}, arrange \{scope:"section:Name"}, scrape\_board \{section} for an isolated digest, request\_review \{section} for a scoped AI pass. Sections are the ITERATION surface: present the next take on a journey as its OWN section beside the current one ("Checkout — iteration 2") — never mix new cards into the original; iterations then sit side by side, each pill-navigable, separately scrapeable and reviewable. update\_cards patches only your own layer plus section frames (shared structure); it removes freely what you own — AI-layer cards, section frames, cards you created — and gates anyone else’s behind the 409 needs\_confirm handshake (repeat the ids with confirm + a reason ≥ 20 chars; soft trash, reason visible in HISTORY/Trash).

Arguments in full (moved out of the tool schemas):

* `arrange` — Auto-align board cards — the layout engine, zero tokens of geometry. Express intent with the knobs: mode (column · row · grid · flow · lanes · masonry · timeline · timeaxis · compare · align-\* · distribute-\*), gap/padding, scope (ai · all · section:\<Name> · ids). save:true on a section persists the layout so it re-flows on every change.
* `scrape_board` — Read a board as one structured digest: cards with section, image/document URLs, notes, annotations, journey chains (flow), a questions digest and the sections/journeys map. Image cards add `vs` (0–100 visual score; absent until the background lane scored the blob), `dup` (on near-duplicates only: the id of the best-scored card of the group — keep the card whose id equals it) and `device` (desktop | tablet | phone). Pass section to isolate one journey — the scoped read for section work. Page cards come as previews; read\_element reads a whole document.
* `arrange · mode` — column|row|grid|flow|lanes|masonry|timeline|timeaxis|compare|journey|graph|align-left|align-top|distribute-h|distribute-v — open vocabulary, server-validated (unknown mode 400s with the list). journey: left-to-right tree driven by the board's wires — linear runs stay in a lane, a signpost screen fans its branches vertically (wire order), unlinked cards grid below the tree; not persistable (save 400s). graph: top-down tree of doc cards driven by their ref wires (nested documents) — roots are docs nothing points at, siblings in ref order, a parent centered over its children, unlinked cards grid below; not persistable (save 400s)
* `arrange · save` — With a section scope: persist this layout onto the section (payload.layout) so the server auto-reflows it on every future change
* `arrange · scope` — Which cards move: "ai" (default — the expression layer), "all", or "section:\<Name>" (that journey's members, arranged inside the frame)
* `arrange · sections` — mode compare: exactly the two section names to align side by side; any other mode: ONE section name, the same call as scope "section:\<Name>" (the receipt echoes that scope); several names, or one beside scope or ids, is refused with the one-call-per-section fix
* `arrange · cols` — grid/masonry: cards per row (default 3) or "auto" = fit as many columns as the section's inner width allows
* `arrange · sort` — grid/column/row/flow order: reading (default — where cards sit) or oldest (chronological: the uploaded file's original timestamp, falling back to card creation)
* `scrape_board · pages` — page cards: preview (default) = title + excerpt; full = the whole markdown of every page card (read\_element reads one)
* `scrape_board · diff` — Exactly two section names — the ITERATION DIFF: members matched pairwise (same file, then reading order) as \{pairs:\[\{a,b}]} plus unpaired leftovers. Mutually exclusive with section.

Viz také: `section`, `composing`

### `uploads` — Putting files (PDFs, documents, images) on a board from the shell [#uploads]

Run `vitrinka board upload <files…> --board <slug> [--section "Name"] [--anchor <cardId> --side right|below]` — bytes stream from disk over multipart HTTP and never enter your context; the command prints the created card JSON. PDFs render as live embedded documents (kind "file", A4 card, page count), other files as download chips, images as media cards. When a capture sidecar is configured the server also rasterizes a PDF's pages asynchronously into payload `pageImages` (`/uploads/<slug>/<rand>-pN.png`, ≤24 pages), which the face paints instead of the native viewer under press mode (`?press=1`, captures); `POST /api/v1/cards/{id}/pages` re-renders them on demand (501 without a sidecar). Without placement flags the server places the batch in free space — same no-coordinates doctrine as compose. A `--section` that does not exist is created on demand on every board type, unless the name is a near match of an existing section: then the call is refused with 409 `section_near_match` and a `didYouMean` list — re-run with the exact name, or `--create-section` (form field `createSection`, compose\_board `createSection: true`) to insist. Raw API fallback: POST \{base}/api/v1/boards/\{slug}/upload multipart field "files" (+ optional section / anchor+side / createSection form fields). Task evidence has its own door: `vitrinka task upload <id> <files…>` (POST \{base}/api/v1/tasks/\{id}/upload, same multipart) stores the files under `/uploads/tasks/<id>/` and attaches each as a `file` ref carrying the URL — the task's Evidence row links straight to it from any machine; small text can go inline through MCP `upload_task_file {id, filename, content}`. scrape\_board returns file cards as kind "file" with `filename · mime` text and the document URL under `artifact`.

Viz také: `artifact`, `composing`

### `recall` — The pre-build ritual: three cheap read-only calls before ANY UI work on an indexed project [#recall]

Before UI work, spend three read-only calls: (1) get \{kind: "project\_memory"} — the project's ranked memory, the operator's source of truth; a conflict with the user's ask gets surfaced, never silently overridden. (2) search \{kind: "shot"} for the target screen — a hit means the screen exists; look at how it looks today first and reference existing shots on boards instead of re-uploading what the library holds. (3) list \{kind: "component"} — mock with the REAL component vocabulary; an empty index means offer `vitrinka project index --components`, don't proceed blind. Learned something durable while working (a convention, a gotcha, a decision)? Close the loop where the project keeps it: rows from a repo island → `memo add <kind>/<topic> "<sentence>."` in a worktree of that repo (it lands by PR); only a live legacy document (`legacy.state: live`) is still edited — `legacy.body`, edit surgically, save\_project\_memory.

### `search` — How workspace search reads a query: ids in any shape pin, typos also search their correction, and every task hit says where it matched [#search]

One call, `search {q}`, answers every group (tasks, boards, pages, cards,
sessions, shots, tags, annotations). Pass the text as the person typed or
pasted it; the server does the parsing.

## Ids pin, in any shape [#ids-pin-in-any-shape]

A task named by number is read directly and PINNED first in its group
(`pinned: true`), ahead of everything that merely ranked:

* `#3263`, `vt-3263` — `vt-<n>` anywhere in a token, so a branch
  (`work/search-retake-vt-3263`) or a PR title (`(vt-3263)`) pins too.
* `acme/3263` — a workspace slug, then the number (a `KEY-3263` key is checked against the task's project).
* a task URL — `/w/<slug>/p/<project>/t/<id>` or `/p/<project>/t/<id>`.

A bare `3263` pins only as the WHOLE query: "release 4" never pins task 4.
A task named by id also pins the board it references in the boards group.
An id in ANOTHER workspace you belong to (`globex/123`, a `/w/globex/…`
URL) pins with a `workspace` object and that workspace's `url`; one you
cannot open answers no row — the same silence as a missing task.

## Everything a task carries is searchable [#everything-a-task-carries-is-searchable]

Title, body, every text field (gates and their evidence, next action,
outcome, decisions, branch), the addresses of its refs (PR number and
title, board, branch, worktree, Jira key) and its comments. A hand-back
summary is NOT indexed. A task hit names where its snippet came from:
`matchedIn` is `title | body | field:<key> | ref | comment`, and
`matchedLabel` the name a person reads ("Gates", "PR", "Comment").

## Typos also search their correction [#typos-also-search-their-correction]

Every letters-only word of four or more letters is looked up in the
workspace's vocabulary; its nearest words (one edit, two from eight
letters, same first letter) are searched WITH it, and the reply lists
them in `corrections` (`[{from, to}]`). A row only a corrected word
matched carries `corrected: true` and ranks below exact matches. Tokens
with digits, dashes or slashes — slugs, ids, URLs — are never corrected.
Ranking is relevance only; a task's state and age never move it.

## Meaning, when no task holds every word [#meaning-when-no-task-holds-every-word]

On a deployment with an embedding sidecar, a task search none of whose
tasks holds every word also ranks tasks by meaning and fuses the two
orders. Such a reply carries `mode: "hybrid"`; its rows stay leads, and
one only meaning found has `matchedIn: "meaning"`. When meaning was
wanted but unavailable the reply says `mode: "lexical"` with a
`modeReason`. Meaning reads only the most recently updated tasks inside
the search's facets; a hybrid reply whose facets held more carries a
`modeReason` too — narrow by project, type or state to reach older ones.
So does one while much of that window still waits for its vectors (the
index is building) — search again shortly.
No `mode` means the words alone answered.

The task-list text filter (`list {kind: "task", f: {text}}`) matches the same
fields exactly — it is a filter every count agrees on, so it never
corrects.

## Resolve: one task, one call [#resolve-one-task-one-call]

`search {q, mode: "resolve"}` (the API's `?mode=resolve`, the CLI's
`vitrinka search <text> --resolve`) reads `q` as ONE task reference and
answers a `verdict`: `exact` (an id, key or URL named it, or a branch a
task carries), `confident` (the ranker's leader stands clear), `ambiguous`
(pick from the candidates) or `none`. Beside it: `interpreted` (what the
query was read as), at most 5 `candidates` (`id`, `key`, `title`, `type`,
`state`, `parentId`, `parent`, `childCount`, `url`, `why`), `missing` ids
and `next`, the call to make. It is the same one ranker every task search
uses.

It reads the palette's fields (`project:`, `type:`, `state:` / `in:`) and,
at the edges of the text, type and state words in English or Czech
(`bugs` / `chyby`, `epic` / `epik`, `story` / `příběh`, `open` /
`otevřené`, `in progress` / `rozpracované`, `done` / `hotové`),
`under <task>` / `pod <task>` (every task under it, at any depth) and
`mine` / `moje`. When no task inside a typed word's facet holds every
word, the word reads as title text again ("Todo list loses order") or is
dropped, and is listed in `interpreted.relaxed`; a title holding `under`
or `pod` resolves as itself. `project`, `type` and `state` narrow as
everywhere; `groups` and `tags` are refused. Candidates holding only some
of the words are leads under verdict `none`; an id outside `project`,
`type` or `state` is listed in `outside`, where it lives; a draft's id
answers `exact` with `draft: true`.

A tool that takes a task id, given a reference instead —
`get {kind: "task", id: "Release restart"}`, a `vt-42`, a URL as a
`parentId` — answers the resolver's verdict and candidates with the exact
call to make, never a guess.

Viz také: `tasks`, `recall`

### `project-transfer` — Move a whole project between two workspaces you own — copy, verify, then archive the source [#project-transfer]

A transfer copies EVERYTHING a project owns into another workspace: its sets, boards with cards/annotations/questions/tags, sessions, reports, releases, and the whole PM engine (tasks, events, states, labels, sprints, milestones, rules, views, fields), plus every blob those rows reference. It does NOT move tenant-wide state — workspace settings, templates, connectors, listeners, undo history, AI usage — and it never moves memberships: the destination workspace's members are its own. Gate: the caller must be an OWNER of both workspaces (agent/service tokens are refused); a single-tenant deployment answers 501. Always dry-run first (`dryRun`/`--dry-run` counts rows and blobs, writes nothing). The copy is idempotent — a re-run reports `skipped` per table instead of duplicating — and it refuses (409, naming the slug/key) when a board slug or set key already exists in the destination; rename there, then retry — or MERGE. `merge` is for a destination that already holds a project of the same slug (the same team's work split across two workspaces): per-project vocabulary — states, labels, fields, sprints, milestones, saved views, rules — resolves to the destination's row of the same key (reported as `merged` per table), content (tasks, sessions, boards, cards, comments) is appended with fresh ids, and a board slug or set the destination already owns is renamed on the way in (`<slug>-from-<source workspace>`; a set takes the next free version on its branch, so `latest` on that branch becomes the merged set) with its blob keys rekeyed everywhere they are written (a renamed board's live collaborative page state is not carried — the editor re-seeds it from the saved document); the report's `renamed` lists every one, so dry-run and read it first. Only what no rename can reconcile stays a 409 under merge: two `next` releases. Nothing is deleted unless you ask: `archiveSource` retires the project in the source only after the destination committed — boards archived (meta says where they went), live tasks trashed (recoverable, one `deleted` ledger row each), the registry row marked `moved` with the destination workspace so the name leaves both sidebar trees; sets keep the name (URLs and blob keys never change). Three doors, one contract: `POST /api/v1/projects/{project}/transfer {to, dryRun, archiveSource, merge}`, `vitrinka project transfer <project> --to <workspace> [--dry-run] [--archive-source] [--merge]`, and the MCP tool `transfer_project`. The response is the report: boards moved, sets, rows per table (with skipped and merged), renames, blobs and bytes, archived and trashed counts.

### `brief` — The feature brief — the ONE thing a fresh session reads before building an epic: server skeleton from the brainstorm's picks, prose filled by the brainstorming skill under hard caps, surfaced first by pickup [#brief]

A brainstorm leaves a decision log and a board full of rejected takes; the BRIEF is the analyst's document a session that did not brainstorm reads instead (brainstorming levels 2026-09-11, D3–D6). Sections, in order, each with a cap the skill trims to and never exceeds: Intent (3 sentences) · Vocabulary (≤ 8 terms) · As-is (≤ 12 lines, real files and routes) · To-be (≤ 15 numbered user stories) · Architecture (one diagram + ≤ 10 units, each: what it does / how it is used / what it depends on) · Decisions that matter (≤ 8, each `chosen over X because Y`) · Prerequisites (≤ 8) · Acceptance criteria (≤ 15, each naming a story number and the test seam) · Work packages (≤ 8, ordered, disjoint files — what a subagent-driven build dispatches) · Out of scope · Open questions · Refs. TWO DOORS, one contract: `POST /api/v1/tasks/{epic}/brief` (MCP `compose_brief {id}`, CLI `vitrinka task brief <id> [--out <file>]`) composes or rebuilds ONE `Plan` chapter at the end of the epic's brainstorm board — intent, each decision's CHOSEN take only (a ref element pointing at the winning card, never a copy), the architecture diagram, the acceptance checklist — and returns the brief SKELETON: the deterministic half filled from the board (intent, a `| # | Decision | Chosen | Rejected | Why |` row per decision, unanswered decisions under Open questions, the refs) and every prose section holding only `<!-- skill -->`. The first run uploads that skeleton as `brief` version 1; the skill fills the placeholders and uploads the completed file as the NEXT version of the same filename (`upload_task_file {kind: "brief"}` / `vitrinka task upload <id> <file> --kind brief --hint "read first; sufficient to build"`). A brief that still carries a placeholder is visibly incomplete on purpose. LOAD FLAGS: task refs carry `meta.load` ∈ first | on-demand | never — the door stamps the brief `first` and the brainstorm board `on-demand` with `meta.section: "Plan"`; `pickup` (`vitrinka task pickup <id|url>`, `/continue <task url>`) puts `first` refs in its read rail ahead of the cap, lists `on-demand` refs as `read on demand: <kind> <hint> (section Plan)` and never inlines them, and hides `never`. Read the Plan chapter alone with `scrape_board {board, section: "Plan"}`; open the whole brainstorm board only to reopen a decision. 404 `no_brainstorm_board` when the epic has no brainstorm board ref yet — brainstorm first.

Arguments in full (moved out of the tool schemas):

* `compose_brief` — Compose or rebuild the epic's feature brief from its brainstorm board's picks: rebuilds the Plan chapter and returns the brief SKELETON (deterministic sections filled, prose sections as placeholders). First run uploads version 1 of the `<slug>-brief.md` lineage; upload later versions yourself with upload\_task\_file \{kind: "brief", versionOf}. 404 no\_brainstorm\_board without a board ref.

Viz také: `project-transfer`

### `review` — The review rubric as data: one markdown rule per id with YAML frontmatter, layered built-in ▸ workspace ▸ project, rendered into the brief every review pass receives [#review]

A review rule is one markdown file: YAML frontmatter, then the rule sentence as the first paragraph, then a `## Verify` bullet list a reviewer (human or model) can tick. FRONTMATTER: `id` (slug `^[a-z][a-z0-9-]{1,40}$` — the identity every layer, finding category and stat keys on), `family` (free text, the topic the rule belongs to: spacing, copy, data honesty …), `check` ∈ mechanical | partial | judgment (how much of it a program can measure — a mechanical rule's finding carries a number, count or diff as its proof; a judgment rule quotes the rendered evidence), `severity` ∈ high | medium | low (the default severity of a finding filed under it), `platform` ⊆ \[web, mobile] (omit for both), `enabled` (default true). LAYERS: the built-in set ships embedded (broken · ia-flow · redundant · spacing · scope-directive · native-shell · consistency · visual-polish · copy · clarity · reuse · data-honesty · hierarchy · layout-width · redesign-open · states · dark-theme · repeat-class · not-a-finding); a workspace rule with the same id replaces the built-in whole, a project rule replaces both; a new id at any layer is simply appended; deleting a layer's override (reset) lets the layer below show through. Disabling a rule is a rule with `enabled: false` at that layer. THE BRIEF: `review_brief {project, persona?}` / `vitrinka review brief [--project] [--persona]` / `GET /api/v1/projects/{project}/review/brief` render the resolved enabled rules as one prompt block — a fixed preamble (what a finding is, how to file it: `annotate` with `severity` and `category` = rule id, cluster ≥3 instances of one rule across ≥2 screens into one finding, an unreachable screen is an error never zero findings), then `[id · platform · severity] sentence` + verify lines per rule, then the persona seat line. `request_review` without `instructions` sends exactly this brief; explicit instructions win for that pass (≤ 12 000 chars). EDITING: Settings → Projects → review and Settings → Workspace → review (enable · reset · add, the body in the page editor, lint on save); `vitrinka review rules list | get <id> | lint <file…> [--fix] | put <file…> [--workspace]`; `GET/PUT/DELETE /api/v1/projects/{project}/review/rules[/{id}]` and `…/workspace/review/rules[/{id}]` (PUT body `{source}` = the whole markdown, or a field patch `{enabled, body, …}` over the resolved rule; writes are admin+ and audited as `review.rules.update`, every write is a new version). LINT rides the docspec diagnostics envelope with stable codes (missing-frontmatter · invalid-frontmatter · missing-id · invalid-id · missing-check · invalid-check · missing-severity · invalid-severity · invalid-platform · invalid-enabled · missing-body · missing-sentence · missing-family · missing-verify · unknown-key); autofix (`lint --fix`) repairs a BOM, HTML entities, enum near-misses, an id derived from the file name or first heading, and a missing check (→ judgment) — an ambiguous near-miss is never guessed. REVIEW RUNS (`review_plan`, a rare-tier door callable by name; CLI `vitrinka review plan`; `POST /api/v1/review/plan`): `review_plan {boards: [slugs of one project], scope?: latest | all, sections?: [names], full?}` decides what a review must judge and opens a run. latest = each journey's last capture pass (a board without journeys: every screen); no scope over boards holding several passes, or over 600 screens, answers 409 `needs_scope` with each option's cost (images, judge, sheets, gaps, reused) and the corrected call — nothing is planned until the scope is chosen. The plan drops capture gaps (blank, skeleton, undecodable, a runner's failed shot: never a finding, never a pass), reuses every image judged in a completed run under the same rubric (the rendered brief's hash: any rule edit re-judges; `full: true` reuses nothing), judges byte-identical screens once and a near-duplicate (within one device × theme) by a crop of the pixels that differ from its group's representative — a change over 35% of the screen, or another size, is its own tile. Reply: `run`, `sheets`, tiles \{whole, crop}, planned \{judge, exactDuplicates, reused, gaps}, `gaps`, `prior` (open findings on the in-scope card versions), tokens \{baseline: every screen on 12-tile survey sheets, plan, saved}. SHEETS: `get_card_image {run, sheet}` — whole screens ≤ 12 a sheet, crops ≤ 24; the text block maps each tile to \{cardId, board, cardVersion, width, height (annotate's basis), at, crop + vs (its representative) for a crop, prior\[], lint\[] (mechanical candidates from text boxes)}. Sheet 1 opens with the SIGHT CODE, printed in pixels only: echo it as `sight` on the next call (`get_card_image {run, sheet: 2, sight}`, or `review_plan {run, sight}` for a one-sheet run) — sheet 2+ and filing answer 403 `sight_required` until then, and three wrong codes lock the run (409 `sight_locked`): a host that cannot read the code sees no images and files nothing. RECEIPT: `review_plan {run, cards?}` — one state per in-scope card: inspected (a whole tile) · represented (a crop or an exact duplicate of a covered representative) · reused · gap · blocked, pending until the run completes (verified, every sheet served). FILING: `annotate {board, run, items}` files only against a verified run, on its boards and cards; an item restating an open finding on the same card version (same category, regions overlapping by half the smaller) answers `duplicate` with that finding's id and writes nothing. Measure regions on a single-card read (`get_card_image {board, cardId}`), never on a sheet.

Viz také: `annotation`

### `annotation` — Agent-authored annotations: coordinate space, revision pinning, attribution, lifecycle, batch idempotency [#annotation]

An annotation is a region on a shot/media card plus a thread; it is the ONE shape for a finding an agent files — "review these screens and annotate the inconsistencies" is answered with `annotate`, never with document/finding cards from compose\_board and never with `highlight` (a stroke, not a record). READ FIRST: `get_card_image {board, cardId, version?, region?, maxDim?}` returns the pixels (image content block) plus \{width, height, cardVersion} — width × height is the SOURCE image's size and cardVersion its revision; scrape\_board reports the same width/height/cardVersion per card. SURVEY A SET: `get_card_image {board, cardIds:[…]}` (at most 12) returns ONE contact sheet, each tile labelled `#<cardId>`, plus `cards` — per tile its cardId, width, height, cardVersion and `tile` rectangle on the sheet; it is for spotting what to look at, never for measuring, and replaces fetching screens to tile them yourself (image bytes never belong in a shell command; in a code-mode script hand the image block to the host's image call, e.g. `image(block)`, and print only the text block — never stringify or log the whole result). COORDINATE SPACE: every region is \{x, y, w, h} in source-image pixels of the declared cardVersion, origin top-left, bounds = that width × height; a small overshoot is clamped, one mostly or wholly outside is refused per item as `out_of_bounds`. Measure on the pixels you saw — never guess from captions, and never measure on a downscaled (maxDim) image's own size. Declare `image: {width, height}` from the get\_card\_image read on each item so a region measured on another size is refused as `basis_mismatch` (the error names both sizes and the rescale ratio) instead of landing wrong. Every result carries `image` (the basis used) and `covers` — the text under the filed region — so read `covers` back, and `dryRun: true` first when unsure what a region points at. REVISION: each item declares the cardVersion it was measured on; when the card has moved on the item answers `stale` with `currentVersion` and writes nothing (re-inspect, retry) — `pinStale: true` on the batch files it against the older revision instead, shown with the ·vN badge. ATTRIBUTION: the batch's `agent` (claude-code | codex | eve | gemini | other:\<slug>) is the displayed author `agent:<runtime>`; the authenticated principal (PAT user or agent token) is stored beside it as the verified identity and shown as "via \<name>" — a misdeclared runtime is visible next to the proof. `reply` and `attach_after` take the same `agent` argument. LIFECYCLE: agent findings are born `staged` and never ride the board's Send; a human Accepts (staged → open, then the normal queue: working → in\_review → resolved) or Dismisses (→ cancelled), per item or Accept all / Dismiss all on the board's agent-findings group; the intent you set (fix default · investigate · refactor · redesign · comment · other — never reshoot/remind/task) can be retriaged before accepting. Agents never verdict their own findings. BATCH: ≤ 100 items per call, prompt (summary + detail) ≤ 8 KiB each; `key` per item is the idempotency key, scoped to the BOARD and agent (not the card — use one key per finding, e.g. `c<cardId>-<n>`): a retry with the same keys returns `existing` ids, never a duplicate, never a 409; a partially failed batch lands the rest. `related: [{cardId, region?}]` pins the other screens a finding spans, each to that card's current version, listed as jump links in the thread. Result per item: \{key, status: created | existing | stale | out\_of\_bounds | rejected, id?, url? (/a/\<id>, the solo view), currentVersion?, error?} plus created / existing / refused counts. OVER HTTP: POST /api/v1/boards/\{slug}/annotations/batch with the same body; the human triage door is POST /api/v1/boards/\{slug}/annotations/agent-verdict \{action: accept | dismiss, ids?} (board UI only, not an MCP tool). No AI-credit debit — you brought your own model.

Arguments in full (moved out of the tool schemas):

* `set_status` — Move an annotation through its lifecycle: "working" when picking it up, "in\_review" after attaching the after-shot and replying. Never "resolved" — the user accepts in the board UI. A 409 "cancelled" means the user withdrew it: revert and move on.
* `attach_after` — Attach the "after" proof shot to an annotation by naming a file inside a set version you already pushed. Appends a fix attempt to the before/after deck and advances the card's face until the user accepts or rejects — the canonical way to show a fix; never paste the after as a new card.
* `annotate` — File your OWN findings as native annotations on shot cards, born staged for the human to Accept or Dismiss — THE answer to "review these screens", never finding cards or highlight. Regions are SOURCE-IMAGE pixels of the declared cardVersion (read get\_card\_image first); a stale version answers `stale`; `key` per item makes retries idempotent. ≤100 items. Doctrine: docs topic "annotation".
* `get_card_image` — SEE a shot/media card's pixels: an image block plus \{width, height, cardVersion, cropped, imageWidth, imageHeight, region?} — width/height/cardVersion are the SOURCE space annotate regions use; imageWidth/imageHeight are the downscaled bytes, never measure on them. region zooms; version reads an older face.
* `annotate · agent` — The agent runtime speaking: claude-code | codex | eve | gemini | other:\<slug> | judge/\<model>. Judge provenance preserves the model for review statistics (up to 96 characters). Becomes the displayed author `agent:<value>`; the authenticated principal is stored beside it as the verified identity. Required unless the caller authenticates with an agent token, whose registered name is used (an empty value on a PAT is a 422).
* `annotate · pinStale` — true = pin items whose cardVersion is no longer current to that older revision instead of refusing them as stale (an audit of an earlier push, filed deliberately). Default false.
* `annotate · dryRun` — Resolve and validate without writing findings. Ready items return their resolved region and a version-pinned cropUrl for inspection.
* `annotate · items[].key` — Your stable id for this finding within the batch (e.g. "login-cta-contrast") — the idempotency key; reuse it on retry.
* `annotate · items[].region` — Exactly one of source-image \{x,y,w,h}, normalized \{nx,ny,nw,nh}, or exact case-sensitive \{text,nth?}. Text resolves against boxes.json of cardVersion; duplicates require zero-based nth. Missing/ambiguous map returns unresolved. Rectangles are intersected with source-image bounds.
* `annotate · items[].related` — Other screens the same finding spans — listed as jump links in the thread, each pinned to that card's current version
* `get_card_image · maxDim` — Longest side of the returned image in pixels (default 1600) — smaller is cheaper; coordinates stay in source pixels regardless
* `reply / attach_after · agent` — The agent runtime speaking: claude-code | codex | eve | gemini | other:\<slug>. The author becomes `agent:<runtime>`; the authenticated principal is stored beside it as the verified identity. Omit only on a legacy registration (then the bare "claude" label is used).
* `get_card_image · region / annotate · related[].region` — Rectangle in SOURCE-IMAGE pixels of the declared cardVersion (origin top-left, bounds = the image's width × height as get\_card\_image / scrape\_board report them). Partially outside is clamped; no overlap = that item is refused as out\_of\_bounds.
* `annotate · agent` — Agent runtime: claude-code | codex | eve | gemini | other:\<slug> | judge/\<model>; required unless an agent token.
* `annotate · pinStale` — true pins items whose cardVersion is stale to that revision instead of refusing them.
* `annotate · dryRun` — Validate without writing; ready items return their resolved region and a version-pinned cropUrl.
* `annotate · items[].cardVersion` — The card's face version the region was measured on (get\_card\_image / scrape\_board cardVersion)
* `annotate · items[].region` — One of source-image \{x,y,w,h}, normalized \{nx,ny,nw,nh}, or \{text,nth?} resolved against boxes.json.
* `annotate · items[].summary` — One line — the finding as the annotation prompt's first line
* `annotate · items[].detail` — Evidence and the suggested change (markdown-ish plain text; ≤ 8 KiB with summary)
* `annotate · items[].category` — Required review rule id or legacy category.
* `annotate · items[].related` — Other screens the same finding spans, pinned to each card's current version.
* `annotate · items[].cardId` — The shot/media card the finding is on
* `annotate · items[].region` — shape \{x?: number, y?: number, w?: number, h?: number, text?: string, nth?: integer, nx?: number, ny?: number, nw?: number, nh?: number} — \{x,y,w,h} source px, \{nx,ny,nw,nh} normalized, or \{text,nth?}.
* `annotate · items[].related` — shape \[\{cardId: number, region?: \{x: number, y: number, w: number, h: number}}] — Other screens the finding spans.

Viz také: `composing`, `uploads`

### `tasks` — The task engine's MCP doors in full: hand\_back, spot, propose\_tasks, bulk updates, attachments, task-id spelling, the work wire [#tasks]

The task-engine tools advertise one line each; this page carries their full argument contracts. Ids are per-workspace: spell them "\<workspace>/\<id>" on every id-only tool. A bare id resolves only when your connection reaches a single workspace; otherwise the reply names the ambiguity and the corrected spelling.

Arguments in full (moved out of the tool schemas):

* `wait_for_work` — BLOCK until scoped work exists — long-polls, costs nothing idle, returns up to 3 ready-to-act capsules: annotations, released question answers (choices\[], regenerate:true after ↻ Regenerate), ask-AI requests (asks\[] — answer via answer\_ask) and new recorder sessions (sessions\[]). \{"idle":true} means call again. ALWAYS SCOPE with project + branch or board.
* `add_task_ref` — Attach a session, shot, board, file, PR ("owner/repo#n"; meta.merged completes the task) or run to a task, or link a typed document by URL. Re-attaching updates meta. Upload kinds take a URL only — inline text goes through upload\_task\_file, files through `vitrinka task upload`. Refs are versioned, never replaced.
* `upload_task_file` — Store inline TEXT as a typed attachment on a task (kind, hint, filename; .md .txt .json .jsonl .csv .html .yaml .log .diff .patch; 1 MiB). The same filename is the NEXT VERSION; versionOf continues a lineage explicitly. Binaries and files on disk go through `vitrinka task upload`. Reply: \{task, refs}.
* `hand_back` — The ONE door that ends a session's work on a task: `next` lands as steps and gates on the task's checklist (a check on the work you just did, a merge, a look, a human decision), `task: true` files a child for work that needs its own session, PR or QA record; `omitted` with `decide: true` is a human gate; moves `status`, attaches `refs`, lands the summary as the next handback.md version, updates the parent epic, ends the live run. PRINT `rendered` VERBATIM as your hand-back; never file next steps again through create \{kind: "task"}.
* `spot` — File something you spotted but will NOT do in this change — a defect or follow-up — as a child of the bound task (parentId) or the project's rolling 'Found during implementation' epic. Body = what · where · why out of scope. `fromStep` promotes a step of the bound task into the child (the step leaves the checklist). Print the returned url. Read the task's children first; never batch spots.
* `read_task_ref` — Load ONE task attachment progressively: under `budget` tokens the raw text; over it, or with `question`, the AI backend distills (without one, the head cut at the budget with truncated:true). Reply: \{ref, version, size, tokens, truncated, content}. Read the refs index (meta.tokens, meta.hint) first.
* `ask_task` — Ask ONE plain-language question over a task's whole corpus (`id`: row, fields, comments, events, every attachment version, boards, PRs) and get an answer with citations \[\{ref, version, at, quote}] — or over a whole project (`project` instead of `id`): Eve reads the project's tasks and answers \{answer, tasks: \[\{id, key, title, type, state, url, why}], confidence}, only live tasks of that project, each with its url. The project form answers open questions ("what blocks 4.0.0?", "who is on the checkout work?"); finding ONE task stays search \{q, mode: "resolve"}. `intent` (developer · product · qa · reviewer · onboarding) shapes it. Writes nothing; costs AI credits — 501 without a backend, do not retry.
* `bulk_update_tasks` — Apply ONE patch to up to 200 tasks in one transaction (all-or-nothing). Patch fields: state, status, type, priority, assignee, assigneeId, sprintId, milestoneId, parentId, dueAt, estimate, project — zero values clear like update \{kind: "task"}; `project` moves the tasks (subtrees whole) into another project of the same workspace, for a whole project use merge\_project.
* `propose_tasks` — File one or more task DRAFTS through the intake door for a human to accept — the "file these" path. Each draft is deduped against the project's open tasks and carries its verdict. Drafts never appear in normal lists until accepted.
* `wait_for_work · project` — Only wait for one project's work (matches the card set\_ref); pair with branch to scope to your repo+branch
* `upload_task_file · hint` — One line telling a future reader WHEN to load this ("load when continuing the auth work"); lands in meta.hint
* `hand_back · summary` — The higher-level summary everyone reads (required, ≤ 4000 chars): what the work achieved, for a PM as much as for the next agent
* `hand_back · readFirst` — Ref ids (on this task or its epic) the next session must read first — they lead the pickup's READ section
* `hand_back · status` — Move the task; omit to leave the status to the rules (a run starts it, an open PR reviews it, a merged PR completes it)
* `spot · parentId` — The bound task (the pickup you started from, the checkout's vt-\<id>); omit when no task is bound — the rolling epic catches it
* `spot · fromStep` — A step of parentId (its item id or exact name) that outgrew the checklist: it is removed there and the child carries the work; 400 when no such step, parentId required
* `ask_task · scope` — with `id` only: self reads the task alone; tree adds its descendants (three levels, 200 tasks): rows, last 10 comments each and their attachment index, newest first. Default: tree for an epic, self otherwise
* `create · update {kind: "task"} · human` — The human-view flag (vitrinka for humans): true lists the task on the human surfaces (Mission control, Needs you, the project and feature pages) as a row of its own, false keeps it off; unset, the type rule decides — epics and human-asked stories are human-level, everything else rolls up as progress. Over REST, `null` hands it back to the rule.
* `bulk_update_tasks · patch.project` — Move the tasks into another EXISTING project of the same workspace (target slug; an unknown project is a 422, never minted): each subtree moves whole; state, labels, fields, sprint and milestone remap by key, a key the target lacks is created there, the source vocabulary is never touched. For a whole project use merge\_project.
* `propose_tasks · drafts[].key` — A caller-chosen name for this draft inside this call, so a later draft can file under it with parentKey
* `propose_tasks · drafts[].parentKey` — File under the draft of this batch carrying that key (must come earlier in drafts) — a qa draft and its journey drafts in ONE call. Exclusive with parentId
* `propose_tasks · drafts[].parentId` — File under this task — a live one or a still-pending intake row (a qa draft under its epic, journey drafts under a pending qa draft). Declining a parent draft declines its pending children; accepting a child accepts a pending parent first
* `propose_tasks · drafts[].fields` — Custom-field values keyed by field key (list \{kind: "field"}), validated per kind the way update \{kind: "task"}.fields is — e.g. a journey's \{role, route, steps: \[\{name, done}], expected, verdict}
* `task id (every id-only tool)` — Task id — or "\<workspace>/\<id>" for a task in another workspace of your organisation (ids are per-workspace; the same spelling project takes)
* `hand_back · summary` — The summary everyone reads (required, ≤4000 chars): what the work achieved.
* `hand_back · surfaces` — New APIs, components, doors — where the next session builds on
* `hand_back · buildOn` — Where the next session should start building (a file, a door, a seam)
* `hand_back · next` — Next steps, in this order: each lands as a step on the task's `gates` checklist unless `task: true`, which files a child (ranked after the existing children). A check on the work you just did, a merge, a look, a verify, a human decision is a step or gate — never a task
* `hand_back · next[].kind` — step (default; the agent's own item, never blocks) · human (a gate `who` resolves; blocks Done; a `who` with no kind makes the item human) · date (a gate reached on `at`, required) · action (work still to be done; never blocks). A step re-filed by name keeps its tick
* `hand_back · next[].ask` — A human gate's sentence for the PERSON (Needs you prints it, never the title): ≤ 90 characters, verb first ("Approve the release copy"), never an id, key, path, link or session id — the door refuses one and returns the item with it stripped; `why` is one line of what waits on the answer, `verb` (decide · approve · review · test · answer) the chip, derived from the ask when absent. Accepted without an ask for now, with a `warnings` line; the same three ride `omitted` decide items and any `fields.gates` human item
* `hand_back · next[].when` — The todo grammar ("pr 12 merged", "deployed prod", "after 718"), read by kind. On a human or date gate it is the proof: the fire door ticks the gate by itself when it fires, evidence attached — a merge that should close itself is `kind: human` ("Merge PR #12", `who` the person who merges, `when: pr 12 merged`). On a step (the default) or an action it is the moment the work falls due, never proof it happened: the fire door stamps `ripeAt` (evidence attached), the pickup's NEXT shows it `waiting — <when>` before and `due — <when>` after, and it stays open until someone does the work and ticks it ("Ship after the merge" with `when: pr 12 merged`)
* `hand_back · omitted` — Work seen and deliberately left out: `decide: true` lands as a `human` gate `Decide: <title>` on the task (waiting on `who`, else the assignee; `why` is its evidence) — only in a run nobody attends; with a human in the session, ask the call before the hand-back and record the answer in `summary` — other omissions are filed as children so they are never lost
* `hand_back · prerequisites` — What must happen before the next step can start (moves the parent epic to waiting)
* `hand_back · readFirst` — Ref ids the next session must read first — they lead the pickup's READ section.
* `hand_back · status` — Move the task; omit to let the rules decide (run starts, PR reviews, merge completes).
* `hand_back · refs` — Refs to attach beside the hand-back — the PR, the board made during testing
* `hand_back · refs[].ref` — The ref key: owner/repo#n for a pr, the slug for a board
* `hand_back · refs[].meta` — Ref meta, merged on conflict (a pr's \{url, branch, state}; a board's \{board: true})
* `hand_back · sessionId` — This session's id: ends its live run on the task with the summary
* `hand_back · run` — a memo run island \{island, repo, branch, commit, checkpoint?}, printed by `memo run locate <task> --json` as data.ref: the continuation lives in the run, so no handback.md is uploaded — the task's run ref gains a version carrying the hand-back; refs of the moved kinds (handoff, plan, notes, distill, brainstorm, conversation, transcript) are refused; an omission that is not a decision stays in the run and files no child. The pickup's RUN row names the run and `memo run locate`; read\_task\_ref on the run ref is its checkpoint view, rendered from the verified archive or the receipt
* `hand_back · next` — shape \[\{title: string, task?: boolean, kind?: step|human|date|action, who?: string, at?: YYYY-MM-DD, when?: string, ask?: string, why?: string, verb?: decide|approve|review|test|answer, type?: task|bug, body?: string, priority?: urgent|high|medium|low|}] — Steps and gates on the task; `task: true` files a child (type, body, priority apply to it).
* `hand_back · omitted` — shape \[\{title: string, why?: string, decide?: boolean, who?: string, ask?: string, verb?: decide|approve|review|test|answer}] — Left out deliberately; decide → a human gate on the task (unattended runs only), else a child.
* `hand_back · refs` — shape \[\{kind: session|shot|board|file|pr|run|transcript|conversation|brainstorm|plan|handoff|decision|distill|commit|final|brief|notes, ref: string, meta?: object}] — Refs to attach — the PR, the board.
* `spot · refs` — shape \[\{kind: session|shot|board|file|pr|run|transcript|conversation|brainstorm|plan|handoff|decision|distill|commit|final|brief|notes, ref: string, meta?: object}] — Evidence you already have — a board, a session, a PR
* `propose_tasks · source` — shape \{kind?: string, ref?: string, url?: string, meta?: object} — Where the drafts come from
* `propose_tasks · drafts[].refs` — shape \[\{kind: session|shot|board|file|pr|run|transcript|conversation|brainstorm|plan|handoff|decision|distill|commit|final|brief|notes, ref: string, meta?: object}]
* `bulk_update_tasks · patch` — shape \{state?: string, status?: backlog|todo|in\_progress|in\_review|done|cancelled, type?: task|bug|story|epic|todo|qa|journey|meeting, priority?: |urgent|high|medium|low, assignee?: string, assigneeId?: number, sprintId?: number, milestoneId?: number, parentId?: number, dueAt?: string, estimate?: number, project?: string} — The patch

Viz také: `brief`, `review`
