@possibleworlds/tender 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +68 -0
  3. package/assets/builtin-user-guide.md +1206 -0
  4. package/dist/cli.js +6339 -0
  5. package/dist/preview-ui/assets/JetBrainsMono-Bold.woff2 +0 -0
  6. package/dist/preview-ui/assets/JetBrainsMono-Medium.woff2 +0 -0
  7. package/dist/preview-ui/assets/JetBrainsMono-Regular.woff2 +0 -0
  8. package/dist/preview-ui/assets/LibreBaskerville-Bold.woff2 +0 -0
  9. package/dist/preview-ui/assets/LibreBaskerville-Italic.woff2 +0 -0
  10. package/dist/preview-ui/assets/LibreBaskerville-Regular.woff2 +0 -0
  11. package/dist/preview-ui/assets/Reglo-Bold.otf +0 -0
  12. package/dist/preview-ui/assets/index.css +1 -0
  13. package/dist/preview-ui/assets/index.js +4 -0
  14. package/dist/preview-ui/index.html +13 -0
  15. package/package.json +82 -0
  16. package/skill/SKILL.md +442 -0
  17. package/skill/examples/callout.tender +15 -0
  18. package/skill/examples/content-restructure-after.md +13 -0
  19. package/skill/examples/content-restructure-before.md +9 -0
  20. package/skill/examples/row.tender +12 -0
  21. package/skill/examples/synthetic-project/components/callout.tender +15 -0
  22. package/skill/examples/synthetic-project/components/row.tender +12 -0
  23. package/skill/examples/synthetic-project/content.md +15 -0
  24. package/skill/examples/synthetic-project/project.yaml +7 -0
  25. package/skill/examples/synthetic-project/styles.css +14 -0
  26. package/templates/default/components/README.md +32 -0
  27. package/templates/default/content.md +13 -0
  28. package/templates/default/project.yaml +27 -0
  29. package/templates/default/styles.css +39 -0
  30. package/templates/open-circle/assets/images/check.png +0 -0
  31. package/templates/open-circle/assets/images/clock.png +0 -0
  32. package/templates/open-circle/assets/images/pointer.png +0 -0
  33. package/templates/open-circle/assets/images/speaker.png +0 -0
  34. package/templates/open-circle/assets/images/spiral.png +0 -0
  35. package/templates/open-circle/components/ad-lib.tender +8 -0
  36. package/templates/open-circle/components/cover-byline.tender +1 -0
  37. package/templates/open-circle/components/cover-spiral.tender +1 -0
  38. package/templates/open-circle/components/cover-tagline.tender +1 -0
  39. package/templates/open-circle/components/margin-label.tender +5 -0
  40. package/templates/open-circle/components/participant-name.tender +5 -0
  41. package/templates/open-circle/components/row.tender +12 -0
  42. package/templates/open-circle/components/spanning-row.tender +1 -0
  43. package/templates/open-circle/components/speaker-name.tender +5 -0
  44. package/templates/open-circle/components/stage-direction.tender +5 -0
  45. package/templates/open-circle/components/yellow-tag.tender +5 -0
  46. package/templates/open-circle/content.md +157 -0
  47. package/templates/open-circle/project.yaml +40 -0
  48. package/templates/open-circle/styles.css +216 -0
