Files
ttrpg-tools/.agents/skills/tttk-authoring/SKILL.md
T
hyper ae36125af0 docs(skill): add rules-writing conventions
Add rules.md covering document structure, theme/players/time header, icon-first mentions, veteran-first prose, and a closing timestamp. Link it from SKILL.md.
2026-10-24 16:51:59 +08:00

148 lines
7.2 KiB
Markdown

---
name: tttk-authoring
description: Author and edit TTRPG game design documents for the tttk (Tabletop Toolkit) CLI. Use when writing or modifying markdown content that uses tttk custom elements (:md-dice, :md-table, :md-deck, :md-print, :md-pins, etc.), custom fenced code blocks (role=spark-table / declare / file / tag), companion CSV data files, or when previewing content with `tttk serve` and validating it with `tttk check`.
---
# Authoring tttk game design documents
`tttk` (Tabletop Toolkit) renders a folder of markdown into an interactive TTRPG
web app. A game design document is **ordinary GFM markdown** plus exactly two
extensions:
1. **Custom elements** written with `marked-directive` syntax: `:name[content]{attrs}`.
2. **Custom fenced code blocks** whose info string carries a `role=` attribute.
Everything else — headings, lists, tables, blockquotes, alerts, mermaid — is
standard markdown. Do not invent element names, props, or roles; check
`reference.md` when unsure. Deeper topics have their own files: `yaml-tag.md`
(deck config), `csv.md` (data files), `layout.md` (columns), and `markdown.md`
(alerts and mermaid).
## Workflow
- Content lives in a directory (commonly `content/`). Preview with hot reload:
`npx tttk serve ./content` (default port 3000, override with `-p 8080`).
- **Validate after every edit:** `npx tttk check ./content`. It exits `1` when
there are errors and prints `file:line` diagnostics. Use `--json` for
machine-readable output and `--quiet` for errors only. This is the primary
verification loop: write → check → fix.
- The scanner picks up `.md`, `.csv`, `.yarn`, and `.svg` files.
- All paths in directives are **relative to the current markdown file**
(`./data.csv`, `../shared/npcs.csv`, `./images/map.png`).
- A typical game/adventure folder:
```
my-adventure/
├── design.md # design notes (plain markdown)
├── adventure.md # the document players/GM read
├── locations.csv # data referenced by :md-table
├── cast.csv
└── route.csv
```
## Directive syntax
```
:name[content]{key="value" .class #id}
```
- `[content]` is the directive body — a path, dice formula, color, etc.
- `{key="value"}` sets attributes. `{.class}` and `{#id}` are shorthands.
- Container directives use `::: name` … `:::`; nest deeper with `::::`, `:::::`.
Use the **registered element names**: `:md-dice`, not `:dice`. The full list is
in `reference.md`.
## Code block roles
Fenced blocks are dispatched by the `role=` attribute in the info string. Both
backtick and tilde (`~~~`) fences work; the closing fence must reuse the opening
marker, and fence attribute keys may contain hyphens (e.g. `data-foo=bar`):
| Info string | Behavior |
|---|---|
| ` ```csv role=spark-table ` | Body is CSV; replaced with `:md-table[./csv_<hash>]`. First column header must be a dice formula (`d6`, `2d6+1`, …). |
| ` ```markdown role=spark-table ` | Body is a GFM pipe table; converted to CSV, then same as above. |
| ` ```yaml role=declare ` | YAML `vars:` / `tags:` maps defining reactive variables and tag modifiers. Block is stripped. |
| ` ```<lang> role=file ` | Body stored as text content, block stripped, referenced by its derived id. |
| ` ```yaml role=tag ` | Kept intact; rendered as a custom element from YAML (`tag:`, `body:`, …). |
| ` ```<lang> ` (no role) | Rendered as a normal visible code block. |
| unknown role | Warned about and stripped. |
## Rules that prevent broken output
- Quote any attribute value containing spaces or `"`:
`{pins="A:30,40 B:10,30"}`.
- In CSV, wrap fields containing `,`, `"`, or a newline in double quotes and
escape `"` as `""`. Lines starting with `#` are comments.
- `label` and `body` are the conventional CSV columns. Put titles inside `body`
as markdown headings; use **bold labels** for sub-fields, not headings.
- A `group` column in a table CSV automatically produces group tabs — no
attribute needed.
- Reference each CSV once per document; don't repeat the same `:md-table`.
- For card decks, use the `yaml role=tag` form with `tag: md-deck` (see
`yaml-tag.md`). The `:md-deck[...]{...}` directive is legacy and can't express
template layers or card backs.
- To print a rules document, use `:md-print[./rules.md]` (or the `yaml role=tag`
form). It paginates into A4 landscape sheets holding two A5 pages, each with
`columns` columns (default 2). See `reference.md` for the attributes and
`rules.md` for how to write the rules text itself.
- Keep CSV files clean — header row plus data. Put deck config and templates in
the YAML block's `data-config`, not in CSV front matter (see `csv.md`).
- `{{prop}}` in a CSV `body` interpolates another column (or front-matter key)
from the same row; with `{remix}` it pulls from a random row instead.
## Validation
`npx tttk check ./content` validates against the same content registry the app
uses, so a clean check means the app resolves the content too. Errors exit `1`:
| Code | Meaning |
|---|---|
| `TTTK-S002` | container directive `:::` never closed |
| `TTTK-D001` | unknown `:md-*` element name |
| `TTTK-D002` | malformed attribute block (e.g. an unquoted value with a space) |
| `TTTK-R001` | unknown code-block `role=` (the block is silently dropped) |
| `TTTK-R003` | `role=declare` failed to parse |
| `TTTK-R005` | `role=tag` YAML is invalid |
| `TTTK-R006` | `role=tag` missing or naming an unknown element |
| `TTTK-C001` | content reference does not resolve |
| `TTTK-C004` | `:md-embed` cycle |
| `TTTK-L001` | link target file does not exist |
| `TTTK-V001` | CSV failed to parse (e.g. an unclosed quote) |
Warnings are usually real too:
| Code | Meaning |
|---|---|
| `TTTK-R002` | spark-table first header is not a dice formula |
| `TTTK-R004` | `role=declare` block declares nothing |
| `TTTK-C002` | reference resolves to an unexpected file type |
| `TTTK-L002` | `#section` matches no heading |
| `TTTK-A001` | icon missing under `assets/` |
| `TTTK-A002` | image missing |
| `TTTK-V002` | CSV row column count differs from the header |
| `TTTK-V003` | duplicate `label` values |
| `TTTK-V004` | `{{prop}}` is not a CSV column or front-matter key |
| `TTTK-V005` | spark-table roll value is not a number or range |
`TTTK-C003` (info) flags the same CSV referenced twice by `:md-table` in one
document. Icon checks assume no `assets/` directory exists above the checked
directory. The full code catalog lives in `reference.md`.
## Reference
- `reference.md` — **generated** catalog of every custom element, inline
directive, code block role, and dice notation. Do not edit by hand;
regenerate from the repo with `npm run gen:skill-reference`.
- `yaml-tag.md` — `role=tag` blocks, `data-config` layers, card backs, and the
legacy `:md-deck` layer syntax.
- `csv.md` — CSV authoring: columns, `{{prop}}` interpolation, quoting, and the
discouraged front-matter form.
- `layout.md` — column layout markers.
- `rules.md` — conventions for writing rules documents: the five-part
structure, the theme/players/time header, icon-first mentions, veteran-first
prose with details boxed in alerts, and a timestamp.
- `markdown.md` — GitHub alerts and mermaid diagrams.
- `examples/adventure.md` and `examples/encounters.csv` — a complete minimal
document and its data file.