Skip to content

Commit dbf2b26

Browse files
dmealingclaude
andcommitted
Merge: linked, syntax-highlighted template source in meta docs
The meta docs template.output/template.prompt page now embeds the actual Mustache SOURCE -- syntax-highlighted, with every variable hot-linked to that field's documentation: a fenced mustache code block (viewer-highlighted), a Variables table linking each variable to its field doc (Owner.field -> Owner.md#field-NAME), and a collapsible rich HTML Linked view (colored, inline-styled, clickable variables). Both color + links from one annotated model; email renders per-part. Reuses the render verify() variable-to-field resolution (exported, byte-identical -- verify behavior unchanged) so the doc can never claim a link verify would reject (gated). Per-field anchors on the neutral entity page are the link targets via one shared slug helper (anchor + link cannot drift). A link-integrity gate asserts every linked field has a real anchor on the emitted entity page (cross-VO links included) + annotator/verify agree. meta docs (neutral Tier-2, TS) only; render engine unchanged; rich HTML escaped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents 5c4fa67 + 1a527ee commit dbf2b26

39 files changed

Lines changed: 2107 additions & 33 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Linked template source in meta docs — implementation plan
2+
3+
> REQUIRED SUB-SKILL: superpowers:subagent-driven-development.
4+
5+
**Goal:** Embed the Mustache template source in the `meta docs` template page —
6+
syntax-highlighted, with every `{{variable}}` linked to its field's doc — in two
7+
renderings from one model (Markdown block + variables link table; rich inline-
8+
linked HTML). Reuse the render `verify()` variable→field mapping. TS / meta docs
9+
(neutral Tier-2) only. Decisions (resolved): rich view = collapsed `<details>`;
10+
anchor slug = `field-<name>` prefixed; partials link to the referenced template
11+
page when it's a documented template.output, else highlight-only.
12+
13+
## Reference (read first)
14+
- `server/typescript/packages/render/src/verify.ts``Mustache.parse` tokens +
15+
the context-aware field resolution (`lookup`/`current`); `PayloadField` tree.
16+
- `server/typescript/packages/codegen-ts/src/generators/template-doc-builder.ts`
17+
+ `template-doc-data.ts` — the existing template page builder + `@payloadRef`
18+
field-tree derivation. `templates/docs/template-page.md.mustache`.
19+
- `src/generators/docs-data-builder.ts` + `templates/docs/entity-page.md.mustache`
20+
— the Constraints table (where per-field anchors go).
21+
- `spec/decisions/ADR-0022-...md`; the design spec `…/specs/2026-06-05-linked-template-source-docs-design.md`.
22+
23+
## Task 1: The annotated-template IR (the core)
24+
**Files:** `src/generators/template-source-annotate.ts`; test `test/template-source-annotate.test.ts`.
25+
- `annotateTemplate(source, payloadFieldTree, opts): TplToken[]``Mustache.parse`
26+
the source; walk tokens emitting `text` / `var` / `unescaped` / `section` /
27+
`inverted` / `close` / `partial` / `comment`; resolve each variable/section path
28+
against the payload field tree (REUSE verify's resolution — if its `lookup`/
29+
`current` helpers are internal, export them or factor `resolveTemplateVariable`
30+
shared in render). Each resolved var gets `{ owner, name, type, required, href }`
31+
where `href = ./<OwnerVO>.md#field-<name>`. Context-pushing sections resolve
32+
nested fields; dotted paths walk; unresolved → `valid:false` no href.
33+
- [ ] TDD: a fixture template with a scalar var, a `{{#section}}{{field}}{{/}}`
34+
(nested-object context), a dotted path, a partial, a comment, and an off-payload
35+
var → assert the token kinds + each var's resolved owner/field/href + the
36+
off-payload one is `valid:false`. Run → FAIL → implement → PASS.
37+
38+
## Task 2: Per-field anchors on the entity page
39+
**Files:** `docs-data-builder.ts` (constraints rows), `templates/docs/entity-page.md.mustache`; update the entity goldens.
40+
- Emit a stable anchor per field in the Constraints table (e.g. an
41+
`<a id="field-<name>"></a>` in the Field cell, or an anchored field name) so
42+
`./Entity.md#field-<name>` resolves. Slug = `field-<fieldname>`.
43+
- [ ] TDD: assert the rendered entity page contains `id="field-<name>"` per field;
44+
update the docs-file goldens to the anchored output.
45+
46+
## Task 3: The three renderers
47+
**Files:** `src/generators/template-source-render.ts`; test `test/golden/template-source-render.test.ts`.
48+
- From `TplToken[]`: (a) `renderSourceBlock` → a ```` ```mustache ```` fenced block
49+
of the raw source; (b) `renderVariablesTable` → a Markdown table of unique vars
50+
`{{path}}``[Owner.field](href)` | type | required (unresolved flagged);
51+
(c) `renderRichLinkedHtml``<details><summary>Linked view</summary>` + `<pre>`
52+
with self-contained INLINE-STYLED color spans per token kind + `<a href>` on
53+
variables/sections. Collapsed by default.
54+
- [ ] TDD: byte-golden each renderer for a fixture IR; assert the rich HTML has
55+
inline styles (no external CSS) + resolving `<a href>`; the table links match
56+
the IR hrefs; the block is the verbatim source.
57+
58+
## Task 4: Integrate into the template page
59+
**Files:** `template-doc-data.ts`, `template-doc-builder.ts`, `templates/docs/template-page.md.mustache` (canonical root + run `scripts/sync-doc-templates.sh`); update template-page goldens + the cli `meta docs` test.
60+
- Builder attaches the annotated source per template + per email part (resolve
61+
each ref's mustache via the filesystem provider — the same the verify drift gate
62+
uses; the field tree from `@payloadRef`). Add a `## Template source` section to
63+
the template page rendering the block + table + rich `<details>` per part.
64+
- [ ] TDD: the template page golden gains the source section with a linked var;
65+
`meta docs` emits it; byte-identity gate (root==package template) green.
66+
67+
## Task 5: Conformance gate (link integrity + annotator⇆verify agreement)
68+
**Files:** `test/golden/template-source-conformance.test.ts`.
69+
- For a fixture model: (a) every var the annotator links has a matching
70+
`id="field-<name>"` anchor on the target entity page actually emitted in the run
71+
(cross-doc link integrity); (b) the set of vars the annotator marks `valid`
72+
EQUALS the set render `verify()` does NOT flag `ERR_VAR_NOT_ON_PAYLOAD` for the
73+
same template+payload (the doc can't claim a link verify would reject).
74+
- [ ] TDD: assert both; an injected off-payload var → annotator `valid:false` AND
75+
verify flags it (they agree); a renamed field breaks the link-integrity check.
76+
77+
## Task 6: Closeout
78+
- Full `bun test packages/codegen-ts packages/cli packages/render` green; counts.
79+
- Whole-branch review + simplifier; fix findings.
80+
- Eyeball the rendered template page (source block + table + rich view).
81+
- Forward-merge (temp worktree off latest origin/main). Update memory.
82+
83+
## Guard
84+
Reuse verify's mapping (don't reimplement mustache resolution); the render engine
85+
`verify()` behavior UNCHANGED. Anchors + links must resolve (gated). Templates
86+
under `templates/docs/` stay byte-identity-gated + synced. Don't touch other ports
87+
/ api-docs / agent-context / init.ts.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Linked, syntax-highlighted template source in `meta docs` — design
2+
3+
**Date:** 2026-06-05
4+
**Status:** Design (pending review)
5+
**Relates to:** ADR-0022 (this is a `meta docs` / neutral Tier-2 enhancement),
6+
the render `verify()` engine (reused), the neutral entity + template docs.
7+
8+
## Goal
9+
10+
On the `meta docs` `template.output` / `template.prompt` page, **embed the actual
11+
Mustache template source**, syntax-highlighted, with **every `{{variable}}`
12+
hot-linked to that field's documentation** on its entity/VO page. Connect the
13+
template to the model it depends on, reusing the variable→field mapping the
14+
render `verify()` engine already computes (its build-time drift gate).
15+
16+
Both renderings from one model: a portable Markdown form (highlighted code block +
17+
a Variables→field-doc link table) that works on GitHub and for an agent, AND a
18+
rich HTML form (the template rendered as colored, inline-linked clickable
19+
variables) for a docs site.
20+
21+
## Why it's cheap to do
22+
- `verify.ts` already parses the template (`Mustache.parse` → tokens with
23+
type/value/position) and resolves each variable against the payload field tree
24+
(context-pushing sections handled) to drive `ERR_VAR_NOT_ON_PAYLOAD`. We reuse
25+
that parse + resolution to know, per variable, the exact field it maps to.
26+
- The neutral entity page already documents each field (the Constraints table) —
27+
the link target. It just needs a stable per-field anchor.
28+
29+
## Scope
30+
`meta docs` only (neutral, Tier-2, TS — the single shared docs engine per
31+
ADR-0022). Both `template.output` and `template.prompt`. The render engine's
32+
`verify()` behavior is UNCHANGED (we read its parse/resolution, not alter it).
33+
34+
## Design
35+
36+
### 1. The annotated-template IR
37+
A new builder produces, per template (and per email part), an ordered token list:
38+
```ts
39+
type TplToken =
40+
| { kind: "text"; text: string }
41+
| { kind: "var" | "unescaped"; raw: string; path: string; field?: ResolvedField; href?: string; valid: boolean }
42+
| { kind: "section" | "inverted" | "close"; raw: string; path: string; field?: ResolvedField; href?: string }
43+
| { kind: "partial"; raw: string; ref: string; href?: string }
44+
| { kind: "comment"; raw: string };
45+
interface ResolvedField { owner: string; name: string; type: string; required: boolean; }
46+
```
47+
- Built from `Mustache.parse(source)` + the payload field tree (reuse the
48+
template-doc-builder's `@payloadRef` field-tree derivation + verify's
49+
context-resolution: a `{{field}}` inside `{{#section}}` resolves within the
50+
section's nested VO; dotted paths walk the tree).
51+
- `href` = `./<OwnerVO>.md#<field-anchor>` (the field's entity/VO page + anchor).
52+
A nested-object section that points at another `object.value` links to THAT
53+
VO's page; the leaf field links to its owner's page.
54+
- Variables NOT on the payload → `valid:false`, no href, flagged (in a valid
55+
model the drift gate already forbids these; render defensively).
56+
- Partials → optionally link to the referenced template's page if it's a
57+
documented `template.output`; else highlight only.
58+
59+
### 2. Per-field anchors on the entity page
60+
The entity-page generator emits a stable anchor per field (e.g. an
61+
`<a id="<field>"></a>` in the Field cell, or render the field name as an anchored
62+
span) so `./Entity.md#<field>` resolves. Anchor slug = the field name
63+
(kebab/lower as needed; documented + consistent with the link builder).
64+
65+
### 3. Renderings (both from one source) — on the template page
66+
For each template (and each email part), emit:
67+
1. **Source block** — a ```` ```mustache ```` fenced block of the raw template
68+
(clean source; the viewer/site highlighter colors it). Universal, agent-clean.
69+
2. **Variables table** — each UNIQUE variable: `{{path}}``[Owner.field](href)` |
70+
type | required. (The links, Markdown-native.) Unresolved vars flagged.
71+
3. **Rich linked view** — a collapsible `<details><summary>Linked view</summary>`
72+
containing an HTML `<pre>` of the template where each token is a
73+
**self-contained inline-styled** span (color) and each variable/section is
74+
wrapped in `<a href="…">` (clickable). Inline styles (no external CSS) so it
75+
renders identically on GitHub and a docs site. Collapsed by default so it
76+
doesn't clutter the plain/agent view.
77+
78+
This gives color (block + the rich view), links (table + the rich view), and
79+
agent-cleanliness (block + table) — from one annotated IR.
80+
81+
### 4. Integration
82+
- `template-doc-data.ts` / `template-doc-builder.ts` gain the annotated-template
83+
per template + per email part (reusing the existing `@payloadRef` field-tree +
84+
the render `Mustache.parse`/verify resolution).
85+
- `templates/docs/template-page.md.mustache` renders the three forms under a new
86+
`## Template source` section (after the existing Source refs).
87+
- The annotator + the rich-HTML renderer are pure functions over the IR
88+
(testable, golden-pinned).
89+
90+
## Accuracy / conformance gate
91+
- **Links resolve:** a conformance test asserts every variable the annotator links
92+
has (a) a field that verify agrees is on the payload, AND (b) a matching anchor
93+
emitted on the target entity page (cross-doc link integrity — the two surfaces
94+
agree).
95+
- **Annotator ⇆ verify agree:** the set of variables the annotator marks `valid`
96+
equals the set verify does NOT flag `ERR_VAR_NOT_ON_PAYLOAD` for the same
97+
template+payload (so the doc can't claim a link verify would reject).
98+
- Byte-golden the rendered source block + table + rich view for a fixture.
99+
100+
## File structure (TS)
101+
- `src/generators/template-source-annotate.ts``Mustache.parse` + field
102+
resolution → the `TplToken[]` IR (reuse verify's resolution helpers; if they're
103+
internal, factor a shared `resolveTemplateVariables` in render or codegen-ts).
104+
- `src/generators/template-source-render.ts` — the three renderers (fenced block,
105+
variables table, rich-HTML `<details>`).
106+
- `template-doc-builder.ts` + `template-doc-data.ts` — attach the annotated
107+
source; `template-page.md.mustache` — the `## Template source` section.
108+
- entity page: per-field anchors (`docs-data-builder.ts` constraints + the
109+
entity template).
110+
- Tests: annotator unit (sections/dotted/partials/unresolved), the link-integrity
111+
+ annotator⇆verify conformance gate, render goldens.
112+
113+
## Open questions (for review)
114+
1. **Rich view default:** collapsed `<details>` (proposed) vs always-expanded.
115+
Collapsed keeps the agent/plain view clean; humans expand for clickable vars.
116+
2. **Anchor slug scheme** — bare field name vs `field-<name>` prefixed (avoid
117+
collision with other page anchors). Lean prefixed for safety.
118+
3. **Partials linking** — link `{{>ref}}` to the referenced template page if it's
119+
a documented template.output, else highlight-only. Lean: link when resolvable,
120+
else highlight (cheap, useful).

fixtures/conformance/docs-file-basic/expected/Author.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
| Field | Required | Type | Limits | Rules |
2828
|---|---|---|---|---|
29-
| `id` | yes | `long` | | primary key |
30-
| `name` | yes | `string` | maxLength: 200 | |
31-
| `status` | yes | `enum` | | one of `active`, `suspended`, `archived` |
32-
| `tags` | | `string[]` | | |
29+
| <a id="field-id"></a>`id` | yes | `long` | | primary key |
30+
| <a id="field-name"></a>`name` | yes | `string` | maxLength: 200 | |
31+
| <a id="field-status"></a>`status` | yes | `enum` | | one of `active`, `suspended`, `archived` |
32+
| <a id="field-tags"></a>`tags` | | `string[]` | | |

fixtures/conformance/docs-file-m2m/expected/User.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@
2929

3030
| Field | Required | Type | Limits | Rules |
3131
|---|---|---|---|---|
32-
| `id` | yes | `long` | | primary key |
33-
| `name` | yes | `string` | | |
32+
| <a id="field-id"></a>`id` | yes | `long` | | primary key |
33+
| <a id="field-name"></a>`name` | yes | `string` | | |

fixtures/conformance/template-doc-document/expected/Welcome.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
| Field | Required | Type | Limits | Rules |
1111
|---|---|---|---|---|
12-
| `name` | | `string` | | |
13-
| `headline` | | `string` | | |
12+
| <a id="field-name"></a>`name` | | `string` | | |
13+
| <a id="field-headline"></a>`headline` | | `string` | | |
1414

1515
## Used by
1616

fixtures/conformance/template-doc-document/expected/WelcomePage.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,28 @@
2323

2424
- `site/welcome`
2525

26+
## Template source
27+
28+
```mustache
29+
<h1>Welcome, {{name}}!</h1>
30+
<p>{{headline}}</p>
31+
32+
```
33+
34+
| Variable | Field | Type | Required |
35+
| --- | --- | --- | --- |
36+
| `{{name}}` | [Welcome.name](./Welcome.md#field-name) | string | no |
37+
| `{{headline}}` | [Welcome.headline](./Welcome.md#field-headline) | string | no |
38+
39+
<details>
40+
<summary>Linked view</summary>
41+
42+
<pre style="white-space:pre-wrap;word-break:break-word">&lt;h1&gt;Welcome, <a href="./Welcome.md#field-name"><span style="color:#0969da">{{name}}</span></a>!&lt;/h1&gt;
43+
&lt;p&gt;<a href="./Welcome.md#field-headline"><span style="color:#0969da">{{headline}}</span></a>&lt;/p&gt;
44+
</pre>
45+
46+
</details>
47+
2648
## Capability
2749

2850
A render helper is generated for this template: it takes the payload and returns the rendered output as a single string.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<h1>Welcome, {{name}}!</h1>
2+
<p>{{headline}}</p>

fixtures/conformance/template-doc-email/expected/Welcome.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
| Field | Required | Type | Limits | Rules |
1111
|---|---|---|---|---|
12-
| `name` | | `string` | | |
13-
| `headline` | | `string` | | |
12+
| <a id="field-name"></a>`name` | | `string` | | |
13+
| <a id="field-headline"></a>`headline` | | `string` | | |
1414

1515
## Used by
1616

fixtures/conformance/template-doc-email/expected/WelcomeEmail.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,73 @@ Multipart email — rendered as the following parts:
2828
- `email/welcome.html`
2929
- `email/welcome.text`
3030

31+
## Template source
32+
33+
### Subject
34+
35+
```mustache
36+
Welcome aboard, {{name}}!
37+
38+
```
39+
40+
| Variable | Field | Type | Required |
41+
| --- | --- | --- | --- |
42+
| `{{name}}` | [Welcome.name](./Welcome.md#field-name) | string | no |
43+
44+
<details>
45+
<summary>Linked view</summary>
46+
47+
<pre style="white-space:pre-wrap;word-break:break-word">Welcome aboard, <a href="./Welcome.md#field-name"><span style="color:#0969da">{{name}}</span></a>!
48+
</pre>
49+
50+
</details>
51+
52+
### HTML body
53+
54+
```mustache
55+
<h1>Welcome, {{name}}!</h1>
56+
<p>{{headline}}</p>
57+
58+
```
59+
60+
| Variable | Field | Type | Required |
61+
| --- | --- | --- | --- |
62+
| `{{name}}` | [Welcome.name](./Welcome.md#field-name) | string | no |
63+
| `{{headline}}` | [Welcome.headline](./Welcome.md#field-headline) | string | no |
64+
65+
<details>
66+
<summary>Linked view</summary>
67+
68+
<pre style="white-space:pre-wrap;word-break:break-word">&lt;h1&gt;Welcome, <a href="./Welcome.md#field-name"><span style="color:#0969da">{{name}}</span></a>!&lt;/h1&gt;
69+
&lt;p&gt;<a href="./Welcome.md#field-headline"><span style="color:#0969da">{{headline}}</span></a>&lt;/p&gt;
70+
</pre>
71+
72+
</details>
73+
74+
### Text body
75+
76+
```mustache
77+
Welcome, {{name}}!
78+
79+
{{headline}}
80+
81+
```
82+
83+
| Variable | Field | Type | Required |
84+
| --- | --- | --- | --- |
85+
| `{{name}}` | [Welcome.name](./Welcome.md#field-name) | string | no |
86+
| `{{headline}}` | [Welcome.headline](./Welcome.md#field-headline) | string | no |
87+
88+
<details>
89+
<summary>Linked view</summary>
90+
91+
<pre style="white-space:pre-wrap;word-break:break-word">Welcome, <a href="./Welcome.md#field-name"><span style="color:#0969da">{{name}}</span></a>!
92+
93+
<a href="./Welcome.md#field-headline"><span style="color:#0969da">{{headline}}</span></a>
94+
</pre>
95+
96+
</details>
97+
3198
## Capability
3299

33100
A render helper is generated for this template: it takes the payload and returns the rendered email — subject, HTML body, and an optional text body.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<h1>Welcome, {{name}}!</h1>
2+
<p>{{headline}}</p>

0 commit comments

Comments
 (0)