@m0saic/knowledge 0.2.0

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +7 -0
  5. package/docs/README.md +60 -0
  6. package/docs/file-formats/m0-iteration-protocol.md +120 -0
  7. package/docs/file-formats/m0p-and-custom-field.md +195 -0
  8. package/docs/handbook/README.md +27 -0
  9. package/docs/handbook/composition-arithmetic.md +278 -0
  10. package/docs/handbook/dsl-complexity.md +75 -0
  11. package/docs/handbook/dsl-rules.md +367 -0
  12. package/docs/handbook/feasibility-precision-quantization.md +591 -0
  13. package/docs/handbook/m0-construction-methods.md +201 -0
  14. package/docs/handbook/precision-tiers.md +84 -0
  15. package/docs/m0saic-thesis.md +95 -0
  16. package/docs/runtime/README.md +17 -0
  17. package/docs/runtime/cli-usage.md +372 -0
  18. package/docs/runtime/ffmpeg-expression-limits.md +117 -0
  19. package/docs/runtime/reduce-to-one.md +96 -0
  20. package/docs/skills/README.md +40 -0
  21. package/docs/skills/axis-and-geometry.md +103 -0
  22. package/docs/skills/dsl-stdlib-method-catalog.md +7 -0
  23. package/docs/skills/identity.md +123 -0
  24. package/docs/skills/labels-and-masks.md +170 -0
  25. package/docs/skills/m0saic-string-generation.md +251 -0
  26. package/docs/skills/more-atoms-not-bigger-atoms.md +77 -0
  27. package/docs/skills/overlay-semantics.md +194 -0
  28. package/docs/skills/parse-apis.md +79 -0
  29. package/docs/skills/passthrough-semantics.md +136 -0
  30. package/docs/skills/structural-construction.md +86 -0
  31. package/docs/skills/text-in-templates.md +126 -0
  32. package/docs/skills/zero-overlay-analysis.md +87 -0
  33. package/docs/templates/README.md +65 -0
  34. package/docs/templates/capability-templates.md +72 -0
  35. package/docs/templates/construction-strategy.md +329 -0
  36. package/docs/templates/data-pipeline.md +324 -0
  37. package/docs/templates/emission-patterns.md +130 -0
  38. package/docs/templates/geometry-recipes.md +248 -0
  39. package/docs/templates/layout-contract.md +168 -0
  40. package/docs/templates/output-resolution-tree.md +202 -0
  41. package/docs/templates/patterns/case-study-lessons.md +69 -0
  42. package/docs/templates/patterns/perf-authoring-rules.md +100 -0
  43. package/docs/templates/patterns/primitive-extraction-pattern.md +103 -0
  44. package/docs/templates/philosophy-and-contract.md +310 -0
  45. package/docs/templates/recursion-nested-rendering.md +138 -0
  46. package/docs/templates/reference/grid.md +104 -0
  47. package/docs/templates/reference/json-prop-type.md +169 -0
  48. package/docs/templates/reference/mosaic-color.md +81 -0
  49. package/docs/templates/reference/mosaic-placement-props.md +103 -0
  50. package/docs/templates/reference/prop-bindings.md +203 -0
  51. package/docs/templates/reference/template-flags.md +205 -0
  52. package/docs/templates/render-lifecycle.md +117 -0
  53. package/docs/templates/rendering-model-contract.md +392 -0
  54. package/docs/templates/standalone-pack-authoring.md +233 -0
  55. package/docs/templates/theming.md +81 -0
  56. package/docs/templates/ui-controls.md +150 -0
  57. package/package.json +37 -0
