@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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/docs/README.md +60 -0
- package/docs/file-formats/m0-iteration-protocol.md +120 -0
- package/docs/file-formats/m0p-and-custom-field.md +195 -0
- package/docs/handbook/README.md +27 -0
- package/docs/handbook/composition-arithmetic.md +278 -0
- package/docs/handbook/dsl-complexity.md +75 -0
- package/docs/handbook/dsl-rules.md +367 -0
- package/docs/handbook/feasibility-precision-quantization.md +591 -0
- package/docs/handbook/m0-construction-methods.md +201 -0
- package/docs/handbook/precision-tiers.md +84 -0
- package/docs/m0saic-thesis.md +95 -0
- package/docs/runtime/README.md +17 -0
- package/docs/runtime/cli-usage.md +372 -0
- package/docs/runtime/ffmpeg-expression-limits.md +117 -0
- package/docs/runtime/reduce-to-one.md +96 -0
- package/docs/skills/README.md +40 -0
- package/docs/skills/axis-and-geometry.md +103 -0
- package/docs/skills/dsl-stdlib-method-catalog.md +7 -0
- package/docs/skills/identity.md +123 -0
- package/docs/skills/labels-and-masks.md +170 -0
- package/docs/skills/m0saic-string-generation.md +251 -0
- package/docs/skills/more-atoms-not-bigger-atoms.md +77 -0
- package/docs/skills/overlay-semantics.md +194 -0
- package/docs/skills/parse-apis.md +79 -0
- package/docs/skills/passthrough-semantics.md +136 -0
- package/docs/skills/structural-construction.md +86 -0
- package/docs/skills/text-in-templates.md +126 -0
- package/docs/skills/zero-overlay-analysis.md +87 -0
- package/docs/templates/README.md +65 -0
- package/docs/templates/capability-templates.md +72 -0
- package/docs/templates/construction-strategy.md +329 -0
- package/docs/templates/data-pipeline.md +324 -0
- package/docs/templates/emission-patterns.md +130 -0
- package/docs/templates/geometry-recipes.md +248 -0
- package/docs/templates/layout-contract.md +168 -0
- package/docs/templates/output-resolution-tree.md +202 -0
- package/docs/templates/patterns/case-study-lessons.md +69 -0
- package/docs/templates/patterns/perf-authoring-rules.md +100 -0
- package/docs/templates/patterns/primitive-extraction-pattern.md +103 -0
- package/docs/templates/philosophy-and-contract.md +310 -0
- package/docs/templates/recursion-nested-rendering.md +138 -0
- package/docs/templates/reference/grid.md +104 -0
- package/docs/templates/reference/json-prop-type.md +169 -0
- package/docs/templates/reference/mosaic-color.md +81 -0
- package/docs/templates/reference/mosaic-placement-props.md +103 -0
- package/docs/templates/reference/prop-bindings.md +203 -0
- package/docs/templates/reference/template-flags.md +205 -0
- package/docs/templates/render-lifecycle.md +117 -0
- package/docs/templates/rendering-model-contract.md +392 -0
- package/docs/templates/standalone-pack-authoring.md +233 -0
- package/docs/templates/theming.md +81 -0
- package/docs/templates/ui-controls.md +150 -0
- 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
|