@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,310 @@
1
+ # Templates: Contract & Philosophy
2
+
3
+ Entry doc for `docs/templates/`. Defines what a template IS — the
4
+ executable contract and its invariants. How to BUILD one is
5
+ [`construction-strategy.md`](construction-strategy.md); the required authoring
6
+ process (add / deprecate, tests, registry) is the maintainers' agent contract §10 (not published).
7
+
8
+ ## The template contract
9
+
10
+ A template is not "a layout string" — a `MosaicTemplate<P>` is a **typed,
11
+ capability-scoped program**: a globally-identified unit (`id`), a typed props
12
+ interface with schema + canonical defaults (`propsSchema`, `defaultProps`), an
13
+ explicit security contract (`capabilities`), and
14
+ `render(props, ctx) -> Promise<MosaicRenderableFile>` where the renderable is a
15
+ `MosaicDocument` or `MosaicDocumentPipeline`. Templates are the main extension
16
+ point for m0saic.
17
+
18
+ Every template must: return a valid renderable whose emitted m0 strings validate
19
+ (`isValidM0String` / `validateM0String`); stay feasible at intended default
20
+ sizes; be deterministic under `tier: "core"`; declare capabilities honestly
21
+ under `tier: "capability"`. It should document intended resolutions via
22
+ `outputHints.note`, provide safe defaults, and avoid unnecessary complexity for
23
+ human-authored layouts.
24
+
25
+ > **Source of truth:** authoring entrypoints `defineMosaicTemplate` +
26
+ > `definePropsSchema` in [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src);
27
+ > implementations in [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic);
28
+ > the full type contract in https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts.
29
+
30
+ ## Renderables: spatial vs temporal
31
+
32
+ **`MosaicDocument` (spatial)** — a pure spatial description of a single render
33
+ pass: `m0` defines layout geometry only (splits, holes, donations,
34
+ overlays); `config` defines sources and render intent; optional `children`
35
+ enables recursive composition (children render first, their outputs become
36
+ sources). The DSL describes *where tiles exist*; the template decides *what
37
+ fills them*.
38
+
39
+ **`MosaicDocumentPipeline` (temporal)** — composes multiple renderables over
40
+ time: `steps[]` each carry inline `file: MosaicDocument` or a `ref`, explicit
41
+ per-step timing (`durationMs`), explicit transitions (`cut` / `fade`), optional
42
+ pipeline-level fps normalization. A pipeline has no single `m0` string —
43
+ each step has its own spatial layout.
44
+
45
+ ### Document vs pipeline — when to use which
46
+
47
+ Use a **MosaicDocument** when building layouts, split logic, overlays, spatial media
48
+ composition, or reusable building blocks. Use a **MosaicDocumentPipeline** when
49
+ sequencing scenes, slideshows, transitions, fade/cut stitching — combining documents
50
+ over time. Geometry is the document; time is the pipeline; keep the spatial algebra
51
+ pure. (Full pipeline semantics — `emit: "single" | "multi"`, `intermediate`, per-step
52
+ geometry — live in [`rendering-model-contract.md`](rendering-model-contract.md).)
53
+
54
+ ## Output contract (hints vs reality)
55
+
56
+ Templates may provide `outputHints` (width / height / fps / durationMs / note /
57
+ format intent). These are UI recommendations, not enforced requirements — with
58
+ one declaration the contract does ask for:
59
+
60
+ **Every public template declares `outputHints.format`** (the `outputFormat`
61
+ convention, 2026-09-13; record posture — the templates build gate WARNS, never
62
+ fails, and lists it in the Stage 1 line as `warning knob(s): outputFormat`).
63
+ `{ kind: "video", container: "mp4" }` for anything with motion;
64
+ `{ kind: "image", container: "png" }` for a still, plus `pixelFormat: "rgba"`
65
+ when it ships alpha (transparent overlays, QR stamps, wireframes); a template
66
+ with an `outputFormat` knob declares the knob's DEFAULT (the knob still wins
67
+ at render — precedence in
68
+ [`output-resolution-tree.md`](output-resolution-tree.md) §format). Without
69
+ it the CLI names the output `out.mp4` even for a still and a Make share link
70
+ at defaults carries an `f=` ask. Exempt: `internal: true` (building blocks
71
+ render only nested) and `deprecated` (frozen history). Mixed-output templates
72
+ whose kind follows the INPUT (blur-regions, watermark) declare the common
73
+ case; Make derives the real kind from the resolved renderable and the CLI
74
+ switches a defaulted output to png when every input is an image. Audit:
75
+ `auditOutputFormat` (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/auditSchemaConventions.ts).
76
+
77
+ **A canvas that is a knob declares `resolveOutputHints(props)`** (2026-09-15,
78
+ `MosaicTemplate.resolveOutputHints?: (props) => Partial<MosaicTemplateOutputHints>`).
79
+ Pure and cheap — props in, hints out; no ctx, no media, no I/O. Hosts call it
80
+ with the CURRENT props before rendering and seed the target from the result
81
+ merged over the static hints (`resolveTemplateOutputHints` in
82
+ `@m0saic/template-utils`), so the CLI plans at the right canvas, Make's Device
83
+ anchor follows the knob, and the feasibility guard measures the right size.
84
+ Precedence is unchanged: an explicit user ask still wins. `render` reads the
85
+ canvas back from `ctx.target` like every other template and must still lay
86
+ out correctly at ANY target (a host that predates the field ignores it — the
87
+ resolver decides the canvas, it is not the layout). Convention
88
+ `outputHintsResolve` (throw): object, deterministic, and at defaults equal to
89
+ the static hints. First implementer: `@m0saic-dev/creator/drop-calendar/v1`
90
+ (`platform` → canvas + safe area).
91
+
92
+ Authoritative values always come from `ctx` (resolved by host) — but from the right slot:
93
+
94
+ - **`ctx.target` — geometry and timing.** Width/height/fps/duration for everything you
95
+ size or time. It is the per-render slot rect and may differ from `ctx.output` (nested
96
+ renders override only `ctx.target` — https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/render/renderNestedTemplate.ts#L43).
97
+ - **`ctx.output` — format decisions only.** Container/codec/alpha. **Never geometry** —
98
+ sizing off `ctx.output.width/height` is the latent 5× bug documented in
99
+ [`rendering-model-contract.md`](rendering-model-contract.md) Rule 5b.
100
+
101
+ Templates MUST read timing/resolution from `ctx.target` (the only source of time
102
+ — agent contract §10) and stamp resolved values into returned renderables for
103
+ portability and correct nesting (the wrapper does this — internal render-path
104
+ walkthrough: `.ai/moat/templates/cli-template-lifecycle.md`, step 4). Resolution is
105
+ a host decision: be resolution-aware, not resolution-dependent unless documented.
106
+
107
+ **An `outputHints.durationMs` is a HINT, never a pin.** The CLI seeds
108
+ `ctx.output.durationMs` from the hint, so comparing it to your default to detect a
109
+ user ask reads a host default as user intent — dsl-tutorial crammed every walk into
110
+ its 12s hint (gate 33, 2026-09-05). Detect a pin with `resolvePinnedDurationMs(ctx)`
111
+ (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/anim/timing.ts#L156, `userIntent`-only); otherwise
112
+ author the NATURAL length on `doc.durationMs` (template intent out-ranks the hint
113
+ at plan time). On the CLI `.mosaicx` path the wrapper's `durationMs` (or
114
+ `--durationMs`) IS the ask — `mosaicxUserIntent` threads it as
115
+ `userIntent.durationMs` on both `make` and `resolve`
116
+ (the CLI source (not published)); before 2026-09-05 it only set the plan
117
+ length, so a self-timing template was TRIMMED (30s of a 220s walk) while Make
118
+ pinned. Full precedence tree: [`output-resolution-tree.md`](output-resolution-tree.md).
119
+
120
+ ## Capabilities: explicit security contract
121
+
122
+ Every template must declare `capabilities`. This is not optional metadata — it is
123
+ part of the core execution contract. Two tiers:
124
+
125
+ ### Tier: core (deterministic, safe anywhere)
126
+
127
+ `{ tier: "core" }` — fully deterministic, safe to run anywhere without
128
+ sandboxing, suitable for public registries and one-click renders. Core templates
129
+ must not depend on: filesystem access, network access, process execution,
130
+ wall-clock time, non-seeded randomness.
131
+
132
+ #### Core is dynamic — but pure
133
+
134
+ `{ tier: "core" }` does **not** mean static. A core template may generate different
135
+ m0 strings from props, branch on numeric inputs, inspect **engine-provided** media
136
+ metadata (ffprobe-derived dimensions/duration — explicit inputs, not side effects),
137
+ compute layouts programmatically, and use seeded randomness when the seed derives
138
+ from props. Deterministic branching — e.g. `2(1,1)` for landscape media, `2[1,1]`
139
+ for portrait — is fully allowed. The rule is purity, not staticness: core templates
140
+ are **pure functions of their declared inputs** (`props`, `ctx`, engine metadata) —
141
+ same inputs + same engine version → identical `MosaicDocument`.
142
+
143
+ ### Tier: capability (powerful, explicitly granted)
144
+
145
+ `{ tier: "capability", caps: { fs?, net?, exec? } }` — may request filesystem
146
+ access (read/list/write/temp), network access (fetch), process execution
147
+ (spawn). The host (CLI / Desktop / Web) may grant, restrict, or deny.
148
+ **Default-deny applies: only explicitly granted capabilities are exposed on the
149
+ engine context.** Capability templates can fetch external data, build caches,
150
+ preprocess assets, and orchestrate multi-step pipelines — intentionally
151
+ powerful; treat them like code (review source, understand the requested caps).
152
+ Full patterns: [`capability-templates.md`](capability-templates.md).
153
+
154
+ Because templates are typed, validated before render, capability-scoped, and
155
+ deterministic by default, they are the standard mechanism for turning structured
156
+ data (JSON, metrics, event lists) into reproducible media artifacts.
157
+
158
+ ## Props as a UI-renderable schema
159
+
160
+ Props are defined by `propsSchema` (metadata) + `defaultProps` (canonical
161
+ defaults). Prop metadata exists for UI generation, docs, and high-level
162
+ validation. The design goal: every prop must have a clear generic UI
163
+ representation.
164
+
165
+ The full `MosaicTemplatePropType` union
166
+ (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src/template/template.ts#L75-105`, verified 2026-07-27):
167
+
168
+ - primitives: `string`, `number`, `boolean`
169
+ - arrays: `string[]`, `number[]`
170
+ - media: `media`, `media[]`
171
+ - structural: `group`, `list`
172
+ - m0-family (DSL inputs — editor surfaces them as Layout pickers): `m0` (bare
173
+ layout string), `m0c` (m0 with cell labels; can require label names via
174
+ `meta.contract`), `m0p` (named bag of layout variants, enumerated or targeted
175
+ — see `MosaicPropContract`, template.ts:815)
176
+ - `json` — structured payload mapping to a TS interface; renders as a code
177
+ editor, or a structured form when `meta.constraints.jsonSchema` is declared.
178
+ The validator accepts any value for `type: "json"` — render-time parsing /
179
+ validation is the template's job. Prefer this over JSON-string encoding.
180
+ Details: [`reference/json-prop-type.md`](reference/json-prop-type.md).
181
+
182
+ Constraints (`MosaicPropConstraints`, template.ts:108-146): numeric bounds
183
+ (`min`/`max`), array bounds (`minItems`/`maxItems`), linked lengths
184
+ (`lengthOf`), literal enums (`oneOf`), `isColor`, and `jsonSchema` for `json`
185
+ props. (An `isM0saicLayout` flag no longer exists — layout inputs are the
186
+ m0-family prop *types* above.) Editor presentation hints live under `meta.ui`
187
+ (`MosaicPropUI`, template.ts:727).
188
+
189
+ ### Defaults are part of the contract — "a knob shows what it does"
190
+
191
+ Founder ruling 2026-09-05 (gate 33): Make showed dsl-tutorial's `title` EMPTY and
192
+ `showCanvas` OFF while the render used "DSL Tutorial" with the canvas on, because
193
+ those fallbacks lived inside `render()` where no editor can see them. "If they are
194
+ at a default value, the prop showing has to reflect that — enforce it at the
195
+ contract level." An optional knob's UNSET state must be visible:
196
+
197
+ | optional knob | must carry |
198
+ |---|---|
199
+ | `boolean` | a `defaultProps` value (a toggle cannot show "unset" — it shows OFF) |
200
+ | closed-set `string`/`number` (`constraints.oneOf` / `control.options`) | a `defaultProps` value (a picker shows "—" otherwise) |
201
+ | plain `string`/`number` | a `defaultProps` value OR `meta.control.placeholder` naming the unset behaviour ("auto") |
202
+ | required · `ui.hidden` · `ui.consumer:"human"` · media / json / lists / m0-family / code / a group's own presence | exempt (inputs, not defaults); a `group` is audited field by field against its default object |
203
+
204
+ Keep `render()` fallbacks as belt-and-braces only; the schema is the truth an
205
+ editor shows.
206
+
207
+ **Where it fires:** inside `defineMosaicTemplate` — the one seam every registered
208
+ template passes through — at DEFINITION time (module import / `registerTemplate`),
209
+ so the author sees it, not a test they may never run. Code
210
+ (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template, verified 2026-09-05; re-verify:
211
+ `grep -n "^export function\|^export class" https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/{auditDefaultProps,templateConventions,templateOriginScope}.ts`):
212
+
213
+ - `auditDefaultProps` / `assertDefaultPropsComplete` — the pure rule
214
+ (`auditDefaultProps.ts:97` / `:108`).
215
+ - `enforceTemplateConventions` + `TemplateConventionError` (`templateConventions.ts:106`
216
+ / `:51`) — the seam; findings log `listTemplateConventionFindings` /
217
+ `drainTemplateConventionFindings` (`:81` / `:86`).
218
+ - **Posture follows the ambient origin scope** (`templateOriginScope.ts:50`, shared by
219
+ the registry and the wrapper): first-party → **throws**; inside
220
+ `withExternalTemplateOrigin` (`templateRegistry.ts:274` — an external repo,
221
+ including one whose module body calls `defineMosaicTemplate` directly at import)
222
+ → **recorded**, never thrown, so one bad template can't abort a repo load; hosts
223
+ read the log. `defineMosaicTemplate(t, { conventions: "record" | "throw" })`
224
+ overrides.
225
+ - **No escape hatch.** The 76 pre-contract templates were migrated the day the
226
+ contract landed (2026-09-05, 366 knobs: a real default wherever the render had a
227
+ literal fallback, a placeholder wherever the value is derived from theme / data /
228
+ mode), so every first-party template passes at import; the registry sweep
229
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/default-props-audit.test.ts restates the guarantee.
230
+ - **Where a file defines a base and a runner under one id** (`brand/logo/v3`:
231
+ `logo.ts` + `logo_runner.ts`), the REGISTERED object is the one the seam audits.
232
+ - **A closed set must contain the unset state.** `wireframe/*`'s `preset` picker had
233
+ no "no preset" option, so unset could not be shown: add the option (`"none"` =
234
+ mode-driven), default it, and normalise it back to `undefined` at the top of
235
+ `render()` — never default a real preset over "no preset".
236
+
237
+ Lock per template: `expect(auditDefaultProps(T)).toEqual([])` plus an
238
+ explicit-equals-implicit render in the gate test. Reference:
239
+ [`reference/template-flags.md`](reference/template-flags.md).
240
+
241
+ ## Identity, nesting, and ownership
242
+
243
+ Templates may stamp `editor` metadata (UI-only ownership/labeling/provenance)
244
+ and `engine` metadata (diagnostics) on renderables; neither affects rendering.
245
+ Identity inside the DSL is structural (StableKeys); template attachment is
246
+ explicit via config/children — the DSL does not name tiles. Full nested-render
247
+ semantics: [`recursion-nested-rendering.md`](recursion-nested-rendering.md).
248
+
249
+ ## Provenance & trust flair
250
+
251
+ **Trust comes from the HOST-stamped `provenance` (`builtin` | `community` |
252
+ `external`), never from anything a repo declares about itself** — `repoId`,
253
+ `displayName`, template-id scope, homepage are all self-declared. Always call
254
+ `deriveTemplateSource(templateId, meta)` WITH the meta
255
+ (the Mosaic Desktop / Web app source (not published)); the id-prefix fallback
256
+ exists only for hosts that predate the stamp. (The Templates card called it with
257
+ the id alone until 2026-09-17, and an external repo publishing `@m0saic/…` ids
258
+ wore a VERIFIED ribbon.)
259
+
260
+ **Three protected namespaces get official flair; everything else is 3P.**
261
+
262
+ | Namespace | Provenance | Flair |
263
+ |---|---|---|
264
+ | `@m0saic/…` (built-in) | `builtin` | VERIFIED ribbon / badge |
265
+ | `@m0saic-dev/…`, `@m0saic-community` (the community pack) | `community` → kind `curated` | COMMUNITY ribbon, `Community · official` badge (same accent family) |
266
+ | anything else | `external` | 3P ribbon + the third-party banner / note |
267
+
268
+ Official templates ship inside the app; they are never loaded from a repo. The
269
+ registry enforces the same three prefixes (`RESERVED_TEMPLATE_ID_PREFIXES` in
270
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/templateRegistry.ts): an external
271
+ registration under any of them is refused (`TEMPLATE_ID_RESERVED_NAMESPACE`)
272
+ unless the host opened the origin scope with a matching `trustedNamespaces`
273
+ grant (only the pinned community repo).
274
+
275
+ **Lookalikes get the louder warning.** `assessOfficialLookalike(templateId,
276
+ meta, source)` (the Mosaic Desktop / Web app source (not published)) flags an
277
+ EXTERNAL repo whose id scope is a protected namespace, or whose `repoId` /
278
+ display name / source URL reads as m0saic or the community pack after folding
279
+ case, leetspeak digits and separators (`m0saic`, `mo5aic`, `M-0-S-A-I-C` →
280
+ `mosaic`; tokens: mosaic, community, official, verified, curated, builtin).
281
+ Filesystem paths are never judged (every dev checkout lives under
282
+ `…/m0saic/…`), and neither is `repoHomepage` (the public starter ships
283
+ `github.com/m0saic/template-repo-starter`, and every un-edited fork carries
284
+ it). Surfaces: `3P · NOT m0saic` ribbon (TemplateCard), red badge + "Not an
285
+ m0saic template" note (TemplateInfoModal), red banner (Make), and a per-URL
286
+ callout in the consent gate (`sourceLooksOfficial` — URL only, since no code
287
+ has loaded). The classifier can only ESCALATE a warning; it never grants trust.
288
+
289
+ An adversarial fixture exercises every layer in one folder:
290
+ the Mosaic Desktop / Web app source (not published) (README has the desktop
291
+ test steps; locked by the Mosaic Desktop / Web app source (not published)).
292
+
293
+ ## Template categories
294
+
295
+ Practically, templates fall into three families: **structural** (human-authored
296
+ split hierarchies, predictable, maintainable), **engine-native**
297
+ (generated/dictionary-driven — deep overlay chains, large strings, optimized for
298
+ deterministic emission over readability), and **advanced integrations**
299
+ (capability-tier, data-connected). When to use which style — and the live
300
+ category folder list — is [`construction-strategy.md`](construction-strategy.md).
301
+
302
+ ## Status flags: `internal` / `primitive` / `deprecated`
303
+
304
+ Three orthogonal flags describe whether and how a template surfaces: `primitive`
305
+ (informational badge — a base others build on, still standalone), `internal`
306
+ (visibility gate — **not intended as a top-level pick**, hidden from listings but
307
+ available for nested rendering; it may or may not render standalone — the flag
308
+ doesn't decide that), `deprecated` (visibility gate — superseded, hidden with a
309
+ "use X instead" pointer). Truth table, code pointers, surfaces, and the curation
310
+ rules: [`reference/template-flags.md`](reference/template-flags.md).
@@ -0,0 +1,138 @@
1
+ # Children, recursion, and nested rendering
2
+
3
+ How `children` work inside a `MosaicDocument`, and how nested documents and
4
+ pipelines evaluate. Source of truth: `renderNestedTemplate`
5
+ ([https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/render/renderNestedTemplate.ts](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/render/renderNestedTemplate.ts));
6
+ document types in [https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src](https://github.com/m0saic-project/m0saic-packages/blob/main/packages/types/src).
7
+
8
+ The core rule — **bottom-up evaluation**: for a document D, every child C in
9
+ `children` renders fully first; C's rendered output is then treated as a media
10
+ source; D renders last from those resolved inputs. Each document stays spatially
11
+ pure, children resolve before parent composition, and no circular dependencies
12
+ are possible. Children may themselves contain children — recursion depth is
13
+ unbounded in principle, limited only by practical engine constraints.
14
+
15
+ ## A nested mosaic is also a clip region
16
+
17
+ Children have a second, less obvious use: **bounding an animated overlay's pixels.**
18
+
19
+ ### The trap
20
+
21
+ `MosaicMediaSource.overlay.xExpr` / `.yExpr` are **offsets** handed to ffmpeg's
22
+ `overlay` filter. That filter positions the layer — it does **not** clip it to the
23
+ destination rect. So a source placed in a cell, even with `placement.inset` and a
24
+ correct `fit: "contain"`, will happily paint **outside** its cell once `xExpr` /
25
+ `yExpr` push it there. `applyInsetToRect`
26
+ (the render engine source (not published), verified 2026-07-27) insets the
27
+ *destination*; it does not constrain the *payload*. The natural assumption —
28
+ "it's placed in a cell, so it stays in the cell" — is wrong.
29
+
30
+ Worst offenders:
31
+
32
+ - **Diagonal sweeps** — a tilted band's horizontal footprint is
33
+ `sin(angle)·tileSize + 2·halfWidth/cos(angle)`, easy to under-estimate.
34
+ - **Offscreen entry/exit** — the layer is *intentionally* outside the rect at the
35
+ extremes of the animation.
36
+ - **Procedural offsets** where no static bound is obvious.
37
+
38
+ ### The fix is structural, not arithmetic
39
+
40
+ Don't tighten the math — **wrap the moving layer in a child mosaic.** Per the
41
+ bottom-up rule, the engine renders each child into an intermediate framebuffer
42
+ sized to its allocated frame, then composites that result. A finite buffer clips
43
+ by construction: pixels the inner overlay tries to paint past the edge have
44
+ nowhere to land.
45
+
46
+ Parent — swap the moving media source for a `type: "mosaic"` source:
47
+
48
+ ```ts
49
+ sources.push({
50
+ type: "mosaic",
51
+ ref: "<child-key>",
52
+ placement: { fit: "contain", hAlign: "left", vAlign: "top", inset: { /* … */ } },
53
+ overlay: { blendMode: "screen", enable: enableExpr }, // re-declare the gate here too
54
+ });
55
+ ```
56
+
57
+ Child — a transparent base plus the moving layer:
58
+
59
+ ```ts
60
+ doc.children["<child-key>"] = {
61
+ kind: "mosaic_document",
62
+ version: 1,
63
+ m0: toM0String("F{F}", "ChildName"),
64
+ assets: { /* the moving layer */ },
65
+ sources: [
66
+ { type: "lavfi", color: "black@0" }, // transparent base = the framebuffer
67
+ { type: "media", mediaType: "image", assetId: id,
68
+ placement: { fit: "contain" },
69
+ overlay: { xExpr: sweepXExpr, enable: enableExpr } }, // clipped at the edge
70
+ ],
71
+ };
72
+ ```
73
+
74
+ `color: "black@0"` is the idiomatic transparent base. Intermediates carry alpha by
75
+ default, so the composite stays clean.
76
+
77
+ ### Cost, and when to skip it
78
+
79
+ One extra encode pass per nested mosaic — negligible for a small overlay (corner
80
+ badge, shimmer on a 10–20% cell) against the parent's full-canvas encode, but not
81
+ free. Skip the wrap when the offset is bounded by static math **and you have
82
+ verified it** at every value (rare, historically easy to get wrong), or when you
83
+ *want* the bleed (a shadow or glow deliberately falling outside its source).
84
+
85
+ ### Reference — read the history, not just the code
86
+
87
+ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic/media/qr/stamp-video/v1 wraps its gleam sweep in
88
+ `children["qr_stamp_gleam"]` (`qr-stamp.ts:536`), with a ±0.7w sweep in
89
+ `gleamBand.ts` `buildGleamSweepXExpr` that pushes the band fully offscreen at both
90
+ extremes — **only safe because the child clips.** It shipped with the bleed bug
91
+ first; the wrap was the fix.
92
+
93
+ > ⚠️ **That template is `deprecated`** (replacement `@m0saic/media/qr/stamp/v1`),
94
+ > and its deprecation note retires the gleam sweep *by design*: "decoration
95
+ > belongs baked into the input media, not computed in the stamp." It stays
96
+ > registered as the reference adaptive video stamp. So this is currently a
97
+ > **pattern with no live consumer** — the engine behavior it exploits is real and
98
+ > unchanged, but if you reach for it, you are the first user in the current
99
+ > shelf. Weigh the "bake it into the input instead" argument before adding an
100
+ > animated overlay at all.
101
+
102
+ Related: the same child mechanism is the vehicle for complexity pushdown — see
103
+ [`../runtime/reduce-to-one.md`](../runtime/reduce-to-one.md). Clipping and
104
+ pushdown are two applications of one feature.
105
+
106
+ ## Children fill tiles; they never change geometry
107
+
108
+ children?: Record<string, MosaicDocument | MosaicDocumentPipeline>
109
+
110
+ The m0 string defines geometry only — it does not name tiles. Template code maps
111
+ logical tiles to child entries by StableKey: parse the m0, identify target tiles,
112
+ attach children under consistent keys. StableKeys are structural, so they stay
113
+ stable across resolution changes — the mapping is robust.
114
+
115
+ Children do NOT modify the parent's m0 string, split behavior, overlay logic, or
116
+ StableKey generation. They only fill existing tiles: the DSL is *shape*,
117
+ `children` is *content*.
118
+
119
+ ## Pipelines as children
120
+
121
+ A child entry may be a `MosaicDocumentPipeline` rather than a document. The
122
+ pipeline renders first; its final stitched output is treated as media that the
123
+ parent consumes. This enables animated sub-tiles, time-sequenced inserts, and
124
+ scene-within-scene structures without polluting the DSL with time.
125
+
126
+ ## Determinism
127
+
128
+ Nested rendering stays deterministic when all child templates are deterministic,
129
+ no capability introduces nondeterminism, and props + ctx.output are fixed —
130
+ bottom-up evaluation then guarantees reproducibility.
131
+
132
+ ## Flags (`internal: true` etc.)
133
+
134
+ Template status flags — `internal` (not intended as a top-level pick: hidden
135
+ from public registries, available for nested rendering; standalone renderability
136
+ varies per template), `primitive`, `deprecated` — are owned by
137
+ [`reference/template-flags.md`](reference/template-flags.md). Internal templates are the
138
+ natural children in layered template architectures.
@@ -0,0 +1,104 @@
1
+ # grid
2
+
3
+ Canonical dsl-stdlib builder for grid-layout m0 strings.
4
+
5
+ > **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/builders/grid.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/builders/grid.ts) (`GridOptions` / `GridResult` in the same file).
6
+
7
+ ## Default mode: the gutterless simple path
8
+
9
+ When the call has **no gutter, no `outerGutters`, no `cellWeightBase`, and no
10
+ output dimensions**, the builder short-circuits (`grid.ts:65-82`): compact
11
+ nested equal splits via `equalSplit`, one row expression per row, claimant `1`:
12
+
13
+ ```
14
+ grid({ rows: 3, cols: 2 }).m0 // "3[2(1,1),2(1,1),2(1,1)]"
15
+ grid({ rows: 1, cols: 4 }).m0 // "4(1,1,1,1)" (rows=1 → just the row expr)
16
+ ```
17
+
18
+ No weight math at all. Result metadata reflects the unit basis:
19
+ `{ cellW: 1, cellH: 1, gutterW: 0, totalX: cols, totalY: rows }`. The per-axis
20
+ basis is just the cell count — minimal quantization risk, nests cleanly.
21
+ **This is the shape the ratio-grid recipe builds on** (gutterless `grid()` +
22
+ gap carved with `latticeCellInset` — see "Uniform grids" in
23
+ [`../construction-strategy.md`](../construction-strategy.md)). Reach for this
24
+ first; the weighted machinery below exists for DSL-baked gutters.
25
+
26
+ ## Weighted mode: gutters and resolution-aware weights
27
+
28
+ Any of `gutter > 0`, `outerGutters: true`, `cellWeightBase`, `outputWidth`, or
29
+ `outputHeight` routes to the weighted path (`grid.ts:84-154`): cells become
30
+ weight-`cellW` claimants, gaps become blank (`-`) tokens of weight `gutterW`,
31
+ built via `strip` per axis. For the weight system itself (donation, `splitEven`,
32
+ outside-in remainder) see [`../../handbook/dsl-rules.md`](../../handbook/dsl-rules.md);
33
+ for why large weight bases quantize badly (px-per-weight collapse), see
34
+ [`../../handbook/feasibility-precision-quantization.md`](../../handbook/feasibility-precision-quantization.md).
35
+
36
+ ### Cell weight (X) and auto-scaling
37
+
38
+ Priority (`grid.ts:93-108`): explicit `cellWeightBase` wins; else if
39
+ `outputWidth` is given, auto-scale to keep ≥ `MIN_PX_PER_WEIGHT = 4` px per
40
+ weight unit; else `DEFAULT_CELL_WEIGHT = 50`.
41
+
42
+ ```
43
+ maxTotalX = floor(outputWidth / 4) // MIN_PX_PER_WEIGHT
44
+ cellW = max(2, min(50, floor((maxTotalX - gutterSlots) / cols)))
45
+ ```
46
+
47
+ (`gutterSlots` approximates gutterW=1 for the budget; the `max(2, …)` clamp is
48
+ in `grid.ts:99-105`.) Then `gutterW = max(1, round(cellW * gutter))`. Keeping
49
+ px-per-weight ≥ 4 bounds the `splitEven` remainder at ≤ 25% of the base
50
+ allocation. Trade-off: smaller `cellW` coarsens gutter-ratio precision — the
51
+ minimum non-zero gutter is `1/cellW` of cell width (2% at the default 50).
52
+
53
+ ### Equal pixel gaps across axes (`cellH` derivation)
54
+
55
+ With both output dimensions, `cellH` is derived so one weight unit maps to the
56
+ same pixel count on both axes (`grid.ts:117-130`) — otherwise a `gutterW: 1` gap
57
+ could be 6px across and 3px down:
58
+
59
+ ```
60
+ Goal: outputWidth / totalX = outputHeight / totalY
61
+ totalX = cols * cellW + gutterCountX * gutterW
62
+ targetTotalY = totalX * outputHeight / outputWidth
63
+ cellH = max(1, round((targetTotalY - gutterCountY * gutterW) / rows))
64
+ ```
65
+
66
+ `gutterW` stays identical on both axes; the cell weights absorb the aspect ratio.
67
+
68
+ ## Options
69
+
70
+ | Option | Type | Default | Description |
71
+ |--------|------|---------|-------------|
72
+ | `rows` | number | required | Grid row count |
73
+ | `cols` | number | required | Grid column count |
74
+ | `gutter` | number | 0 | Gap ratio relative to cellW. gutterW = max(1, round(cellW * gutter)) |
75
+ | `outerGutters` | boolean | false | Add gutter padding on all 4 edges, not just between cells |
76
+ | `cellWeightBase` | number | 50 (or auto) | Explicit X cell weight. Overrides auto-scaling. |
77
+ | `outputWidth` | number | - | Output pixel width. Enables auto-scaling and equal-gap correction. |
78
+ | `outputHeight` | number | - | Output pixel height. Enables equal-gap correction. |
79
+
80
+ ## Result
81
+
82
+ | Field | Description |
83
+ |-------|-------------|
84
+ | `m0` | The DSL string (field is `m0`, NOT `m0saic` — `GridResult`, https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-stdlib/src/builders/grid.ts#L22) |
85
+ | `order` | Row-major index array (always `[0, 1, 2, ...]`) |
86
+ | `totalX` | Total X weight count (`cols` on the simple path) |
87
+ | `totalY` | Total Y weight count (`rows` on the simple path) |
88
+ | `cellW` | X-axis cell weight (`1` on the simple path; may be auto-scaled) |
89
+ | `cellH` | Y-axis cell weight (equals cellW when no output dimensions) |
90
+ | `gutterW` | Gutter weight (0 when no gutter) |
91
+
92
+ > ⚠️ **When to pass `outputWidth`/`outputHeight` (corrected 2026-07-26).** Pass them
93
+ > only when you are keeping DSL-baked gutters at the **head** of a document. For
94
+ > primitives and anything that nests, the current guidance is the opposite: use the
95
+ > **gutterless** simple path and carve gaps afterwards with `latticeCellInset`. A
96
+ > pixel-derived gutter basis re-introduces the coprime-basis blowup
97
+ > (`~30,316 → ~2,211` nodes measured) — see the ratio-grid recipe in
98
+ > [`../construction-strategy.md`](../construction-strategy.md).
99
+
100
+ ## Golden tests
101
+
102
+ Wireframe PNG goldens live in per-suite dirs under https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-visual-tests/src
103
+ (e.g. `stdlib/grid/__goldens__/`, `stdlib/split/__goldens__/`,
104
+ `stdlib/snapGrid/__goldens__/`, `brand/__goldens__/`). Run: `npm test --prefix https://github.com/m0saic-dsl/m0/blob/main/packages/dsl-visual-tests