@@ -0,0 +1,205 @@
1
+ # Template status flags: `internal` / `primitive` / `deprecated`
2
+
3
+ Lookup reference for the three orthogonal discovery/visibility flags on
4
+ `MosaicTemplate`. A template may carry any combination; conflating them is the
5
+ standard mistake. Declared in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts —
6
+ `internal?` (:1385), `primitive?` (:1416), `deprecated?` (:1446), each with its
7
+ truth table in the JSDoc (line numbers verified 2026-07-27; re-verify:
8
+ `grep -n "internal?: boolean\|primitive?: boolean\|deprecated?: {" https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts).
9
+
10
+ | Flag | Question | Renderable alone? | Default UI |
11
+ |---|---|---|---|
12
+ | `primitive?: boolean` | "Is this a base others build on?" | **Yes** | Shown + **PRIMITIVE** badge (teal) |
13
+ | `internal?: boolean` | "Is this intended as a **top-level selection**?" (No → hidden) | **Sometimes** — the flag doesn't decide this | **Hidden**; **INTERNAL** badge (violet) when revealed |
14
+ | `deprecated?: {reason?, replacement?, since?}` | "Is this still the recommended pick?" | Yes | **Hidden**; **DEPRECATED** tag + "use X instead" |
15
+
16
+ **`primitive` is informational — it does NOT hide anything.** `internal` and
17
+ `deprecated` are the visibility gates.
18
+
19
+ ## Why `primitive` ≠ `internal`
20
+
21
+ - **`primitive` is a quality** — foundational, widely reused. `charts/donut`,
22
+ `charts/stat-card`, `charts/bar-graph`, `primitives/grid` are primitives *and*
23
+ perfectly good standalone picks.
24
+ - **`internal` is an INTENT/visibility flag, not a capability statement** — it says
25
+ "not intended as a top-level pick": excluded from public listings and template
26
+ pickers by default, but fully available to `renderNestedTemplate()`
27
+ (`internal` JSDoc, `template.ts:1375-1384`). Whether it can render standalone
28
+ varies by template and is NOT what the flag encodes: the ffmpeg-pulse piece
29
+ templates are internal yet render fine on their own; `bar-graph`'s `bar-cell`
30
+ leaf is internal AND genuinely expects parent-injected context. Don't infer
31
+ either way from the flag — read the template.
32
+ - **Both** is a real combination: a shared building block many templates reuse
33
+ that still isn't meant to be picked top-level.
34
+
35
+ (The `primitive` flag's orthogonality table in `template.ts` used to gloss
36
+ `internal` as "can this render on its own?", contradicting the `internal` JSDoc —
37
+ fixed in-code 2026-07-27; both JSDoc now carry the top-level-selection semantics.)
38
+
39
+ ## Where the flags surface
40
+
41
+ - **Electron `templates:get`** (the Mosaic Desktop / Web app source (not published)) serializes all
42
+ three into the meta the desktop Templates page consumes.
43
+ - **Desktop Templates page** — PRIMITIVE / INTERNAL badges with explanatory tooltips.
44
+ - **CLI `list-templates` / `browse-templates`** — annotates
45
+ `(primitive) (internal) (deprecated)` in that **fixed order**
46
+ (the CLI source (not published)-33`).
47
+
48
+ ## Curation is a content decision
49
+
50
+ `primitive` started as 4 seeded entries and is on **29 templates** as of 2026-07-27
51
+ (re-verify: `grep -rn "primitive: true" https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic | wc -l`).
52
+ There is no rule that derives it — like `deprecated`, someone decides. If you're
53
+ adding a template that other templates will compose, set it; don't wait for a sweep.
54
+
55
+ ⚠️ **Setting `deprecated` obligates a pruning pass** — repoint the curated registry,
56
+ and remove the id from the CLI E2E cases and the e2e variant list. The full ordered
57
+ procedure is in the maintainers' agent contract §10 (not published) "Deprecating a template"; a deprecated id must
58
+ never linger in those lists. The default e2e sweep auto-excludes deprecated
59
+ templates; opt back in with `M0SAIC_INCLUDE_DEPRECATED=1`.
60
+
61
+ ### Deprecation also silences convention advice (2026-09-09)
62
+
63
+ `M0SAIC_INCLUDE_DEPRECATED=1` governs more than the e2e render sweep: it also
64
+ controls **convention findings** in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/tools/check-registry.mjs,
65
+ which gates `npm run build`.
66
+
67
+ By default a deprecated template's definition-time and render-time convention
68
+ warnings/errors are **suppressed** — the template is defunct, so the advice will
69
+ never be acted on and repeating it forever is noise. This makes the intended
70
+ workflow self-cleaning: for a shipped template that violates a convention, **bump
71
+ to vN+1 and deprecate the old one — the old one goes quiet automatically.** There
72
+ is no exclusion list to maintain.
73
+
74
+ The `outputFormat` convention (2026-09-13: a public template declares
75
+ `outputHints.format`; record posture) treats both visibility gates as
76
+ exemptions at the audit itself, not only through this suppression: an
77
+ `internal` building block renders only nested and a `deprecated` template is
78
+ frozen history, so neither owes a deliverable declaration
79
+ (`auditOutputFormat`, https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/auditSchemaConventions.ts).
80
+
81
+ The suppressed count is always printed, never silent:
82
+
83
+ ```
84
+ [check-registry] ℹ 14 finding(s) on 22 deprecated template(s) suppressed
85
+ (advice only — freeze + fingerprints still apply).
86
+ M0SAIC_INCLUDE_DEPRECATED=1 to see them.
87
+ ```
88
+
89
+ ⭐ **Suppression is ADVICE ONLY.** The freeze gate (`frozen.manifest.json`, every
90
+ template shipped in 0.1.0) and the layout fingerprints still cover deprecated
91
+ templates in full, byte for byte, in both modes. Deprecation means "stop
92
+ suggesting improvements" — it NEVER grants permission to change a shipped
93
+ template. See the maintainers' agent contract §10 (not published).
94
+
95
+ Measured when introduced (103 registered templates, 22 deprecated): render-time
96
+ warnings 21 → 12, definition-time warning knobs 208 → 165 across 30 → 25
97
+ templates; freeze (327 files) and fingerprints (89) identical either way.
98
+
99
+ ## The repo's front door — `repo.helloWorld` (2026-09-14)
100
+
101
+ Not a template flag but a **repo-descriptor** field beside them
102
+ (`MosaicTemplateRepoDescriptor.helloWorld?: TemplateId`,
103
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template-repo/template-repo.ts): the id of the template a
104
+ newcomer renders first. Core names `@m0saic/hello-world/v1`; each starter names
105
+ its own `…/basics/hello-world/v1`. It is deliberately NOT a `TemplateRole` — roles
106
+ say what a template *produces*; this says which one is the door.
107
+
108
+ Readers: the CLI alias (`m0saic hello-world --template-repo <path>`), the manifest
109
+ (the descriptor is embedded, so `template-manifest.json` carries it zero-exec; the
110
+ platform loader passes it through), and — follow-up — Make's "Start here" chip.
111
+ The gate's `repoFrontDoor` convention (record posture) warns when the field is
112
+ unset or names an id the repo does not register; `internal` / `deprecated`
113
+ templates are fine to name, just unusual. Authoring recipe: `standalone-pack-authoring.md`
114
+ §1.
115
+
116
+ ## A canvas that is a knob — `resolveOutputHints` (2026-09-15)
117
+
118
+ Also not a flag but a **template field** beside `outputHints`:
119
+ `resolveOutputHints?: (props) => Partial<MosaicTemplateOutputHints>`. The
120
+ static `outputHints` stay what the manifest and the cards show; the resolver
121
+ is what a host asks with the current props before rendering, through the one
122
+ helper `resolveTemplateOutputHints(tmpl, props)` (`@m0saic/template-utils`).
123
+ Readers: CLI `make` (+ wireframe / tutorial paths), Electron preview / cover /
124
+ render-to-file + the `templates:resolveOutputHints` IPC, the web design
125
+ preview, and Make's Device anchor (re-resolved on every prop change). The
126
+ seam's `outputHintsResolve` convention (THROW posture) checks it returns an
127
+ object, is deterministic at `defaultProps`, and agrees with the static hints
128
+ for every field it returns there. Precedence: [`../output-resolution-tree.md`](../output-resolution-tree.md) §size.
129
+
130
+ ## The lattice declarations — `lattice` (2026-09-16)
131
+
132
+ **Convention `latticeSmooth` (throw):** every split count above 12 in the rendered
133
+ layout — root document and every nested child, walked as documents (a nested doc
134
+ whose flatten fails below its floor is still measured) — is 5-smooth, `N = 2ᵃ·3ᵇ·5ᶜ`.
135
+ Counts ≤ 12 with a rough factor (7 weekdays, 11 tiles) pass as small-basis ratio
136
+ fill. Measured at the hinted canvas and, under `--sweep`, on the standard canvases.
137
+ Why: all delivery canvases are 5-smooth with family gcd 120, so 5-smooth counts
138
+ divide them (pixel-exact), any two 5-smooth self-lattices LCM to a small number,
139
+ and a child never inherits a rough cell size (handbook
140
+ [`composition-arithmetic.md`](../../handbook/composition-arithmetic.md) §1–3).
141
+ `m0saic doctor <repo>` reports it for external packs at error severity — the
142
+ publish requirement. Plan + numbers: (internal design history).
143
+
144
+ `MosaicTemplate.lattice?` (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts, beside
145
+ `primitive?` / `deprecated?`) is the declaration surface — every override is typed,
146
+ reasoned, and printed by the gate:
147
+
148
+ | field | when it is honest | effect |
149
+ |---|---|---|
150
+ | `mode: "bitmap"` | the split counts ARE a raster (QR modules, barcode bars, a logo trace, the community M) — handbook §3c BITMAP, baked once, never live-composed | the template is skipped with a note |
151
+ | `allow: [{ count, reason }]` | a count above 12 that is content cardinality (53 ISO weeks; a 335-tile fixture grid) | the count is accepted; the reason is printed and carried in `--json` |
152
+ | `canvas: "physical"` | the hinted size is a millimetre spec at a dpi (business card 1130×678 = 2·5·113 × 2·3·113) | the rough-axis finding is not raised, counts the canvas hands down are charged to it (not to the construction), and the `--sweep` canvases are skipped — a print size cannot move |
153
+
154
+ Per DOCUMENT, `MosaicEngineMeta.lattice?: { mode: "bitmap" }` (`doc.engine.lattice`)
155
+ marks a raster a parent assembles itself (the business card's QR child, built from
156
+ `qrToRenderable` pieces) — that subtree is skipped, the parent is still measured.
157
+ (The layout-fingerprint sidecar cannot carry this marker: the fleet lock
158
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/lattice-audit.test.ts re-measures such a template with
159
+ `auditRenderedTemplate` instead of trusting the flattened string.)
160
+
161
+ **Not honest:** a count that came from your arithmetic — independent rounding to a
162
+ cap (121/119/241/245), a pitch matched to a grid that no longer exists (499),
163
+ `round(side/K)` + `ceil` self-frames (99/101), pixel splits inside an already
164
+ quantized cell. The finding names the nearest 5-smooth neighbours and the LCM cost;
165
+ the neighbour almost always looks identical.
166
+
167
+ ## Reading the dictionary from a template — web vs node (2026-09-16)
168
+
169
+ `@m0saic/dictionary` has two entries: node (`index.ts`, reads `.m0`/`.m0c` from
170
+ disk) and browser (`browser.ts`, what the Mosaic Desktop / Web app source (not published) bundles via the package's
171
+ `browser` field). Every jest suite resolves the NODE entry, so a template that reads
172
+ a brand entry synchronously can be green under test and an error mosaic on
173
+ app.m0saic.io — which is exactly what QR Code's centre M, Brand Marks v3 and
174
+ community-m did until 2026-09-16 (the browser entry shipped every brand `m0` as `""`
175
+ and its `getRankSet` threw). Rules that hold on BOTH entries now:
176
+
177
+ - **`entry.m0` is populated for every entry whose canonical m0 is ≤ 40 KB**
178
+ (`INLINE_M0_MAX_CHARS` in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/dictionary/tools/validate.js, which emits a
179
+ committed sibling `m0.json` per entry that `browser.ts` imports). Only
180
+ `brand/m-33_bitmap` (149 KB) ships `m0: ""` on web — a template that can take it
181
+ must guard `if (!entry.m0)` and say "Desktop renders this" (Brand Marks v3 does).
182
+ - **`entry.masks`, `entry.rankSets`, `entry.sourceCount` ride in `metadata.json`** →
183
+ identical on web. `registry.getRankSet(id, name)` works on web (same lookup as
184
+ node). Named mask SETS (`getMaskSet`) are node-only.
185
+ - **The guards, in order of reach:** https://github.com/m0saic-project/m0saic-packages/blob/main/packages/dictionary/src/browser.test.ts
186
+ (browser m0 == node m0 for every inlined entry; only the bitmap lazy) ·
187
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/webDictionary.test.ts (renders QR Code / Brand Marks /
188
+ community-m against the browser entry via `jest.mock`) · the Playwright web lane
189
+ the Mosaic Desktop / Web app source (not published) (the shipped bundle).
190
+ **Add your template to `webDictionary.test.ts` if it reads a brand entry.**
191
+
192
+ the Mosaic Desktop / Web app source (not published) copies `*.json`, so `m0.json`
193
+ also lands under `public/dictionary/` — harmless (≤ 10 KB each). The light entries in
194
+ `browser.ts` are still hand-written F-form literals; the drift test compares them
195
+ canonically to the generated `m0.json`.
196
+
197
+ ## Known wrinkle
198
+
199
+ The Templates page's **"Show layout primitives" toggle** (renamed from "Show
200
+ primitives" on 2026-09-16 so the label says what it does) gates a *tag-derived*
201
+ Primitives **category** (tags `grid` / `primitive` — in practice the `primitives/`
202
+ family) — separate from the cross-cutting `primitive` **flag**, which only ranks and
203
+ badges. They coexist cleanly, but they are two mechanisms with one word. Driving the
204
+ toggle off the flag would hide most of the alpine/charts shelf by default, so the
205
+ founder kept the category gate and fixed the label instead.
@@ -0,0 +1,117 @@
1
+ # Template render lifecycle — the four entry points
2
+
3
+ `render` is required. The other three are optional, opt-in, and **editor-only** —
4
+ none of them is ever on the `m0saic make` / CLI path.
5
+
6
+ > **Source of truth:** the four members + their JSDoc in
7
+ > [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts);
8
+ > the wrapper in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/defineMosaicTemplate.ts;
9
+ > the dispatch helpers in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/render/renderTemplate{Lite,Cover,Tutorial}.ts`;
10
+ > the shipped exemplar https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic/media/screencap_grid/v2.
11
+
12
+ | Function | Who calls it | When absent |
13
+ |---|---|---|
14
+ | `render` | the engine, for real output | — (required) |
15
+ | `renderLite` | preview hosts, on the design hot path | falls back to `render` |
16
+ | `renderCover` | the editor, on a pure-default first open | **nothing happens** |
17
+ | `renderTutorial` | the editor, when the user clicks the "?" pill | **nothing happens** |
18
+
19
+ Tier is orthogonal. A core-tier template may declare any of the three optional
20
+ members; `renderLite`'s *capability* rationale (and the RenderHero wiring that
21
+ goes with it) lives in
22
+ [`capability-templates.md`](capability-templates.md).
23
+
24
+ ---
25
+
26
+ ## The dispatch asymmetry (the thing to get right)
27
+
28
+ `renderTemplateLite(tmpl, props, ctx)` falls back to `render` — historical
29
+ behavior, and correct: a preview must always show *something*.
30
+
31
+ `renderTemplateCover` / `renderTemplateTutorial` return `null` and **never** fall
32
+ back. Absence means "behave exactly as before this feature existed". Hosts must
33
+ not synthesize a generic cover or tutorial; the seam encodes that so no host has
34
+ to remember it.
35
+
36
+ Copying the `renderLite` helper when adding the next surface is the mistake to
37
+ avoid — it silently re-introduces "every template gets a cover", which is
38
+ precisely what the opt-in design forbids.
39
+
40
+ ## Error semantics differ per surface, on purpose
41
+
42
+ - **Cover** error or absence → `null` → the host falls through to its normal
43
+ preview path, silently. An error card *about the cover* would recreate the
44
+ broken-first-impression problem the cover exists to fix.
45
+ - **Tutorial** error → `makeErrorMosaic`, rendered inside the tutorial view. The
46
+ user explicitly clicked; a silent no-op reads as a dead button.
47
+
48
+ Both hosts (the Mosaic Desktop / Web app source (not published),
49
+ the Mosaic Desktop / Web app source (not published)) implement this pair
50
+ identically. The desktop bridge methods are **optional** on the channel, so an
51
+ older shell paired with newer web code degrades to "no cover, no pill" rather
52
+ than throwing.
53
+
54
+ ---
55
+
56
+ ## Authoring checklist
57
+
58
+ - **Deterministic and side-effect-free.** No `Date.now`, no `Math.random`.
59
+ - **Browser-safe** if the template ships in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/web.ts — no
60
+ node-only imports.
61
+ - **Never read `ctx.media`.** Both hosts pass `{}` (no ffprobe pass), so these
62
+ surfaces are self-contained by contract.
63
+ - **Size to `ctx.target`.** It carries the editor's canvas dimensions.
64
+ - **Real geometry.** A cover page's bands and a tutorial's diagram are known
65
+ rects — carve m0 cells, don't float drawtext over a full-canvas source. The
66
+ Rect Thesis does not get a pass for onboarding content (see
67
+ [`construction-strategy.md`](construction-strategy.md), "Hard rule: real
68
+ geometry first").
69
+ - **ASCII-only copy.** drawtext renders `→ · — ×` from the bundled font as tofu
70
+ — the same reason `makeErrorMosaic` runs an `asciiOnly()` pass. See
71
+ [`../skills/text-in-templates.md`](../skills/text-in-templates.md).
72
+ - **A tutorial owns its timing.** Declare per-step `durationMs`; never read
73
+ `ctx.target.durationMs`. The host passes no form duration, and a tutorial is
74
+ watched, not rendered into a window.
75
+ - **Invocation props**: the tutorial gets the template's OWN `defaultProps`
76
+ (stable regardless of editor state); the cover gets the working props, which at
77
+ the only moment it shows ARE the defaults — so a cover may ignore props
78
+ entirely.
79
+ - `defineMosaicTemplate` wraps all three with the **capability gate ONLY** — no
80
+ autoCompact, no stamping, no `assertTiming`. Ephemeral preview material, like
81
+ `renderLite`. (Stamping a tutorial would be actively wrong: it declares its own
82
+ per-step durations.)
83
+ - **No new CLI E2E case.** These are unit-tested; they never reach `m0saic make`.
84
+ Same posture as `renderLite`.
85
+
86
+ ---
87
+
88
+ ## Host lifecycle (the Make page)
89
+
90
+ - The cover shows iff `hasRenderCover && !coverDismissed`. `coverDismissed`
91
+ starts `true`, is set at template load from "did this open carry
92
+ caller-supplied props" (so `m0saic open --props` skips the cover), and is
93
+ flipped permanently by the first user prop edit. Deliberately **not** derived
94
+ from `props === defaults` — edit-then-undo must not resurrect it.
95
+ - A cover is a **stand-in**, like a `renderLite` card: it must stay out of the
96
+ render-progress hero filmstrip. Note a template can have a cover *without* a
97
+ `renderLite`, so a hero guard written against `hasRenderLite` alone misses it.
98
+ - The tutorial substitutes only the STAGE (a `stageRenderable` + a
99
+ `tutorial:<id>` doc key). The real document keeps feeding save, geometry-edit,
100
+ doc-stats, and `renderRequest`, so view-only holds by construction rather than
101
+ by discipline — the Make button always renders the real template.
102
+
103
+ ## Meta flags
104
+
105
+ `templates:get` reports `hasRenderLite` / `hasRenderCover` / `hasRenderTutorial`,
106
+ with field-for-field parity in the web `getTemplate` and in the `TemplateMeta`
107
+ type. UI affordances gate on these flags, never on making the call and seeing
108
+ what comes back.
109
+
110
+ ## Exemplar
111
+
112
+ `@m0saic/media/screencap_grid/v2` — `screencap-grid-cover.ts` (a weighted band
113
+ split) and `screencap-grid-tutorial.ts` (six pages, two of them real diagrams:
114
+ an actual gutter-separated 4×4 grid, and an actual pane band over chipped tiles).
115
+ Its `render` keeps its zero-input `makeErrorMosaic` fail-fast contract untouched
116
+ — that is the point. The cover exists so the fail-fast contract can stay strict
117
+ without costing the template its first impression.