| id | 75 |
|---|---|
| title | Single-brace placeholders everywhere |
| status | ✅ |
| summary | Replace {{.field}} with {field} in both required-structure and catalog. One placeholder syntax across the whole tool. |
Part of the user-model work from plan 73. Addresses #68.
Depends on: none (independent of other plans). Update plan 74 guide after landing.
One placeholder syntax: {field}. It works
the same way everywhere -- resolves a
front-matter key by name using CUE path syntax
(plan 79
adds nested access).
- In catalog
row/header/footer: inserts the value from matched files' front matter. - In schema headings: heading must contain the value from the document's own front matter.
{{.field}} (Go template syntax) is removed
from the user-facing surface entirely.
Blind trials (plan 73) showed {{.field}} was
the top confusion source. The original plan
separated the two syntaxes ({field} for
schemas, {{.field}} for catalog). But keeping
Go templates in catalog means two grammars.
Aligning to one syntax is simpler.
Go text/template is overkill for catalog row
rendering: no user uses conditionals, loops,
or functions in row templates. Simple string
interpolation with {field} suffices.
{field} renders as visible literal text on
GitHub. # {id}: {name} reads as a clear
pattern. Catalog row params in YAML also read
naturally: row: "- [{title}]({filename})".
No backslash escaping or dot-prefix needed.
Replace Go text/template in catalog with
simple {field} interpolation. Same regex,
same resolver, same CUE path semantics as
required-structure.
row: "- [{title}]({filename})"-- catalog row rendering.# {id}: {name}-- schema heading match.{field}resolves to empty string if the key is missing (current behavior preserved).{filename}remains a built-in field in catalog context (relative path).- Other built-ins:
{title},{summary}, etc. are front-matter lookups, not special.
| Feature | Go template | {field} |
|---|---|---|
| Syntax | {{.field}} |
{field} |
| Nested access | {{.a.b}} |
{a.b} (plan 79) |
| Quoted keys | {{ index . "k" }} |
{"my-key"} (plan 79) |
| Conditionals | {{ if .x }} |
Not supported |
| Loops | {{ range }} |
Not supported |
| Functions | {{ .x | fn }} |
Not supported |
| Missing key | empty string | empty string |
No catalog in the repo uses conditionals, loops, or functions today.
Literal { is written as {{, literal } as
}}. Same convention as Python's str.format.
Example: row: "{{literal}} {title}" renders
as {literal} My Title.
- Add a
{field}interpolation engine in a shared package (e.g.internal/fieldinterp):
- Parse
{...}placeholders from a string - Resolve each placeholder against a
map[string]anyusing CUE path rules - Return the interpolated string
- Handle
{{as escaped literal{
- Update
catalog/generate.go:
- Replace
text/templaterendering withfieldinterp.Interpolate - Keep
{filename}as a built-in injected into the data map before interpolation
- Update
requiredstructure/rule.go:
- Replace
fieldPatternregex with call to the sharedfieldinterpparser resolveFieldsuses the shared resolver- Pattern matching builds regex from parsed placeholders (same logic, shared parse)
- Update unit tests in both rules.
- Update fixtures:
internal/rules/MDS020-required-structure/internal/rules/MDS019-catalog/- Any fixture templates or catalog directives
using
{{.field}}
- Migrate all schema files:
plan/proto.mdinternal/rules/proto.md.claude/skills/proto.md
- Migrate all catalog directives in the repo
(CLAUDE.md, README.md, rule READMEs) from
{{.field}}to{field}. - Update rule READMEs (MDS019, MDS020).
- Update
docs/guides/directives/guides (plan 74) if it already exists. - Run
mdsmith check .to verify.
-
{field}is the only placeholder syntax in both catalog and required-structure -
{{.field}}is no longer recognized - All schema files use
{field} - All catalog directives use
{field} - Shared interpolation engine exists
- Literal
{is escaped as{{ - MDS019 and MDS020 READMEs updated
- All fixtures updated and passing
- All tests pass:
go test ./... -
go tool golangci-lint runreports no issues -
mdsmith check .passes