docs(skill): Generate the tttk reference catalog
Enrich known.ts with element/role details, inline specs, and dice notation, and render them from reference.ts. Add gen:skill-reference and ~ expansion in --out so the skill copy can be regenerated. The skill's reference.md is now generated; its extra authoring content moved to layout.md, yaml-tag.md, csv.md, and markdown.md.
This commit is contained in:
+3
-1
@@ -368,7 +368,9 @@ Exit code is the contract that makes `check` usable in an agent loop.
|
||||
write → check → fix. Remove the "compile is a no-op stub" note. Cross-link
|
||||
the code catalog (`reference.md`) with the diagnostic codes.
|
||||
- `reference.md` is generated from `known.ts` (§7.11) — do not hand-edit. The
|
||||
skill's copy is refreshed by `npm run gen:reference`.
|
||||
skill's copy is refreshed by `npm run gen:skill-reference`; deeper authoring
|
||||
topics live in the skill's `yaml-tag.md`, `csv.md`, `layout.md`, and
|
||||
`markdown.md` (see `docs/skill-reference-plan.md`).
|
||||
|
||||
## 10. Resolved decisions
|
||||
|
||||
|
||||
+88
-9
@@ -3,8 +3,18 @@
|
||||
Generated from `src/cli/validate/known.ts` by `npm run gen:reference`.
|
||||
Do not edit by hand.
|
||||
|
||||
Everything not listed here is standard GFM markdown. Element names are
|
||||
prefixed `md-` and must match exactly.
|
||||
Complete catalog of the custom elements, inline directives, and code block
|
||||
roles that `tttk` understands. Everything not listed here is standard GFM
|
||||
markdown. Element names are prefixed `md-` and must match exactly.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Directive syntax](#directive-syntax)
|
||||
- [Custom elements](#custom-elements)
|
||||
- [Inline directives](#inline-directives)
|
||||
- [Code block roles](#code-block-roles)
|
||||
- [Dice notation](#dice-notation)
|
||||
- [CSV data files](#csv-data-files)
|
||||
|
||||
## Directive syntax
|
||||
|
||||
@@ -12,9 +22,24 @@ prefixed `md-` and must match exactly.
|
||||
: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 with `::::`, `:::::`.
|
||||
| Part | Meaning | Example |
|
||||
|---|---|---|
|
||||
| `:name` | Registered element name | `:md-dice` |
|
||||
| `[content]` | Directive body | `[2d6+d8]` |
|
||||
| `{attrs}` | Attributes | `{key="attack"}` |
|
||||
| `{.class}` | Shorthand for `class` | `{.text-red-500}` |
|
||||
| `{#id}` | Shorthand for `id` | `{#my-dice}` |
|
||||
|
||||
Container directives:
|
||||
|
||||
```markdown
|
||||
::: container
|
||||
Content here, including nested directives.
|
||||
:dice[1d20]
|
||||
:::
|
||||
```
|
||||
|
||||
Nest with more colons (`::::`, `:::::`).
|
||||
|
||||
## Custom elements
|
||||
|
||||
@@ -36,6 +61,8 @@ prefixed `md-` and must match exactly.
|
||||
|
||||
Clickable dice roller; `key` stores the result in the URL.
|
||||
|
||||
Clicking the text rolls; clicking the 🎲 resets to the formula. With `key`, the result is stored in the URL as `?dice-<key>=<value>`.
|
||||
|
||||
- Body: dice formula
|
||||
- Attributes: `key`
|
||||
|
||||
@@ -55,6 +82,8 @@ Clickable dice roller; `key` stores the result in the URL.
|
||||
|
||||
Tab-separated view of a CSV with optional group tabs.
|
||||
|
||||
`roll` adds a 🎲 random-tab button; `remix` makes `{{prop}}` references pull from a random row. A `group` column in the CSV produces group tabs automatically.
|
||||
|
||||
- Body: CSV path or inline id
|
||||
- Attributes: `roll`, `remix`
|
||||
|
||||
@@ -74,6 +103,8 @@ Tab-separated view of a CSV with optional group tabs.
|
||||
|
||||
Card layout from a CSV (legacy; prefer `yaml role=tag`).
|
||||
|
||||
Prefer the `yaml role=tag` form — it is the only form that supports template layers, per-layer alignment, and card backs. The directive form is legacy and only supports the compact `layers` string.
|
||||
|
||||
- Body: CSV path
|
||||
- Attributes: `size`, `grid`, `layers`
|
||||
|
||||
@@ -99,6 +130,8 @@ Clickable link that expands the target article inline.
|
||||
|
||||
Renders the target article or section inline.
|
||||
|
||||
`headingBase` shifts the heading levels of the embedded content.
|
||||
|
||||
- Body: path, optional #section
|
||||
- Attributes: `headingBase`
|
||||
|
||||
@@ -110,6 +143,8 @@ Renders the target article or section inline.
|
||||
|
||||
Sets the surrounding article card background.
|
||||
|
||||
`fit` is one of `cover` (default), `contain`, `fill`, `none`, `scale-down`.
|
||||
|
||||
- Body: image path or CSS color
|
||||
- Attributes: `fit`
|
||||
- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`).
|
||||
@@ -126,6 +161,8 @@ Sets the surrounding article card background.
|
||||
|
||||
Applies a border to the article card.
|
||||
|
||||
Body is a CSS color or a border-image path. Class flags select sides (`t b l r all`), style (`solid dashed dotted double groove ridge inset outset none`), and image repeat (`stretch repeat round space`).
|
||||
|
||||
- Body: color or border image
|
||||
- Attributes: `width`, `slice`
|
||||
- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`).
|
||||
@@ -142,6 +179,8 @@ Applies a border to the article card.
|
||||
|
||||
Sets the article font.
|
||||
|
||||
`source` is `google`, `emfont`, or `local` (default).
|
||||
|
||||
- Body: font family
|
||||
- Attributes: `source`, `weight`, `color`
|
||||
|
||||
@@ -153,6 +192,8 @@ Sets the article font.
|
||||
|
||||
Numbered markers on an image.
|
||||
|
||||
`pins` is a space-separated list of `label:x,y` (percent coordinates). `fixed` makes it read-only; otherwise click to add and click a pin to remove. `labelStart` sets the first label (letter or number).
|
||||
|
||||
- Body: image path
|
||||
- Attributes: `pins`, `fixed`, `labelStart`
|
||||
|
||||
@@ -168,6 +209,8 @@ Numbered markers on an image.
|
||||
|
||||
Interactive command history / tracker panel.
|
||||
|
||||
The body is a CSV of command templates (`command,parameter,label,description,insertedText`).
|
||||
|
||||
- Body: command template CSV
|
||||
- Attributes: `placeholder`, `class`, `height`
|
||||
|
||||
@@ -179,6 +222,8 @@ Interactive command history / tracker panel.
|
||||
|
||||
Runs a Yarn Spinner dialogue file.
|
||||
|
||||
`start` names the entry node (default `start`).
|
||||
|
||||
- Body: Yarn Spinner file
|
||||
- Attributes: `start`
|
||||
|
||||
@@ -188,24 +233,38 @@ Runs a Yarn Spinner dialogue file.
|
||||
|
||||
## Inline directives
|
||||
|
||||
### Icons — `:[name]`
|
||||
### `:[name]`
|
||||
|
||||
Inserts an icon looked up under `assets/` (searching upward from the file).
|
||||
Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp`
|
||||
are also supported.
|
||||
|
||||
Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp` are also supported. `label` overlays text, `repeat` duplicates N times, and `join` places a tag name between repeats.
|
||||
|
||||
- Body: icon name
|
||||
- Attributes: `label`, `repeat`, `join`, `class`
|
||||
|
||||
```markdown
|
||||
:[attack] :[shield.svg] :[potion]
|
||||
```
|
||||
|
||||
### Command links — `:cmd[command]{label=...}`
|
||||
```markdown
|
||||
:[attack]{label=ATK repeat=3 join=br}
|
||||
```
|
||||
|
||||
### `:cmd[command]`
|
||||
|
||||
Renders a clickable span that dispatches a journal command.
|
||||
|
||||
- Body: journal command
|
||||
- Attributes: `label`
|
||||
|
||||
```markdown
|
||||
:cmd[roll 1d20+5]{label=Roll initiative}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:cmd[spark dungeon room-type]{label=Random room}
|
||||
```
|
||||
|
||||
## Code block roles
|
||||
|
||||
The `role=` attribute on the info string determines how a fenced block is
|
||||
@@ -215,6 +274,8 @@ handled.
|
||||
|
||||
CSV (or markdown pipe table) rendered as `:md-table`.
|
||||
|
||||
The first column header must be a dice formula; each row's first cell is the roll value (a number, or a range like `1-3`). Extra fence attributes are forwarded to the generated `:md-table`, e.g. ` ```csv role=spark-table roll=true `.
|
||||
|
||||
````markdown
|
||||
```csv role=spark-table
|
||||
d6,结果
|
||||
@@ -236,6 +297,8 @@ d6,结果
|
||||
|
||||
Reactive variable / tag-modifier definitions.
|
||||
|
||||
Columns (any order): `tag`, `threshold`, `key`, `expr`. A row with an empty `tag` declares a variable; a row with a `#tag` applies a modifier when that tag is active.
|
||||
|
||||
````markdown
|
||||
```csv role=declare
|
||||
tag,threshold,key,expr
|
||||
@@ -248,6 +311,8 @@ tag,threshold,key,expr
|
||||
|
||||
Stored as inline text content and stripped.
|
||||
|
||||
Referenced by the derived inline id, e.g. `:md-embed[./text_<hash>]`.
|
||||
|
||||
````markdown
|
||||
```text role=file
|
||||
Stored as inline content.
|
||||
@@ -258,6 +323,8 @@ Stored as inline content.
|
||||
|
||||
YAML rendered as a custom element.
|
||||
|
||||
`tag:` names the element, `body:` becomes its content, and any other key becomes an attribute. Fence-line attributes override the YAML body. See `yaml-tag.md`.
|
||||
|
||||
````markdown
|
||||
```yaml role=tag
|
||||
tag: md-deck
|
||||
@@ -272,6 +339,18 @@ grid: 5x5
|
||||
No role → a normal visible code block. Unknown role → warned about and
|
||||
stripped.
|
||||
|
||||
## Dice notation
|
||||
|
||||
Used by `:md-dice`, spark-table headers, and the journal `/roll`:
|
||||
|
||||
| Notation | Meaning |
|
||||
|---|---|
|
||||
| `2d6` | two six-sided dice |
|
||||
| `1d20+5` | d20 plus modifier |
|
||||
| `3d6kh1` | keep highest 1 |
|
||||
| `3d6dl2-1` | drop lowest 2, minus 1 |
|
||||
| `{d4, d8}` | choose one of the listed dice |
|
||||
|
||||
## CSV data files
|
||||
|
||||
Keep CSV files clean — a header row and data rows. Put deck configuration
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# tttk skill reference — reorganization plan
|
||||
|
||||
Status: **implemented.** Follows the `tttk check` work that made
|
||||
`docs/reference.md` a generated artifact (see `docs/check-plan.md` §7.11).
|
||||
|
||||
Landed: `known.ts` carries element/role `details`, inline specs, and dice
|
||||
notation; `reference.ts` renders them; `scripts/generate-reference.ts` expands
|
||||
`~` in `--out`; `npm run gen:skill-reference` refreshes the skill copy. The
|
||||
skill's `reference.md` is now generated and byte-identical to
|
||||
`docs/reference.md`, with the extra content moved to `layout.md`, `yaml-tag.md`,
|
||||
`csv.md`, and `markdown.md`.
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Make the `tttk-authoring` skill's `reference.md` a **generated** file — the same
|
||||
catalog the validator uses — and relocate the authoring content it does not
|
||||
cover into focused companion files. One source of truth (`known.ts`), no
|
||||
hand-maintained element list, and no content lost in the swap.
|
||||
|
||||
## 2. Current state
|
||||
|
||||
The skill (`~/.agents/skills/tttk-authoring/`) has a hand-written
|
||||
`reference.md` (~465 lines) and `SKILL.md`. The repo now generates
|
||||
`docs/reference.md` from `src/cli/validate/known.ts` via `npm run
|
||||
gen:reference`.
|
||||
|
||||
What the generated doc already covers:
|
||||
|
||||
- Directive syntax (brief)
|
||||
- Custom elements: table + per-element summary, body, attrs, examples
|
||||
- Inline directives: icons, `:cmd` (brief)
|
||||
- Code block roles: `spark-table`, `declare`, `file`, `tag` (brief)
|
||||
- CSV data files (brief)
|
||||
|
||||
What only the hand-written skill copy has (the "extra content"):
|
||||
|
||||
| # | Section | Notes |
|
||||
|---|---|---|
|
||||
| A | Directive syntax table + container example | richer than generated |
|
||||
| B | Per-element prose | md-dice behavior, md-table `roll`/`remix`/`group`, md-deck legacy layer syntax, md-bg `fit` values, md-border class flags, md-font `source` values, md-pins behavior, md-commander CSV columns |
|
||||
| C | Icon attributes | `label`, `repeat`, `join`, `class` |
|
||||
| D | Column layout | `\|---` / `-\|-` / `--\|` markers |
|
||||
| E | `role=declare` column semantics | `tag`/`threshold`/`key`/`expr` |
|
||||
| F | YAML tag blocks | `data-config`, layers, `back_layers`, fence-line overrides |
|
||||
| G | CSV authoring | clean-CSV guidance, `label`/`body`, front matter (avoid) |
|
||||
| H | Alerts and mermaid | GitHub alerts, mermaid diagrams |
|
||||
| I | Dice notation | notation table |
|
||||
| J | Validation codes | already duplicated in `SKILL.md` |
|
||||
|
||||
## 3. Target skill layout
|
||||
|
||||
```
|
||||
tttk-authoring/
|
||||
├── SKILL.md # workflow, rules, validation codes, links
|
||||
├── reference.md # GENERATED from known.ts — elements, roles, inline, dice, CSV basics
|
||||
├── layout.md # column layout
|
||||
├── yaml-tag.md # role=tag, data-config, deck layers and backs
|
||||
├── csv.md # CSV authoring and front matter
|
||||
├── markdown.md # alerts and mermaid
|
||||
└── examples/ # unchanged
|
||||
```
|
||||
|
||||
`reference.md` is the only generated file; the rest are hand-written and linked
|
||||
from `SKILL.md`.
|
||||
|
||||
## 4. Content mapping
|
||||
|
||||
| Source | Destination |
|
||||
|---|---|
|
||||
| A directive syntax | generator (`reference.ts`) — add the part/meaning table + container example |
|
||||
| B per-element prose | `known.ts` `details` field → rendered into `reference.md` |
|
||||
| C icon attributes | `known.ts` inline specs → rendered into `reference.md` |
|
||||
| E declare semantics | `known.ts` `RoleSpec.details` → rendered into `reference.md` |
|
||||
| I dice notation | `known.ts` `DICE_NOTATION` → rendered into `reference.md` |
|
||||
| D column layout | `layout.md` |
|
||||
| F YAML tag blocks | `yaml-tag.md` |
|
||||
| G CSV authoring | `csv.md` |
|
||||
| H alerts and mermaid | `markdown.md` |
|
||||
| J validation codes | dropped — `SKILL.md` owns the code tables |
|
||||
|
||||
**Decision (recommended):** fold B/C/E/I into `known.ts` rather than a separate
|
||||
`components.md`. It keeps the generated reference self-sufficient and avoids a
|
||||
second, drifting description of the same elements. The alternative — a
|
||||
hand-written `components.md` — is rejected because it reintroduces the drift the
|
||||
generator exists to prevent.
|
||||
|
||||
## 5. Generator changes
|
||||
|
||||
`src/cli/validate/known.ts`:
|
||||
|
||||
- `ElementSpec.details?: string` — one paragraph of authoring prose.
|
||||
- `ElementSpec.attrs` → optional richer `{ name, description }[]` so the
|
||||
reference can render an attribute table (keep the plain list if not worth it).
|
||||
- `InlineSpec` for `:[icon]` and `:cmd` with body/attrs/summary/examples.
|
||||
- `RoleSpec.details?: string` for `declare` column semantics.
|
||||
- `DICE_NOTATION: { notation, meaning }[]`.
|
||||
|
||||
`src/cli/validate/reference.ts`:
|
||||
|
||||
- Render `details`, the attribute table, inline specs, and dice notation.
|
||||
- Add a `Contents` TOC.
|
||||
- Upgrade the directive-syntax section (table + container example).
|
||||
|
||||
`scripts/generate-reference.ts`:
|
||||
|
||||
- Expand a leading `~` in `--out` to `os.homedir()` so the skill copy can be
|
||||
regenerated cross-platform.
|
||||
|
||||
`package.json`:
|
||||
|
||||
- `gen:reference` — unchanged (writes `docs/reference.md`).
|
||||
- `gen:skill-reference` — `gen:reference -- --out ~/.agents/skills/tttk-authoring/reference.md`.
|
||||
|
||||
## 6. New skill file outlines
|
||||
|
||||
- **`layout.md`** — column markers, the `-N` width suffix, a worked example.
|
||||
- **`yaml-tag.md`** — `role=tag` shape (`tag:`, `body:`, other keys as attrs),
|
||||
fence-line overrides, `data-config` layer fields (`prop`/`template`, `pos`,
|
||||
`font`/`fontSize`, `orientation`, `align`, `visible`), `back_layers`, and the
|
||||
legacy `:md-deck` compact layer syntax.
|
||||
- **`csv.md`** — clean-CSV guidance, `label`/`body` conventions, `group` tabs,
|
||||
`{{prop}}` interpolation, quoting rules, and the discouraged front-matter form.
|
||||
- **`markdown.md`** — GitHub alerts and mermaid (standard markdown, not tttk
|
||||
syntax).
|
||||
|
||||
## 7. SKILL.md changes
|
||||
|
||||
- Update the "Reference" section to link `reference.md` plus the four new files.
|
||||
- Keep the validation code tables in `SKILL.md` (they are the agent's quick
|
||||
reference); `reference.md` no longer carries them.
|
||||
- Reword "check `reference.md` when unsure" to point at the right file per topic.
|
||||
|
||||
## 8. Steps
|
||||
|
||||
1. Enrich `known.ts` (details, inline specs, role details, dice notation).
|
||||
2. Update `reference.ts` and `generate-reference.ts`; regenerate
|
||||
`docs/reference.md`; keep the `known.test.ts` drift guard green.
|
||||
3. Extract D/F/G/H from the old skill `reference.md` into `layout.md`,
|
||||
`yaml-tag.md`, `csv.md`, `markdown.md`.
|
||||
4. Regenerate the skill's `reference.md` with `npm run gen:skill-reference`.
|
||||
5. Update `SKILL.md` links.
|
||||
6. Verify (§9).
|
||||
|
||||
## 9. Verification
|
||||
|
||||
- `npm run gen:reference` leaves `docs/reference.md` unchanged (idempotent).
|
||||
- `npx jest` passes, including the reference drift guard.
|
||||
- Diff the old skill `reference.md` against the new file set and confirm every
|
||||
section in §2's table has a destination — no content silently dropped.
|
||||
- `npx tttk check ./content` still runs clean (the catalog changes are
|
||||
documentation-only, but the drift guard protects `known.ts`).
|
||||
|
||||
## 10. Risks
|
||||
|
||||
- **Content loss during extraction.** Mitigate with the §4 mapping as a
|
||||
checklist and a manual diff before deleting the old file.
|
||||
- **`known.ts` bloat.** Keep `details` to one paragraph per element; long-form
|
||||
material belongs in the companion files.
|
||||
- **Skill path portability.** `~` expansion in the generator; the skill lives
|
||||
outside the repo, so the drift guard cannot cover it — document that
|
||||
`gen:skill-reference` is the refresh command.
|
||||
- **Two references.** `docs/reference.md` and the skill copy are both generated
|
||||
from `known.ts`; they must be regenerated together. Note this in
|
||||
`docs/check-plan.md` §9.
|
||||
@@ -30,6 +30,7 @@
|
||||
"ttrpg": "node --loader ts-node/esm ./src/cli/index.ts",
|
||||
"tttk": "node --loader ts-node/esm ./src/cli/index.ts",
|
||||
"gen:reference": "node --loader ts-node/esm ./scripts/generate-reference.ts",
|
||||
"gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out ~/.agents/skills/tttk-authoring/reference.md",
|
||||
"test": "jest",
|
||||
"prepare": "npx husky",
|
||||
"prepublishOnly": "npm run build"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { mkdirSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, resolve } from "path";
|
||||
import { renderReference } from "../src/cli/validate/reference.js";
|
||||
|
||||
@@ -15,7 +16,16 @@ function argValue(flag: string): string | undefined {
|
||||
return index >= 0 ? process.argv[index + 1] : undefined;
|
||||
}
|
||||
|
||||
const out = resolve(argValue("--out") ?? "docs/reference.md");
|
||||
/** Expand a leading `~` so the skill copy can be targeted cross-platform. */
|
||||
function expandHome(path: string): string {
|
||||
if (path === "~") return homedir();
|
||||
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
||||
return resolve(homedir(), path.slice(2));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
const out = resolve(expandHome(argValue("--out") ?? "docs/reference.md"));
|
||||
mkdirSync(dirname(out), { recursive: true });
|
||||
writeFileSync(out, renderReference(), "utf-8");
|
||||
console.log(`wrote ${out}`);
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface ElementSpec {
|
||||
/** Key attribute names. */
|
||||
attrs: string[];
|
||||
summary: string;
|
||||
/** Authoring prose rendered under the summary. */
|
||||
details?: string;
|
||||
refKind: RefKind;
|
||||
/** Canonical usage examples, rendered into the reference. */
|
||||
examples: string[];
|
||||
@@ -30,6 +32,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "dice formula",
|
||||
attrs: ["key"],
|
||||
summary: "Clickable dice roller; `key` stores the result in the URL.",
|
||||
details:
|
||||
"Clicking the text rolls; clicking the 🎲 resets to the formula. With `key`, the result is stored in the URL as `?dice-<key>=<value>`.",
|
||||
refKind: "none",
|
||||
examples: [
|
||||
':md-dice[1d20+5]{key="attack"}',
|
||||
@@ -42,6 +46,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "CSV path or inline id",
|
||||
attrs: ["roll", "remix"],
|
||||
summary: "Tab-separated view of a CSV with optional group tabs.",
|
||||
details:
|
||||
"`roll` adds a 🎲 random-tab button; `remix` makes `{{prop}}` references pull from a random row. A `group` column in the CSV produces group tabs automatically.",
|
||||
refKind: "csv",
|
||||
examples: [
|
||||
":md-table[./npcs.csv]",
|
||||
@@ -54,6 +60,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "CSV path",
|
||||
attrs: ["size", "grid", "layers"],
|
||||
summary: "Card layout from a CSV (legacy; prefer `yaml role=tag`).",
|
||||
details:
|
||||
"Prefer the `yaml role=tag` form — it is the only form that supports template layers, per-layer alignment, and card backs. The directive form is legacy and only supports the compact `layers` string.",
|
||||
refKind: "csv",
|
||||
examples: [
|
||||
':md-deck[./cards.csv]{size="63x88" grid="5x5" layers="title:1,1-5,1f8 body:1,5-5,8f3"}',
|
||||
@@ -72,6 +80,7 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "path, optional #section",
|
||||
attrs: ["headingBase"],
|
||||
summary: "Renders the target article or section inline.",
|
||||
details: "`headingBase` shifts the heading levels of the embedded content.",
|
||||
refKind: "md",
|
||||
examples: [":md-embed[./rules.md#combat]{headingBase=1}"],
|
||||
},
|
||||
@@ -80,6 +89,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "image path or CSS color",
|
||||
attrs: ["fit"],
|
||||
summary: "Sets the surrounding article card background.",
|
||||
details:
|
||||
"`fit` is one of `cover` (default), `contain`, `fill`, `none`, `scale-down`.",
|
||||
refKind: "image",
|
||||
examples: [':md-bg[./images/dungeon-bg.jpg]{fit="cover"}', ":md-bg[#2a1a3a]"],
|
||||
},
|
||||
@@ -88,6 +99,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "color or border image",
|
||||
attrs: ["width", "slice"],
|
||||
summary: "Applies a border to the article card.",
|
||||
details:
|
||||
"Body is a CSS color or a border-image path. Class flags select sides (`t b l r all`), style (`solid dashed dotted double groove ridge inset outset none`), and image repeat (`stretch repeat round space`).",
|
||||
refKind: "image",
|
||||
examples: [
|
||||
":md-border[crimson]{.b}",
|
||||
@@ -99,6 +112,7 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "font family",
|
||||
attrs: ["source", "weight", "color"],
|
||||
summary: "Sets the article font.",
|
||||
details: "`source` is `google`, `emfont`, or `local` (default).",
|
||||
refKind: "none",
|
||||
examples: [
|
||||
':md-font[Noto Sans SC]{source="google" weight="700" color="#1a1a2e"}',
|
||||
@@ -109,6 +123,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "image path",
|
||||
attrs: ["pins", "fixed", "labelStart"],
|
||||
summary: "Numbered markers on an image.",
|
||||
details:
|
||||
"`pins` is a space-separated list of `label:x,y` (percent coordinates). `fixed` makes it read-only; otherwise click to add and click a pin to remove. `labelStart` sets the first label (letter or number).",
|
||||
refKind: "image",
|
||||
examples: [
|
||||
':md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30 C:50,80" fixed}',
|
||||
@@ -120,6 +136,8 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "command template CSV",
|
||||
attrs: ["placeholder", "class", "height"],
|
||||
summary: "Interactive command history / tracker panel.",
|
||||
details:
|
||||
"The body is a CSV of command templates (`command,parameter,label,description,insertedText`).",
|
||||
refKind: "csv",
|
||||
examples: [':md-commander[./commands.csv]{placeholder="输入命令…" height="400px"}'],
|
||||
},
|
||||
@@ -128,6 +146,7 @@ export const KNOWN_ELEMENTS: readonly ElementSpec[] = [
|
||||
body: "Yarn Spinner file",
|
||||
attrs: ["start"],
|
||||
summary: "Runs a Yarn Spinner dialogue file.",
|
||||
details: "`start` names the entry node (default `start`).",
|
||||
refKind: "yarn",
|
||||
examples: [':md-yarn-spinner[./story.yarn]{start="start"}'],
|
||||
},
|
||||
@@ -151,11 +170,49 @@ export const COLOR_BODY_ELEMENTS: ReadonlySet<string> = new Set([
|
||||
"md-border",
|
||||
]);
|
||||
|
||||
export interface InlineSpec {
|
||||
/** Directive form, e.g. ":[name]". */
|
||||
name: string;
|
||||
body: string;
|
||||
attrs: string[];
|
||||
summary: string;
|
||||
details?: string;
|
||||
examples: string[];
|
||||
}
|
||||
|
||||
/** Inline directives that are not custom elements. */
|
||||
export const INLINE_SPECS: readonly InlineSpec[] = [
|
||||
{
|
||||
name: ":[name]",
|
||||
body: "icon name",
|
||||
attrs: ["label", "repeat", "join", "class"],
|
||||
summary:
|
||||
"Inserts an icon looked up under `assets/` (searching upward from the file).",
|
||||
details:
|
||||
"Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp` are also supported. `label` overlays text, `repeat` duplicates N times, and `join` places a tag name between repeats.",
|
||||
examples: [":[attack] :[shield.svg] :[potion]", ":[attack]{label=ATK repeat=3 join=br}"],
|
||||
},
|
||||
{
|
||||
name: ":cmd[command]",
|
||||
body: "journal command",
|
||||
attrs: ["label"],
|
||||
summary: "Renders a clickable span that dispatches a journal command.",
|
||||
examples: [
|
||||
":cmd[roll 1d20+5]{label=Roll initiative}",
|
||||
":cmd[spark dungeon room-type]{label=Random room}",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const KNOWN_INLINE: ReadonlySet<string> = new Set(["cmd"]);
|
||||
|
||||
export interface RoleSpec {
|
||||
role: string;
|
||||
/** Example info string, e.g. "csv role=spark-table". */
|
||||
info: string;
|
||||
summary: string;
|
||||
/** Authoring prose rendered under the summary. */
|
||||
details?: string;
|
||||
/** Canonical fenced-block examples, rendered into the reference. */
|
||||
examples: string[];
|
||||
}
|
||||
@@ -166,6 +223,8 @@ export const ROLE_SPECS: readonly RoleSpec[] = [
|
||||
role: "spark-table",
|
||||
info: "csv role=spark-table",
|
||||
summary: "CSV (or markdown pipe table) rendered as `:md-table`.",
|
||||
details:
|
||||
"The first column header must be a dice formula; each row's first cell is the roll value (a number, or a range like `1-3`). Extra fence attributes are forwarded to the generated `:md-table`, e.g. ` ```csv role=spark-table roll=true `.",
|
||||
examples: [
|
||||
"```csv role=spark-table\nd6,结果\n1,遭遇强盗\n2,平安无事\n```",
|
||||
"```markdown role=spark-table\n| d6 | 结果 |\n|----|------|\n| 1 | 遭遇强盗 |\n| 2 | 平安无事 |\n```",
|
||||
@@ -175,6 +234,8 @@ export const ROLE_SPECS: readonly RoleSpec[] = [
|
||||
role: "declare",
|
||||
info: "csv role=declare",
|
||||
summary: "Reactive variable / tag-modifier definitions.",
|
||||
details:
|
||||
"Columns (any order): `tag`, `threshold`, `key`, `expr`. A row with an empty `tag` declares a variable; a row with a `#tag` applies a modifier when that tag is active.",
|
||||
examples: [
|
||||
"```csv role=declare\ntag,threshold,key,expr\n,,$hp,$con*5+$mod_hp\n#warrior,2,$mod_hp,20\n```",
|
||||
],
|
||||
@@ -183,12 +244,16 @@ export const ROLE_SPECS: readonly RoleSpec[] = [
|
||||
role: "file",
|
||||
info: "<lang> role=file",
|
||||
summary: "Stored as inline text content and stripped.",
|
||||
details:
|
||||
"Referenced by the derived inline id, e.g. `:md-embed[./text_<hash>]`.",
|
||||
examples: ["```text role=file\nStored as inline content.\n```"],
|
||||
},
|
||||
{
|
||||
role: "tag",
|
||||
info: "yaml role=tag",
|
||||
summary: "YAML rendered as a custom element.",
|
||||
details:
|
||||
"`tag:` names the element, `body:` becomes its content, and any other key becomes an attribute. Fence-line attributes override the YAML body. See `yaml-tag.md`.",
|
||||
examples: [
|
||||
"```yaml role=tag\ntag: md-deck\nbody: ./cards.csv\nsize: 63x88\ngrid: 5x5\n```",
|
||||
],
|
||||
@@ -199,9 +264,6 @@ export const KNOWN_ROLES: ReadonlySet<string> = new Set(
|
||||
ROLE_SPECS.map((r) => r.role),
|
||||
);
|
||||
|
||||
/** Inline directives that are not custom elements. */
|
||||
export const KNOWN_INLINE: ReadonlySet<string> = new Set(["cmd"]);
|
||||
|
||||
/** Expected file extensions per reference kind. */
|
||||
export const REF_EXTENSIONS: Record<Exclude<RefKind, "none">, readonly string[]> =
|
||||
{
|
||||
@@ -211,6 +273,20 @@ export const REF_EXTENSIONS: Record<Exclude<RefKind, "none">, readonly string[]>
|
||||
image: [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"],
|
||||
};
|
||||
|
||||
export interface DiceNotation {
|
||||
notation: string;
|
||||
meaning: string;
|
||||
}
|
||||
|
||||
/** Dice notation accepted by `:md-dice`, spark-table headers, and `/roll`. */
|
||||
export const DICE_NOTATION: readonly DiceNotation[] = [
|
||||
{ notation: "2d6", meaning: "two six-sided dice" },
|
||||
{ notation: "1d20+5", meaning: "d20 plus modifier" },
|
||||
{ notation: "3d6kh1", meaning: "keep highest 1" },
|
||||
{ notation: "3d6dl2-1", meaning: "drop lowest 2, minus 1" },
|
||||
{ notation: "{d4, d8}", meaning: "choose one of the listed dice" },
|
||||
];
|
||||
|
||||
/** Whether a directive body is a CSS color rather than a path. */
|
||||
export function isColorValue(value: string): boolean {
|
||||
const v = value.trim();
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
* Render the `known.ts` catalog to markdown.
|
||||
*
|
||||
* `docs/reference.md` is generated from this; do not hand-edit it. Regenerate
|
||||
* with `npm run gen:reference`. The skill copy can be refreshed with
|
||||
* `npm run gen:reference -- --out <path>`.
|
||||
* with `npm run gen:reference`. The skill copy is refreshed with
|
||||
* `npm run gen:skill-reference`.
|
||||
*/
|
||||
|
||||
import {
|
||||
COLOR_BODY_ELEMENTS,
|
||||
DICE_NOTATION,
|
||||
INLINE_SPECS,
|
||||
KNOWN_ELEMENTS,
|
||||
ROLE_SPECS,
|
||||
type ElementSpec,
|
||||
type InlineSpec,
|
||||
} from "./known.js";
|
||||
|
||||
/** Wrap `code` in a fenced block, widening the fence when it contains one. */
|
||||
@@ -19,16 +22,26 @@ function fence(code: string, lang = "markdown"): string {
|
||||
return `${ticks}${lang}\n${code}\n${ticks}`;
|
||||
}
|
||||
|
||||
/** `- Body: …` / `- Attributes: …` bullet list shared by all directive specs. */
|
||||
function specBullets(body: string, attrs: string[]): string[] {
|
||||
const lines = [`- Body: ${body}`];
|
||||
if (attrs.length) {
|
||||
lines.push(`- Attributes: ${attrs.map((a) => `\`${a}\``).join(", ")}`);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function elementSection(element: ElementSpec): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`### \`:${element.name}\``);
|
||||
lines.push("");
|
||||
lines.push(element.summary);
|
||||
lines.push("");
|
||||
lines.push(`- Body: ${element.body}`);
|
||||
if (element.attrs.length) {
|
||||
lines.push(`- Attributes: ${element.attrs.map((a) => `\`${a}\``).join(", ")}`);
|
||||
if (element.details) {
|
||||
lines.push(element.details);
|
||||
lines.push("");
|
||||
}
|
||||
lines.push(...specBullets(element.body, element.attrs));
|
||||
if (COLOR_BODY_ELEMENTS.has(element.name)) {
|
||||
lines.push("- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`).");
|
||||
}
|
||||
@@ -40,6 +53,25 @@ function elementSection(element: ElementSpec): string {
|
||||
return lines.join("\n").trimEnd();
|
||||
}
|
||||
|
||||
function inlineSection(spec: InlineSpec): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`### \`${spec.name}\``);
|
||||
lines.push("");
|
||||
lines.push(spec.summary);
|
||||
lines.push("");
|
||||
if (spec.details) {
|
||||
lines.push(spec.details);
|
||||
lines.push("");
|
||||
}
|
||||
lines.push(...specBullets(spec.body, spec.attrs));
|
||||
lines.push("");
|
||||
for (const example of spec.examples) {
|
||||
lines.push(fence(example));
|
||||
lines.push("");
|
||||
}
|
||||
return lines.join("\n").trimEnd();
|
||||
}
|
||||
|
||||
/** Render the full reference document. */
|
||||
export function renderReference(): string {
|
||||
const lines: string[] = [];
|
||||
@@ -52,18 +84,41 @@ export function renderReference(): string {
|
||||
lines.push("Do not edit by hand.");
|
||||
lines.push("");
|
||||
lines.push(
|
||||
"Everything not listed here is standard GFM markdown. Element names are",
|
||||
"Complete catalog of the custom elements, inline directives, and code block",
|
||||
);
|
||||
lines.push("prefixed `md-` and must match exactly.");
|
||||
lines.push(
|
||||
"roles that `tttk` understands. Everything not listed here is standard GFM",
|
||||
);
|
||||
lines.push("markdown. Element names are prefixed `md-` and must match exactly.");
|
||||
lines.push("");
|
||||
|
||||
lines.push("## Contents");
|
||||
lines.push("");
|
||||
lines.push("- [Directive syntax](#directive-syntax)");
|
||||
lines.push("- [Custom elements](#custom-elements)");
|
||||
lines.push("- [Inline directives](#inline-directives)");
|
||||
lines.push("- [Code block roles](#code-block-roles)");
|
||||
lines.push("- [Dice notation](#dice-notation)");
|
||||
lines.push("- [CSV data files](#csv-data-files)");
|
||||
lines.push("");
|
||||
|
||||
lines.push("## Directive syntax");
|
||||
lines.push("");
|
||||
lines.push(fence(":name[content]{key=\"value\" .class #id}", "text"));
|
||||
lines.push(fence(':name[content]{key="value" .class #id}', "text"));
|
||||
lines.push("");
|
||||
lines.push("- `[content]` is the directive body — a path, dice formula, color, etc.");
|
||||
lines.push("- `{key=\"value\"}` sets attributes; `{.class}` and `{#id}` are shorthands.");
|
||||
lines.push("- Container directives use `::: name` … `:::`; nest with `::::`, `:::::`.");
|
||||
lines.push("| Part | Meaning | Example |");
|
||||
lines.push("|---|---|---|");
|
||||
lines.push("| `:name` | Registered element name | `:md-dice` |");
|
||||
lines.push("| `[content]` | Directive body | `[2d6+d8]` |");
|
||||
lines.push("| `{attrs}` | Attributes | `{key=\"attack\"}` |");
|
||||
lines.push("| `{.class}` | Shorthand for `class` | `{.text-red-500}` |");
|
||||
lines.push("| `{#id}` | Shorthand for `id` | `{#my-dice}` |");
|
||||
lines.push("");
|
||||
lines.push("Container directives:");
|
||||
lines.push("");
|
||||
lines.push(fence("::: container\nContent here, including nested directives.\n:dice[1d20]\n:::"));
|
||||
lines.push("");
|
||||
lines.push("Nest with more colons (`::::`, `:::::`).");
|
||||
lines.push("");
|
||||
|
||||
lines.push("## Custom elements");
|
||||
@@ -84,24 +139,10 @@ export function renderReference(): string {
|
||||
|
||||
lines.push("## Inline directives");
|
||||
lines.push("");
|
||||
lines.push("### Icons — `:[name]`");
|
||||
lines.push("");
|
||||
lines.push(
|
||||
"Inserts an icon looked up under `assets/` (searching upward from the file).",
|
||||
);
|
||||
lines.push(
|
||||
"Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp`",
|
||||
);
|
||||
lines.push("are also supported.");
|
||||
lines.push("");
|
||||
lines.push(fence(":[attack] :[shield.svg] :[potion]"));
|
||||
lines.push("");
|
||||
lines.push("### Command links — `:cmd[command]{label=...}`");
|
||||
lines.push("");
|
||||
lines.push("Renders a clickable span that dispatches a journal command.");
|
||||
lines.push("");
|
||||
lines.push(fence(":cmd[roll 1d20+5]{label=Roll initiative}"));
|
||||
for (const spec of INLINE_SPECS) {
|
||||
lines.push(inlineSection(spec));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
lines.push("## Code block roles");
|
||||
lines.push("");
|
||||
@@ -115,6 +156,10 @@ export function renderReference(): string {
|
||||
lines.push("");
|
||||
lines.push(role.summary);
|
||||
lines.push("");
|
||||
if (role.details) {
|
||||
lines.push(role.details);
|
||||
lines.push("");
|
||||
}
|
||||
for (const example of role.examples) {
|
||||
lines.push(fence(example));
|
||||
lines.push("");
|
||||
@@ -128,6 +173,17 @@ export function renderReference(): string {
|
||||
lines.push("stripped.");
|
||||
lines.push("");
|
||||
|
||||
lines.push("## Dice notation");
|
||||
lines.push("");
|
||||
lines.push("Used by `:md-dice`, spark-table headers, and the journal `/roll`:");
|
||||
lines.push("");
|
||||
lines.push("| Notation | Meaning |");
|
||||
lines.push("|---|---|");
|
||||
for (const dice of DICE_NOTATION) {
|
||||
lines.push(`| \`${dice.notation}\` | ${dice.meaning} |`);
|
||||
}
|
||||
lines.push("");
|
||||
|
||||
lines.push("## CSV data files");
|
||||
lines.push("");
|
||||
lines.push(
|
||||
|
||||
Reference in New Issue
Block a user