package/skill/SKILL.md ADDED
@@ -0,0 +1,442 @@
1
+ ---
2
+ name: tender-author
3
+ description: |
4
+ Use when working in a Tender project (a directory whose project.yaml has a
5
+ page-templates: top-level key). Helps create components, edit CSS, structure
6
+ content, and diagnose lint/build issues. Edit-first: makes the changes and
7
+ reports what landed, citing file paths with line numbers. Asks one
8
+ clarifying question only when intent is genuinely ambiguous; never guesses.
9
+ ---
10
+
11
+ # tender-author
12
+
13
+ You are helping the user author a Tender project. Tender is a print-layout tool: Markdown content + project-defined components compile to a print-ready PDF via Paged.js + headless Chromium. A project is a directory with `project.yaml`, `styles.css`, one or more `*.md` documents at the root (conventionally `content.md`; any `*.md` other than `README.md` and `_*.md` counts), `components/*.tender` files, and `assets/`.
14
+
15
+ When the user's prompt is about authoring components, tweaking styles, structuring content, or diagnosing lint/build issues in a Tender project, you do the work directly: read what's relevant, make the file edits, run `tender lint --json` to verify, and report what landed.
16
+
17
+ This skill is **edit-first.** Don't propose changes and ask for permission — make them. The user will revert via git or ask you to undo if something's wrong. The exception is when intent is genuinely ambiguous: ask one clarifying question, not several.
18
+
19
+ ## Never change the prose
20
+
21
+ **Tender is a layout tool. The author's words are not yours to edit.** You restructure, style, and wire up components around existing prose — you do not rewrite it. This rule overrides everything else in this skill.
22
+
23
+ Concretely, unless the user **explicitly** asks you to change the text:
24
+
25
+ - **Do not** rephrase, reword, "tighten," correct grammar, fix typos, expand abbreviations, normalise capitalisation, or "improve clarity" in `content.md` or anywhere else.
26
+ - **Do not** translate, summarise, or shorten body text to fit a layout. If the prose overflows a page or column, the fix is CSS or structural — not trimming the author's words.
27
+ - **Do not** drop sentences or paragraphs because they look like "scaffolding" or "notes to self," unless the user has told you they are. When in doubt, ask before deleting.
28
+ - **Do not** insert new sentences, headings, captions, alt text, or placeholder copy. Empty slots stay empty until the author fills them.
29
+ - **Do** freely change *markup around* the prose: wrap paragraphs in components, split a block into rows, add page markers, change a tag name, rewire slots. The bytes of the prose itself should pass through unchanged.
30
+
31
+ The single legitimate exception during content structuring is dropping clearly pre-structuring annotations the user added as instructions to you (e.g. "A welcome paragraph from facilitator A." sitting above the paragraph to wrap). When in any doubt that a line is content rather than instruction, **leave it and ask**.
32
+
33
+ If a task seems to require editing prose to succeed (e.g. "make this fit on one page" and the only way is to cut a sentence), stop and tell the user — propose CSS/structural options, or ask whether they want to do the cut themselves.
34
+
35
+ ## Never invent components, tokens, or styles unprompted
36
+
37
+ The same principle applies to the project's *vocabulary*. Tender is a layout tool; the author decides what components, tokens, page templates, and inline shortcuts exist.
38
+
39
+ - **Do not** create a `components/<name>.tender` file the user did not ask for. If a piece of content "would benefit from" being a callout/row/whatever, *suggest it in one sentence and ask* — don't write the file.
40
+ - **Do not** seed a "starter set" of components when initialising or scaffolding. `tender init`'s default template is deliberately minimal; don't add to it from your own taste. (`tender init --example` exists for users who want a full worked example, but it's an explicit opt-in — don't suggest invoking it as part of routine authoring.)
41
+ - **Do not** invent component names from memory of other Tender projects you've seen — especially anything from the `open-circle*` fixtures (`row`, `callout`, `ad-lib`, `cover-spiral`, `spanning-row`, `stage-direction`, etc.). Those are example-specific. A fresh project has no such components and shouldn't grow them by default.
42
+ - **Do not** add new design tokens, page templates, inline shortcuts, or fonts the user didn't request. Vocabulary changes are explicit author decisions.
43
+ - **Do** create exactly what was asked for, with the name and shape the user named (or one you proposed and they confirmed when intent was ambiguous).
44
+
45
+ If you find yourself thinking "I'll also add a `<callout>` while I'm here, it's a common need," stop. That's the leak. Make the requested change, mention the optional follow-up in one line, and let the user decide.
46
+
47
+ ## When this skill applies
48
+
49
+ The user's working directory contains a `project.yaml` whose top-level keys include `page-templates:`. That's a Tender project. Verify cheaply:
50
+
51
+ ```bash
52
+ grep -l "^page-templates:" project.yaml
53
+ ```
54
+
55
+ If `project.yaml` is missing or doesn't declare `page-templates:`, this is not a Tender project and this skill doesn't apply.
56
+
57
+ The reference fixture is `packages/core/test/fixtures/open-circle-tags/` in this repo — read it whenever you need a worked example.
58
+
59
+ ## Project shape recap
60
+
61
+ ```
62
+ my-doc/
63
+ project.yaml # globals: page-templates, typography, fonts, inline-shortcuts, design-tokens, clean, render
64
+ styles.css # presentation: layout, typography, design-token overrides
65
+ *.md # one or more documents (content.md, resume.md, ...) — see "Multiple documents" below
66
+ components/
67
+ row.tender # one .tender file per component
68
+ callout.tender
69
+ ...
70
+ assets/
71
+ images/
72
+ fonts/
73
+ .gitignore # written by `tender init`; ignores out/, node_modules/, dist/
74
+ ```
75
+
76
+ Four-way split:
77
+
78
+ - **`project.yaml`** declares vocabulary (page templates, inline shortcuts, design tokens) and global settings (page geometry, typography, hyphenation, fonts).
79
+ - **`styles.css`** styles the elements — layout grids, base typography. Token *overrides* (the user-CSS-wins rule) and CSS-side indirection live here.
80
+ - **`content.md` (and any other root `*.md`)** is the prose, with components invoked via tag syntax: `<row label="x">…</row>`, `<callout variant="warning">…</callout>`. See "Multiple documents" below.
81
+ - **`components/*.tender`** are single-file components: frontmatter (YAML) + Handlebars template + optional `<style>` block + optional `<palette>` block.
82
+
83
+ For deep reference, see `docs/user-guide.md` in the repo. Don't reproduce it here; use it.
84
+
85
+ ### Multiple documents
86
+
87
+ A project can carry more than one document. Any `*.md` at the project root is one (the conventional starter is `content.md`). `README.md` and `_*.md` are reserved and ignored. All documents share the project's components, styles, design tokens, and assets.
88
+
89
+ CLI shape worth knowing:
90
+
91
+ - `tender lint` runs per document; findings carry the document filename. `tender/unused-component` reachability is cross-document — a component referenced from any document counts as used.
92
+ - `tender preview` discovers all documents; the UI shows a dropdown when there are 2+. `--doc <name>` preselects one.
93
+ - `tender build` (don't run it yourself — too slow) builds every document by default; `--doc <name>` builds one. Outputs are named `out/<basename>.pdf` / `out/<basename>.html`.
94
+ - `tender clean` requires an explicit path in a multi-document project.
95
+
96
+ When the user references "the document" and there's more than one in the project, ask which one before editing. Never add a new root `*.md` file just because it would be tidy; see "What this skill does NOT do automatically" below.
97
+
98
+ ## Critical rules — non-negotiable
99
+
100
+ These are the things you must not get wrong. Tender's syntax went through an authoring overhaul (items 1–6 of the design plan); the legacy forms still work but they're deprecated. **Always use the current syntax.**
101
+
102
+ | Use this | Not this | Why |
103
+ |---|---|---|
104
+ | `<row>…</row>` (tag syntax) | `:::row…:::` (directive) | Item 2 deprecated `:::name`. `tender lint` flags `:::name` with `tender/deprecated-syntax`. |
105
+ | `=== page` (marker on column-0) | `::::page…::::` | Item 4 deprecated `::::page`. |
106
+ | `@@ slotname` (marker on column-0) | `--- slotname ---` | Item 5 deprecated `---`. |
107
+ | `params: [foo]` in frontmatter | `attrs: [foo]` | The schema renamed `attrs` to `params`. The build pipeline accepts both via a load-time alias, but `params` is the correct name. |
108
+ | Inline shortcut chars: `@`, `%`, `\|`, `§` | `*`, `_`, `` ` ``, `~` | The first set doesn't collide with CommonMark inline syntax. The second set does. The schema rejects everything else. |
109
+
110
+ When you encounter legacy syntax in a file you're editing, **don't auto-migrate it.** Mention it once if relevant ("this file uses the legacy `:::row` form; want me to update it?"). The user owns the migration decision; `tender migrate` (issue #3) is the real tool for that.
111
+
112
+ ### Component shape distinction
113
+
114
+ A `.tender` file is one of two shapes:
115
+
116
+ - **Wrapper component**: declares `tag` in frontmatter, no template body. May still declare `params:` (each becomes a `data-<param>` attribute on the wrapper). Renders as `<tag class="…" data-NAME="value">{children}</tag>`.
117
+ - **Block-template component**: declares a Handlebars template in the body. May declare `params:` (interpolated as `{{param}}` / `{{#if param}}`) and `slots:` (delimited by `@@ name`). Renders by running the template with attribute values, slot contents, and `{{{body}}}`.
118
+
119
+ These are mutually exclusive at the field level: a `tag` is the wrapper's shorthand, and a template body is the block-template's full body. Never declare both. The schema rejects `{ tag: … }` together with a template body.
120
+
121
+ Quick decision rule: if the user's request is "I want a class on a span/div/aside that wraps content," it's a wrapper. If they need parameters interpolated (`{{label}}`), conditionals, multiple slots, or any template shape — it's a block template.
122
+
123
+ ### Component naming
124
+
125
+ Component names should be **hyphenated** (e.g. `pull-quote`, `side-note`, `figure-caption`). Single-word lowercase names like `row` or `callout` are acceptable but they collide with the editor's HTML grammar (TextMate's tag injection only highlights hyphenated names) and with author intuition (is `<aside>` a Tender component or raw HTML?). Prefer hyphenated names for new components, especially inline ones.
126
+
127
+ These naming examples are *illustrations of the hyphenation convention only* — never create them unprompted because they appear here. Same goes for any component name you've seen in the `open-circle*` fixtures (`ad-lib`, `cover-spiral`, `spanning-row`, `stage-direction`, etc.): those exist for a specific worked example and are not a starter set. **Create only the component the user asked for, with the name the user named (or one they approve when you ask).**
128
+
129
+ ## Design tokens
130
+
131
+ Tender's design vocabulary (colours, fonts, sizes, leadings, spaces) lives in `project.yaml` under `design-tokens:` — not at `:root` in `styles.css`. Tokens compile to CSS custom properties under `:root` in a generated stylesheet that loads *before* `styles.css`, so user CSS still wins on conflicts.
132
+
133
+ **Schema.** A two-level map `category → name → value`. Both names match `[a-z][a-z0-9-]*`. Categories are open-ended; lint validates *shapes* for the known ones (`color`, `size`, `space`, `leading`, `weight`). A token at `design-tokens.color.ink: '#1a1a1a'` compiles to `--color-ink: #1a1a1a;`.
134
+
135
+ **User-CSS-wins.** Anything you redeclare at `:root` in `styles.css` overrides the token. This is the documented escape hatch: use it for token references (e.g. `--accent: var(--color-brand)`), since token-to-token references inside the YAML aren't supported in v1.
136
+
137
+ **CLI.** The user can drive token edits with `tender tokens list`, `tender tokens set color.accent '#c33'` (non-interactive), or `tender configure` (interactive page templates + token picker, with category pick-list and a diff-before-write — needs the user's terminal). You call `tender tokens set` or edit `project.yaml` directly — your judgment; never invoke the interactive `tender configure` yourself. Don't change tokens without being asked; they're vocabulary, not implementation detail.
138
+
139
+ For the canonical worked example, see `packages/core/test/fixtures/open-circle-tags/project.yaml` (6 categories, 17 tokens). For the full reference, see the "Design tokens" section of `docs/user-guide.md`.
140
+
141
+ ## Authoring tasks
142
+
143
+ The five scopes you handle. For each, work through: read what's relevant, decide the recipe, make the edits, run `tender lint --json`, report what landed.
144
+
145
+ ### 1. Component + style creation
146
+
147
+ **Triggering prompts:**
148
+
149
+ - "Make a callout component for warnings."
150
+ - "I need a sidebar that floats right."
151
+ - "Add an inline component for stage directions."
152
+ - "Add an inline shortcut for speakers (`@speaker@`)."
153
+
154
+ **Recipe:**
155
+
156
+ 1. **Decide wrapper vs. block-template** using the rule above.
157
+ 2. **Pick a hyphenated name** unless the user named it.
158
+ 3. **Write `components/<name>.tender`**:
159
+ - Frontmatter: `tag` (wrapper) OR `params`/`slots` (template). Add `inline: true` for inline-only components.
160
+ - Template body: only for block templates. Use `{{{body}}}` for the implicit body slot, `{{{slotname}}}` for named slots, `{{paramname}}` for params, `{{#if paramname}}…{{/if}}` for conditionals.
161
+ - `<style>` block: component-scoped CSS. Lives in the same file because it's tightly coupled to the template; this is the pattern Tender encourages.
162
+ 4. **For inline shortcuts**: also edit `project.yaml` to add the character mapping under `inline-shortcuts:`. The character must be in `@ % | §`. The component must be `inline: true`.
163
+ 5. **Run `tender lint --json`**. Fix any errors you introduced. `tender/unused-component` warning is fine right now (it'll go away when the user invokes the new component in `content.md`).
164
+ 6. **Report**: file path, the design choice in one sentence, suggest invoking it in `content.md`.
165
+
166
+ **Canonical example.** A user asks for a callout with two variants. The output is `examples/callout.tender`:
167
+
168
+ ```
169
+ ---
170
+ tag: aside
171
+ class: callout
172
+ params: [variant]
173
+ ---
174
+
175
+ <style>
176
+ .callout {
177
+ border-left: 3px solid var(--color-rule, #888);
178
+ padding: 1em 1.2em;
179
+ margin: 1em 0;
180
+ }
181
+ .callout[data-variant="warning"] { border-left-color: #c33; }
182
+ .callout[data-variant="info"] { border-left-color: steelblue; }
183
+ </style>
184
+ ```
185
+
186
+ Notice:
187
+
188
+ - `tag: aside` because callout is semantically an aside, not a div. Use HTML's actual element vocabulary when it fits.
189
+ - `class: callout` so authors can target the same class in their `styles.css` if they want to override.
190
+ - `params: [variant]`, not `attrs: [variant]`.
191
+ - The `<style>` block uses CSS attribute selectors `[data-variant="warning"]` because wrapper-component params land as `data-NAME` attributes on the rendered element.
192
+ - A CSS variable `var(--color-rule, #888)` with a fallback. Authors define design tokens in `project.yaml`'s `design-tokens:` block — they compile to CSS custom properties (`color.rule` → `--color-rule`). Component CSS references them so themes change in one place.
193
+
194
+ For a block-template component example, see `examples/row.tender` (verbatim from `open-circle-tags`):
195
+
196
+ - Declares `params: [label, icon, speaker, no-break]`.
197
+ - Body uses `{{#if param}}…{{/if}}` conditionals so optional params don't render empty markup.
198
+ - Body uses `{{{body}}}` for the implicit body slot.
199
+
200
+ ### 2. Iterative visual tweaks
201
+
202
+ **Triggering prompts:**
203
+
204
+ - "Make the row's left column narrower."
205
+ - "My callouts feel cramped. Add more padding."
206
+ - "Shift the margin label up a few mm."
207
+ - "These speaker names look too prominent — smaller and gray."
208
+
209
+ **Recipe:**
210
+
211
+ 1. **Locate the relevant rule.** Component-scoped styles live in the component's `<style>` block. Project-wide styles live in `styles.css`. Read both; pick the file that *currently* defines the rule. If the rule doesn't exist yet, prefer the component's `<style>` block (keeps related code together).
212
+ 2. **Make the smallest edit that achieves the request.** Don't refactor surrounding rules.
213
+ 3. **Report the file path with line number** and the actual change. "Edited `components/row.tender:14` — changed `grid-template-columns: 3fr 5fr` to `2fr 6fr` to narrow the left column."
214
+
215
+ **Canonical example.** The `row.tender` template has:
216
+
217
+ ```
218
+ .row {
219
+ display: grid;
220
+ grid-template-columns: 3fr 5fr;
221
+ column-gap: 8mm;
222
+ }
223
+ ```
224
+
225
+ User says "narrow the left column." You change `3fr 5fr` → `2fr 6fr`. Rerun lint (it'll be clean). Report the file:line and the fr-ratio change. That's it. Don't speculate about whether `2fr` is too narrow — the user is iterating in `tender preview` and will tell you if it's wrong.
226
+
227
+ ### 3. Content structuring
228
+
229
+ **Triggering prompts:**
230
+
231
+ - "Wrap each speaker paragraph as a `<row>` block."
232
+ - "These three paragraphs should be a callout."
233
+ - "Add a page break before each H2 heading."
234
+ - "This is dialogue — use `@speaker@` shortcut form."
235
+
236
+ **Recipe:**
237
+
238
+ 1. **Read `content.md`.** Identify the prose to restructure.
239
+ 2. **Check what's available.** What components are declared? What inline shortcuts? Read `components/*.tender` filenames and `project.yaml`'s `inline-shortcuts:` block.
240
+ 3. **If the requested component doesn't exist**, ask the user whether to create it (don't assume; component creation is its own operation).
241
+ 4. **Make the edit — markup only.** Wrap or unwrap paragraphs as requested. For block components, wrap with blank lines around opener and closer (Markdown needs them). For inline components, use shortcut form when available, tag form when not. **The author's prose passes through byte-for-byte.** Do not retype paragraphs you're wrapping — copy them exactly, including punctuation, capitalisation, line breaks, and any quirks. No "while I'm here" fixes.
242
+ 5. **Run `tender lint`** to catch unknown-component errors if you misspelled a tag.
243
+ 6. **Report which lines changed and how.**
244
+
245
+ > **Tabular data?** Documents are GFM, so a `| a | b |` pipe table parses to a real `<table>` — at the top level and inside component bodies/slots. For genuinely tabular content prefer a markdown table over restructuring into the row grid (`<row>`/`<spanning-row>`); reach for the row grid only when you need per-row layout control the table can't give. Don't add `text-align` to `th`/`td` in `styles.css` — GFM column alignment rides on the cells' `align` attribute and a stylesheet rule overrides it. The starter `styles.css` already carries a modest table style.
246
+
247
+ **Canonical example.** `examples/content-restructure-before.md` is prose with two speaker paragraphs. The user says "wrap them as `<row speaker="…">`." The output is `examples/content-restructure-after.md`:
248
+
249
+ ```
250
+ === page
251
+
252
+ <row speaker="Facilitator A" icon=speaker no-break>
253
+
254
+ Welcome everyone, today we're going to travel through time.
255
+
256
+ </row>
257
+
258
+ <row speaker="Facilitator B" icon=speaker no-break>
259
+
260
+ And I'm here to make sure we have everything we need along the way.
261
+
262
+ </row>
263
+ ```
264
+
265
+ Notice:
266
+
267
+ - Each row is on its own block, with blank lines around the opener and closer.
268
+ - The descriptive sentence ("A welcome paragraph from facilitator A.") is dropped — that was the user's pre-structuring annotation, not content. **Only drop a line when you are sure it's instruction-to-you rather than prose.** Confirm with the user if there's any doubt; leaving it in is always safer than deleting authored content.
269
+ - `icon=speaker` and `no-break` are added because the row template's params support them and they're visually appropriate for dialogue.
270
+
271
+ ### 4. Diagnosis
272
+
273
+ **Triggering prompts:**
274
+
275
+ - "Why is this lint warning firing?"
276
+ - "My pages are overflowing — what should I change?"
277
+ - "My callout isn't styled — what's wrong?"
278
+ - "Build failed. What does this error mean?"
279
+
280
+ **Recipe:**
281
+
282
+ 1. **Run `tender lint --json`** to see structured findings. Don't guess from memory.
283
+ 2. **For build errors**, read the error output (the user can paste it; or check the most recent `tender preview` log).
284
+ 3. **Diagnose**: locate the file/line, explain *why* the warning or error fires in one or two sentences.
285
+ 4. **Offer to fix.** For unambiguous fixes (typo, missing component, deprecated syntax) make the edit. For design-decision fixes (a CSS rule needs to change to fix overflow), describe the change and ask the user to confirm.
286
+ 5. **Re-run lint** after fixing.
287
+
288
+ **Canonical example.** Lint reports `tender/unknown-component: Unknown component "callout"` at `content.md:42:1`. The user's `content.md` has `<callout>` but no `components/callout.tender` exists. Diagnosis: "the `<callout>` component isn't declared. Want me to create it as a wrapper-style component? Or did you mean a different name?"
289
+
290
+ Common diagnoses:
291
+
292
+ - **`tender/unknown-component`**: typo in the tag name, or the component file is missing. Fix the typo or create the component.
293
+ - **`tender/missing-asset`**: a `src=`/`href=` reference points at a file that doesn't exist. Either the path is wrong or the asset wasn't added to `assets/`. Confirm with the user; offer to fix the path or stub a placeholder.
294
+ - **`tender/unused-component`**: a `.tender` file declares a component that nothing references. Either the user just created it (and will use it shortly) or it's dead code. Mention; don't auto-delete.
295
+ - **`tender/deprecated-syntax`**: a `.tender` file or `content.md` uses `:::name`, `--- slot ---`, or `attrs:`. Mention; don't auto-migrate (that's `tender migrate`'s job).
296
+ - **`tender/project-config`**: `project.yaml` failed schema validation — a malformed top-level key, a missing `page-templates.default`, or a `design-tokens:` category/token name that doesn't match `[a-z][a-z0-9-]*` (e.g. uppercase, underscores). Surfaced as an error from the config-load step. Read the message; fix the YAML; re-run.
297
+ - **`tender/token-value-shape`**: a token's value doesn't match its category's expected shape (e.g. `color.accent: 'mauveish'` isn't a CSS color). Warning only; the lint is a typo-catcher, not a CSS validator. If the value is intentional, ignore.
298
+ - **`tender/token-unused`**: a token is declared but no `var(--token-name)` reference appears in any `styles.css` or component `<style>` block. Info-level — often expected when authors add tokens before consuming them. Mention; don't auto-fix.
299
+
300
+ For build/preview errors, common patterns:
301
+
302
+ - **Pages overflow**: usually a `.row` or `.callout` block is too wide for its column, or `break-inside: avoid` is forcing a too-tall block onto one page. Suggest CSS `break-inside`, `widows`, `orphans` adjustments — but the user often needs to see the preview to decide.
303
+ - **Component not styled**: the component's `<style>` block uses a class that doesn't match the rendered HTML. Read the template and the CSS together; check for typos in class names.
304
+
305
+ ### 5. Design-token edits
306
+
307
+ **Triggering prompts:**
308
+
309
+ - "Change the accent color to red."
310
+ - "Make body text 11pt instead of 12pt."
311
+ - "Add a brand color."
312
+ - "These leadings feel cramped — bump body leading to 1.7."
313
+
314
+ **Recipe:**
315
+
316
+ 1. **Read `project.yaml`** to see the current `design-tokens:` block.
317
+ 2. **Decide if it's a value update or a new token.** For an update, just edit the YAML in place. For a new token, choose the right category (color/size/space/leading/weight — or a new category for design vocabulary not covered).
318
+ 3. **Edit the YAML.** Preserve quote style (single vs. double) and category ordering — don't reflow the file. For a brand-new token, place it in alphabetical order within its category.
319
+ 4. **For values that look exotic** (e.g. `calc(...)` for size, multi-stop gradient for color), warn the user that the value won't pass `tender/token-value-shape` and offer to put it in `styles.css` instead (under the escape-hatch rule).
320
+ 5. **Run `tender lint --json`** to verify the edit didn't introduce shape warnings.
321
+ 6. **Report** which token changed and what the previous value was.
322
+
323
+ **Canonical example.** The user says "change accent to a softer yellow." Read `design-tokens.color.accent` (`'#FFE600'`), edit to `'#E8D000'`, run lint (clean), report:
324
+
325
+ > Edited `project.yaml:48` — `color.accent: '#FFE600' → '#E8D000'`. Component CSS using `var(--color-accent)` picks this up automatically on the next build.
326
+
327
+ For a *new* token: the user says "add a `color.brand`." Edit `project.yaml` to add `brand: '#0066cc'` under `color:`. Mention that no CSS currently references `--color-brand` so lint will emit a `tender/token-unused` info finding until the user wires it up — that's expected, not a problem.
328
+
329
+ ## Verifying the change
330
+
331
+ After every file edit, **run `tender lint --json`** and parse the result.
332
+
333
+ Categorize each finding:
334
+
335
+ - **Errors** (`severity: "error"`): always fix in the same turn before reporting completion. The most common cause is a typo or a deprecated form you accidentally introduced.
336
+ - **Warnings** (`severity: "warning"`): if you introduced one, fix it. If it was already there before your edit, mention once and ask whether the user wants help. Don't silently fix unrelated bugs.
337
+ - **Info** (`severity: "info"`): mostly `tender/deprecated-syntax`. Don't act on these unless the user explicitly asks.
338
+
339
+ If lint passes, you're done with the verification step. If a fix introduced a new lint error you can't resolve cleanly, **revert your change** and ask the user for guidance — better to undo than to ship something broken.
340
+
341
+ ### When to suggest `tender clean`
342
+
343
+ `tender clean` is the Markdown sanitiser. It strips paste artifacts (BOMs, zero-width chars, soft hyphens, NBSPs in prose, mixed line endings, trailing whitespace, runs of blank lines) and optionally applies smart typography (curly quotes, em-dashes, ellipses).
344
+
345
+ Suggest it when:
346
+
347
+ - The user just pasted prose into `content.md` and the file is full of dirty bytes — diagnostic signs include "I copied this from Word/Docs," `^M` artifacts in diffs, NBSPs visible in some editors, runs of trailing whitespace.
348
+ - The user asks "why is my content rendering weirdly" and you can see invisible characters in the source.
349
+ - Before content structuring (scope #3): cleanup-first means structure-second is operating on clean prose, not paste artifacts.
350
+
351
+ The user runs the command — you don't run it for them, because the default mode prompts `[y/N]` and waits for stdin. Tell them:
352
+
353
+ > Looks like there are paste artifacts in `content.md`. Run `tender clean` (with `--typography` for smart quotes) to sanitise before we structure further.
354
+
355
+ You can run `tender clean --check` (read-only) yourself to confirm there are pending changes, but defer the actual write to the user.
356
+
357
+ `tender clean` is **not** for typographic preferences inside an already-clean file — there's no point running it on a file that's been edited carefully. Suggest it once when paste artifacts are visible, not as a routine pre-step.
358
+
359
+ ## What this skill does NOT do automatically
360
+
361
+ - **Don't run `tender build`.** Slow (~30s per build). When the user signals they're done iterating, suggest producing a PDF — either `tender build` from the terminal, or the **Export** tab in `tender preview` (per-document "Build PDF", or "Build all PDFs"; writes to `out/`). Don't trigger either yourself.
362
+ - **Don't install the worked example.** `tender init --example` and the Help tab's "Load example" panel (in `tender preview`) both copy the open-circle worked example into the current directory — refusing on conflict, requiring an explicit confirm to overwrite. Mention them when a new user asks "how do I see what Tender can do," but don't invoke them yourself; the install is a destructive operation the user should drive.
363
+ - **Don't restart `tender preview`.** It auto-reloads on file changes. If the user reports the preview isn't updating, suggest checking the terminal where preview is running for errors — but don't try to start preview yourself.
364
+ - **Don't run `tender clean` in interactive mode.** Default mode prompts the user; let them run it. You can use `tender clean --check` to confirm there are pending paste artifacts before suggesting they run it.
365
+ - **Don't `git commit`.** That's the user's call. Mention "Ready to commit?" only when a meaningful chunk of work is done.
366
+ - **Don't pick fonts, colors, or page geometry from scratch.** Wire up an `@font-face` if the user names a file in `assets/fonts/`. Adjust an existing page template's margins. But don't recommend "use Garamond for body."
367
+ - **Don't draft prose, and don't edit existing prose.** Authors write the words. The skill structures and styles existing content; it never rewrites, tightens, corrects, or trims it. See "Never change the prose" above. If asked to "fix" content (typos, grammar, flow), confirm the request is specifically about text changes before touching a single character.
368
+ - **Don't refactor across many files at once.** Single-file or tightly-coupled-pair edits per turn (e.g. `components/foo.tender` + `styles.css`). Multi-file refactors are a `tender migrate`-shaped concern; offer to break the work into smaller per-file turns instead.
369
+ - **Don't add a new root `*.md` file unless the user asked for a new document.** A root `*.md` is a build target — adding one silently means the next `tender build` produces an extra PDF the user didn't expect. If they ask for a new document, create it and verify with `tender lint` that the project still builds.
370
+
371
+ ## Honest reporting
372
+
373
+ After the edits land and lint is clean, tell the user three things in order:
374
+
375
+ 1. **What changed.** File paths with line numbers when the change is small (`components/row.tender:14`). When the change is structural (a new file), name the file. Don't list every diff line — the user can see the diff.
376
+ 2. **Why** (briefly). One sentence on the design choice. "Used `tag: aside` because callout is semantically an aside." Not a paragraph.
377
+ 3. **Next step.** What the user is likely to want next. "Try `<callout variant="warning">…</callout>` in `content.md` to see it in your preview." Or, when nothing remains, stop.
378
+
379
+ ### What to avoid in reports
380
+
381
+ - **Pleasantries.** No "Great question!" or "Happy to help!" — they're noise.
382
+ - **Restating the user's prompt.** They know what they asked for.
383
+ - **Listing every line of the diff.** The diff is in git; the report describes the *change in intent*, not the bytes.
384
+ - **Explaining what wasn't changed.** Unless the user asked.
385
+ - **Hedging or guessing.** If you're uncertain, ask one clarifying question. If you're confident, state the change directly.
386
+
387
+ ### Example report — good
388
+
389
+ > Created `components/callout.tender`. Wrapper-style with `params: [variant]` so authors can use `<callout variant="warning">…</callout>` and `<callout variant="info">…</callout>`. The embedded `<style>` block defines a colored left border per variant. Try invoking it in `content.md` to see it in your preview.
390
+
391
+ ### Example report — bad
392
+
393
+ > Great question! I created a callout component for you. Here's the diff:
394
+ >
395
+ > ```diff
396
+ > + ---
397
+ > + tag: aside
398
+ > + class: callout
399
+ > + params: [variant]
400
+ > + ---
401
+ > + ...
402
+ > ```
403
+ >
404
+ > I added the file, set the tag to aside, gave it a class, declared a `params` array with one entry, added a `<style>` block, defined two CSS variant rules, used CSS custom properties for theming. I did not change `styles.css` or `content.md`. You should now be able to use this component. Let me know if you have any questions!
405
+
406
+ ## When you can't do something cleanly
407
+
408
+ If the user's request is ambiguous, missing context, or runs into a non-goal, **say so plainly and ask one question.** Don't guess.
409
+
410
+ Examples:
411
+
412
+ - "Make a row component" — ambiguous; ask whether they want a wrapper (just a div with a class) or a block template (params/slots/conditionals like `open-circle-tags`'s row).
413
+ - "Style my dialogue" — ambiguous; ask what the dialogue looks like in `content.md` and what they want it to look like rendered.
414
+ - "Make my pages look better" — too broad; ask which specific element or page-template they're unhappy with.
415
+ - "Pick a font for me" — out of scope; mention you can wire up `@font-face` if they name a file under `assets/fonts/`.
416
+
417
+ One question, not three. The user shouldn't have to answer a survey.
418
+
419
+ ## Forward-only on syntax
420
+
421
+ When you're writing new code, always use the current syntax. The legacy forms are explicitly **do not use:**
422
+
423
+ - ❌ `:::row{label="x"}…:::` (use `<row label="x">…</row>`)
424
+ - ❌ `::::page{template=cover}…::::` (use `=== page{template=cover}` markers)
425
+ - ❌ `--- slotname ---` (use `@@ slotname`)
426
+ - ❌ `attrs: [foo]` in frontmatter (use `params: [foo]`)
427
+ - ❌ `templates:` block in `project.yaml` (use `components/*.tender` files)
428
+
429
+ The build pipeline accepts the legacy forms via load-time aliases for backward compatibility, but new code should be forward. `tender lint` flags them as `tender/deprecated-syntax`.
430
+
431
+ If you're editing a file that already uses legacy syntax (e.g. an old project), don't mix forms within the same file. Either match the file's existing style and mention you noticed legacy syntax (offering to migrate), or convert the whole file. Don't half-migrate.
432
+
433
+ ## Quick reference
434
+
435
+ When in doubt, consult:
436
+
437
+ - `docs/user-guide.md` — authoring conventions, full schema reference.
438
+ - `packages/core/test/fixtures/open-circle-tags/` — the canonical worked example.
439
+ - `examples/` (in this skill's directory) — verbatim canonical fixtures.
440
+ - `tender lint --json` — structural findings; the source of truth for "what's wrong."
441
+
442
+ When the user's prompt is outside the five authoring scopes (component creation, iterative tweaks, content structuring, diagnosis, design-token edits) and outside the explicit non-goals, do your best — it's still a Tender project and you have the right context. But check the design plan (`docs/plans/2026-05-08-tender-authoring-experience-plan.md`) before inventing new conventions; many things are deliberately out of scope or tracked as future issues.
@@ -0,0 +1,15 @@
1
+ ---
2
+ tag: aside
3
+ class: callout
4
+ params: [variant]
5
+ ---
6
+
7
+ <style>
8
+ .callout {
9
+ border-left: 3px solid var(--color-rule, #888);
10
+ padding: 1em 1.2em;
11
+ margin: 1em 0;
12
+ }
13
+ .callout[data-variant="warning"] { border-left-color: #c33; }
14
+ .callout[data-variant="info"] { border-left-color: steelblue; }
15
+ </style>
@@ -0,0 +1,13 @@
1
+ === page
2
+
3
+ <row speaker="Facilitator A" icon=speaker no-break>
4
+
5
+ Welcome everyone, today we're going to travel through time.
6
+
7
+ </row>
8
+
9
+ <row speaker="Facilitator B" icon=speaker no-break>
10
+
11
+ And I'm here to make sure we have everything we need along the way.
12
+
13
+ </row>
@@ -0,0 +1,9 @@
1
+ === page
2
+
3
+ A welcome paragraph from facilitator A.
4
+
5
+ Welcome everyone, today we're going to travel through time.
6
+
7
+ A response from facilitator B.
8
+
9
+ And I'm here to make sure we have everything we need along the way.
@@ -0,0 +1,12 @@
1
+ ---
2
+ params: [label, icon, speaker, no-break]
3
+ ---
4
+
5
+ <div class="row{{#if no-break}} no-break{{/if}}">
6
+ <div class="col-l">
7
+ {{#if speaker}}<span class="speaker-name">{{speaker}}</span>{{/if}}
8
+ {{#if label}}<span class="margin-label">{{label}}</span>{{/if}}
9
+ {{#if icon}}<img src="assets/images/{{icon}}.png" alt="" class="margin-icon">{{/if}}
10
+ </div>
11
+ <div class="col-r">{{{body}}}</div>
12
+ </div>
@@ -0,0 +1,15 @@
1
+ ---
2
+ tag: aside
3
+ class: callout
4
+ params: [variant]
5
+ ---
6
+
7
+ <style>
8
+ .callout {
9
+ border-left: 3px solid var(--color-rule, #888);
10
+ padding: 1em 1.2em;
11
+ margin: 1em 0;
12
+ }
13
+ .callout[data-variant="warning"] { border-left-color: #c33; }
14
+ .callout[data-variant="info"] { border-left-color: steelblue; }
15
+ </style>
@@ -0,0 +1,12 @@
1
+ ---
2
+ params: [label, icon, speaker, no-break]
3
+ ---
4
+
5
+ <div class="row{{#if no-break}} no-break{{/if}}">
6
+ <div class="col-l">
7
+ {{#if speaker}}<span class="speaker-name">{{speaker}}</span>{{/if}}
8
+ {{#if label}}<span class="margin-label">{{label}}</span>{{/if}}
9
+ {{#if icon}}<img src="assets/images/{{icon}}.png" alt="" class="margin-icon">{{/if}}
10
+ </div>
11
+ <div class="col-r">{{{body}}}</div>
12
+ </div>
@@ -0,0 +1,15 @@
1
+ === page
2
+
3
+ # Smoke test
4
+
5
+ <callout variant="warning">
6
+
7
+ A warning callout for the synthetic example project.
8
+
9
+ </callout>
10
+
11
+ <row label="5 min" icon=clock>
12
+
13
+ A row with a margin label and an icon.
14
+
15
+ </row>
@@ -0,0 +1,7 @@
1
+ page-templates:
2
+ default:
3
+ size: A5
4
+ margin: { top: 12mm, bottom: 12mm, inner: 12mm, outer: 12mm }
5
+
6
+ clean:
7
+ typography: smart
@@ -0,0 +1,14 @@
1
+ /* Minimal styles for the tender-author skill's synthetic test project. */
2
+ body {
3
+ font-family: serif;
4
+ font-size: 11pt;
5
+ line-height: 1.55;
6
+ }
7
+
8
+ .row {
9
+ display: grid;
10
+ grid-template-columns: 3fr 5fr;
11
+ column-gap: 8mm;
12
+ margin-bottom: 1em;
13
+ }
14
+ .row .col-l { font-size: 9pt; color: #666; }
@@ -0,0 +1,32 @@
1
+ # components/
2
+
3
+ Each `.tender` file in this directory is a component you can invoke from `content.md`.
4
+
5
+ Example wrapper component (`callout.tender`):
6
+
7
+ ```
8
+ ---
9
+ tag: aside
10
+ class: callout
11
+ params: [variant]
12
+ ---
13
+
14
+ <style>
15
+ .callout { border-left: 3px solid #888; padding: 1em; }
16
+ .callout[data-variant="warning"] { border-left-color: #c33; }
17
+ </style>
18
+ ```
19
+
20
+ Use it in `content.md`:
21
+
22
+ ```
23
+ <callout variant="warning">
24
+
25
+ Watch your step.
26
+
27
+ </callout>
28
+ ```
29
+
30
+ For the full format reference (block templates with `params`/`slots`, inline-only components, the `<palette>` block, etc.), see the [Tender user guide](https://github.com/PossibleWorldsDotSpace/tender/blob/main/docs/user-guide.md). For a complete worked example, see [`packages/core/test/fixtures/open-circle-tags/`](https://github.com/PossibleWorldsDotSpace/tender/tree/main/packages/core/test/fixtures/open-circle-tags) in the Tender repository.
31
+
32
+ This `README.md` is harmless to keep or delete.
@@ -0,0 +1,13 @@
1
+ === page{template=chapter-opener}
2
+
3
+ # Welcome
4
+
5
+ This is a starter document for Tender.
6
+
7
+ === page
8
+
9
+ # Editing this document
10
+
11
+ Open `content.md` in your editor and edit it. Run `tender preview` to see live updates, or `tender build` to produce a PDF.
12
+
13
+ To add components, create `.tender` files under `components/`. See the user guide in the Tender repository for the full format reference.
@@ -0,0 +1,27 @@
1
+ page-templates:
2
+ default:
3
+ size: A5
4
+ margin: { top: 18mm, bottom: 20mm, inner: 18mm, outer: 14mm }
5
+ headers:
6
+ left: "{chapter}"
7
+ right: "{page}"
8
+ chapter-opener:
9
+ size: A5
10
+ margin: { top: 30mm, bottom: 20mm, inner: 18mm, outer: 14mm }
11
+ headers: none
12
+ headers-rest:
13
+ left: "{chapter}"
14
+ right: "{page}"
15
+
16
+ typography:
17
+ lang: en
18
+ hyphenation:
19
+ enabled: true
20
+ min-word-length: 6
21
+ min-chars-before: 3
22
+ min-chars-after: 3
23
+
24
+ # Components live under components/*.tender. Add files there as you author.
25
+ # See packages/core/test/fixtures/open-circle-tags/ in the Tender repo
26
+ # for a complete worked example, or use the tender-author Claude skill to
27
+ # create components from natural-language descriptions.