@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,126 @@
1
+ # Text in templates
2
+
3
+ How to put text into a template without it silently clipping.
4
+
5
+ ---
6
+
7
+ ## ⚠️ The rule that bites: nothing soft-wraps
8
+
9
+ **No rasterizer in m0saic wraps text.** A long string in a single `MosaicTextLayer`
10
+ renders as **one line and clips off the cell** — no error, no warning, no visual cue
11
+ in the plan. It just disappears past the edge.
12
+
13
+ This is true for **both** rasterizers:
14
+
15
+ - `drawtext` (ffmpeg) has no soft-wrap.
16
+ - `rasterizer: "svg"` converts glyphs to outlines via `@m0saic/text` — there is no
17
+ wrapping logic in that path either.
18
+
19
+ **Any template that puts user-typed or composed strings into text MUST pre-break
20
+ them.** Use `multilineTextLayers` (below); don't hand-roll.
21
+
22
+ The failure is worst with user data — a title that fits every fixture and clips on the
23
+ one real input nobody tested.
24
+
25
+ ---
26
+
27
+ ## The source shape
28
+
29
+ A text tile is one `MosaicTextSource` per leaf cell:
30
+
31
+ ```ts
32
+ {
33
+ type: "text",
34
+ style: { fontSize, fontColor, fontFamily }, // source-level, applies to all layers
35
+ layers: [ /* MosaicTextLayer[] — one per LINE */ ],
36
+ placement: { hAlign, vAlign }, // optional if layers carry their own
37
+ visual: { backgroundColor }, // solid fill behind the text
38
+ renderMode: { kind: "image" }, // see below
39
+ }
40
+ ```
41
+
42
+ A `MosaicTextLayer` is
43
+ `{ content: { kind: "literal", text } | { kind: "expr", expr }, style?, placement? }`.
44
+ Layers stack within one source; each may carry its own `placement` (including a
45
+ `yExpr`) to position itself in the cell.
46
+
47
+ ### Two orthogonal knobs — don't confuse them
48
+
49
+ | Knob | Values | Question it answers |
50
+ |---|---|---|
51
+ | `renderMode` | `{ kind: "image" }` \| `{ kind: "video" }` | single RGBA frame, or an RGBA video for `ctx.target.durationMs`? |
52
+ | `rasterizer` | `"drawtext"` \| `"svg"` | which mechanism turns glyphs into pixels? |
53
+
54
+ **Choose the rasterizer on dynamism**, per `templates/patterns/perf-authoring-rules.md`
55
+ **R9**: `svg` for static text (a mask + colour tile — no drawtext, no per-frame
56
+ shaping), `drawtext` only when the text is genuinely per-frame dynamic. `svg` does
57
+ **not** support expressions; the fallback to drawtext for those is correct. Read R9
58
+ before scaling up — it carries the density budgets (hundreds of glyph masks in one
59
+ panel approach the argv wall) and the note that **the app fits text wider than the
60
+ CLI**, so leave generous fixed-font margins.
61
+
62
+ ---
63
+
64
+ ## Helpers — use these, don't hand-roll
65
+
66
+ All from `@m0saic/template-utils`:
67
+
68
+ | Helper | Signature | Use |
69
+ |---|---|---|
70
+ | `multilineTextLayers` | `({ text, maxCharsPerLine, fontSize, lineHeightRatio?, hAlign? }) => MosaicTextLayer[]` | **The one you usually want.** Wraps and returns one centred layer per line, vertically stacked via `yExpr`. Feed straight into `layers`. |
71
+ | `wrapText` | `(text, maxCharsPerLine) => string[]` | Greedy word-wrap by character count. Never splits a word. Use when you need the lines themselves. |
72
+ | `makeErrorMosaic` | `(message, { width, height, title?, errorCode?, … })` | Fail-fast card that **also sets `engine.renderStatus = "error"`** — the UI disables Make, and the CLI exits **3** (RENDER DEGRADED) after writing the card. Use for invalid input, and **pass `errorCode`**: it is what lands in the `--report` sidecar's `renderErrorCodes` (omit it and the caller only sees `ENGINE_ERROR`). See `runtime/cli-usage.md` → "Exit codes". |
73
+ | `makeStubMosaic` | `(label, { width, height, backgroundColor?, textColor?, note? })` | "STUB" placeholder. Auto-sizes the font. Does **not** mark an error — validate-only still passes. |
74
+ | `animateNumbersInText` | `(text, { startSec, durationSec, ease }) => string` | Returns an `expr` that counts numbers up on intro (KPI / stat cards). |
75
+ | `fadeInExpr` | `(startSec, durSec, ease?) => string` | Overlay-alpha expression for a timed fade-in. |
76
+
77
+ > `multilineTextLayers` returns `[]` for empty input — **check and omit the source**
78
+ > rather than emitting a text tile with no layers.
79
+
80
+ > `makeErrorMosaic` vs `makeStubMosaic` is a real distinction, not a style choice:
81
+ > error blocks the render path, stub does not. Reach for stub only when "not
82
+ > implemented yet" is a legitimate state.
83
+
84
+ Both size their cards from dimensions **you** pass — pass `ctx.target.{width,height}`,
85
+ never `ctx.output` (see `rendering-model-contract.md` Rule 5b).
86
+
87
+ ---
88
+
89
+ ## Sizing
90
+
91
+ `maxCharsPerLine` is coarse — glyph widths vary by font. Start from:
92
+
93
+ ```
94
+ maxCharsPerLine = floor(cellWidthPx / (fontSize * 0.55))
95
+ ```
96
+
97
+ and tune per font. **Scale `fontSize` to the cell** — `clamp(width / N, lo, hi)` —
98
+ rather than hard-coding pixels, so the template survives a resolution change.
99
+
100
+ ### Canonical wrapped, centred text tile
101
+
102
+ ```ts
103
+ import { multilineTextLayers } from "@m0saic/template-utils";
104
+
105
+ function textTile(text: string, fontSize: number, bg: string, cellWidth: number) {
106
+ const maxChars = Math.max(8, Math.floor(cellWidth / (fontSize * 0.55)));
107
+ return {
108
+ type: "text" as const,
109
+ style: { fontSize, fontColor: "#ffffff", fontFamily: "Arial" },
110
+ layers: multilineTextLayers({ text, maxCharsPerLine: maxChars, fontSize }),
111
+ visual: { backgroundColor: bg },
112
+ renderMode: { kind: "image" as const },
113
+ };
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Related
120
+
121
+ - **`templates/patterns/perf-authoring-rules.md` R9** — rasterizer choice + density budgets.
122
+ - **`templates/construction-strategy.md`** — "Drawing shapes: SVG inline-mask on a
123
+ color source". Text is for *words*; never draw a shape with a glyph — ffmpeg drops
124
+ `▲`/`▼`/emoji as tofu.
125
+ - **`templates/rendering-model-contract.md` Rule 5b** — size error/stub cards off
126
+ `ctx.target`.
@@ -0,0 +1,87 @@
1
+ # The `0{}` operator — phantom geometry, deferred paint
2
+
3
+ `0` is a passthrough: it donates its space to the next claimant and renders nothing.
4
+ `0{...}` is a passthrough whose overlay still renders: the base donates space as
5
+ usual, but the body receives the accumulated carry rect and **paints on top of the
6
+ claimant, deferred**. An element that gives its space away, then draws over the
7
+ sibling that absorbed it. Trimmed 2026-07-27 from a longer essay (git history).
8
+
9
+ > **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts)
10
+ > (+ `parse/sortDeferredOverlays.ts`). Basics: [`passthrough-semantics.md`](passthrough-semantics.md),
11
+ > [`overlay-semantics.md`](overlay-semantics.md).
12
+
13
+ ## The four quadrants
14
+
15
+ | | Owns space | Doesn't own space |
16
+ |---|---|---|
17
+ | **Paints** | `1` (tile) | `0{}` (phantom overlay) |
18
+ | **Doesn't paint** | `-` (null gap) | `0` (passthrough) |
19
+
20
+ `0{}` is the fourth quadrant: spatial accounting and visual presence, fully
21
+ decoupled.
22
+
23
+ ## The mechanism (verified at runtime)
24
+
25
+ `3(1, 0{1}, 1)` @ 1080×720 — three 360px slots; the `0` donates, the last tile
26
+ claims 720px; the overlay body gets the merged carry rect and paints LAST:
27
+
28
+ | Paint order | What | Rect | `logicalIndex` |
29
+ |---|---|---|---|
30
+ | 0 | first tile | `{x:0, w:360}` | 0 |
31
+ | 1 | claimant (expanded) | `{x:360, w:720}` | 2 |
32
+ | 2 | deferred zero-overlay | `{x:360, w:360}` | 1 |
33
+
34
+ Note the split between orders: the overlay is `logicalIndex` **1** (encountered
35
+ before the claimant in DFS) but paints **after** it. Build sources in logical
36
+ order; paint order is derived.
37
+
38
+ **Multiple `0{...}` before one claimant sort area-DESCENDING at flush** — widest
39
+ paints first (background), narrowest last (top). `5(0{1},0{1},0{1},0,1)` @1000px:
40
+ claimant 1000px paints first, then 600 → 400 → 200 on top. Free z-ordering from
41
+ coverage, no z-index.
42
+
43
+ **Rects grow monotonically:** each `0{...}` in a run captures the prefix sum of
44
+ slot widths, anchored at the run start — `4(0{1},0{1},0{1},1)` @800px yields
45
+ overlays at `{x:0, w:200/400/600}` under a full-width claimant. Concentric framing
46
+ from pure geometry.
47
+
48
+ **Trailing `0{}` is always invalid** — `PASSTHROUGH_TO_NOTHING`; an overlay does
49
+ not exempt the donation from needing a claimant. `2(1,0{1})` invalid;
50
+ `2(0{1},1)` valid.
51
+
52
+ ## The body is a full m0 sub-expression
53
+
54
+ Splits, nulls, passthroughs, nested overlays — all legal inside `0{...}`, parsed
55
+ into the merged rect like an independent document. So a phantom can carry an entire
56
+ sub-layout: holes that let the claimant show through (`0{4(1,-,-,1)}`), stacked
57
+ rows (`0{3[1,1,1]}`), thin gridline rules at precise offsets
58
+ (`0{100[0,…,1,…,0,1,…]}`), even nested overlay depth (`0{2(1{1},1{1})}` — four
59
+ sources from one zero-space element). Source count = whatever the body demands.
60
+
61
+ ## Patterns (one line each)
62
+
63
+ - **Floating annotation** — grid-positioned callout over an expanded tile's region.
64
+ - **Progressive reveal** — monotone widths + per-source `startAtSec`: layers peel
65
+ front-to-back.
66
+ - **Gradient banding** — N phantoms at fractional widths, solid colors at
67
+ decreasing opacity.
68
+ - **Watermark/badge** — a tiny phantom deep in a 100-split places a corner stamp;
69
+ content and stamp stay structurally independent.
70
+ - **Conditional layer** — phantom sources honor `overlay.enable` windows: a flash
71
+ highlight with zero structural change.
72
+ - **Debug overlay** — inject `0{1}` tints/labels during development; removing `{1}`
73
+ restores the bare `0` with geometry untouched.
74
+
75
+ ## When `0{}` is wrong
76
+
77
+ - The layer should CLAIM space (push siblings) → use `1` or `-`.
78
+ - The template has many sources and non-expert consumers → the logical/paint order
79
+ split confuses source mapping; keep `0{}` inside internal primitives.
80
+
81
+ ## For agents
82
+
83
+ You will almost never need `0{}` — `1`, `-`, `0`, and `1{1}` cover ~99% of
84
+ layouts. Reach for it only when all three hold: (1) a visual layer at a specific
85
+ grid position, (2) painting on top of whatever fills that region, (3) claiming no
86
+ layout space. When all three hold, `0{}` is the correct expression, not a
87
+ workaround.
@@ -0,0 +1,65 @@
1
+ # templates/ — the authoring surface
2
+
3
+ Read as a path: **contract → build → verify → publish**. The required authoring
4
+ process (add / deprecate / tests / registry) is the agent contract §10 — not
5
+ duplicated here.
6
+
7
+ ## The path
8
+
9
+ 1. [`philosophy-and-contract.md`](philosophy-and-contract.md) — what a template
10
+ IS: the typed contract, ctx.target vs ctx.output, tiers, props-as-schema.
11
+ - **Defaults are part of the contract**: every optional boolean / closed-set
12
+ knob carries a `defaultProps` value, every plain string/number a default or
13
+ a placeholder — enforced inside `defineMosaicTemplate` (throws first-party).
14
+ 2. [`construction-strategy.md`](construction-strategy.md) — how to build one:
15
+ the Rect Thesis, styles, smell tests.
16
+ - **Constraints first.** Declare the props contract and the layout
17
+ constraints, THEN build, THEN check in a loop across canvases × prop
18
+ combinations; the build itself is the first gate —
19
+ [`standalone-pack-authoring.md`](standalone-pack-authoring.md) §0.
20
+ 3. [`geometry-recipes.md`](geometry-recipes.md) — the four placement recipes
21
+ (SNAP_PX · placeInsetPieces · placeOptimizedPieces · lattice ratio-grid).
22
+ 4. [`rendering-model-contract.md`](rendering-model-contract.md) — the 12-rule
23
+ output contract the engine holds you to.
24
+ - [`output-resolution-tree.md`](output-resolution-tree.md) — how top-level
25
+ duration / size / fps / format / audio resolve (user > template intent >
26
+ host hints > engine defaults); read before touching any of them.
27
+ 5. [`layout-contract.md`](layout-contract.md) — verify layout intent
28
+ (withLayoutContract / checkLayout / assertLayout). **Before the first
29
+ candidate** for anything that paints text: tag fitted text, declare
30
+ `textFits`, sweep `assertLayout` at the 7-canvas set in the gate test.
31
+
32
+ ## By topic
33
+
34
+ - [`recursion-nested-rendering.md`](recursion-nested-rendering.md) — children,
35
+ bottom-up eval, nested-mosaic-as-clip-region.
36
+ - [`data-pipeline.md`](data-pipeline.md) — fetcher / adapter / handle patterns +
37
+ the operational how-to.
38
+ - [`render-lifecycle.md`](render-lifecycle.md) — the four entry points
39
+ (`render` required; `renderLite` / `renderCover` / `renderTutorial` opt-in),
40
+ the null-on-absent dispatch rule, and the onboarding-surface checklist.
41
+ - [`capability-templates.md`](capability-templates.md) — self-fetch app-runnable
42
+ templates; renderLite rules.
43
+ - [`emission-patterns.md`](emission-patterns.md) — non-pixel outputs (data /
44
+ sidecars / telemetry).
45
+ - [`theming.md`](theming.md) — design tokens over the upstream channel.
46
+ - [`ui-controls.md`](ui-controls.md) — the `picker` taxonomy + prop controls.
47
+ - [`standalone-pack-authoring.md`](standalone-pack-authoring.md) — branded pack
48
+ playbook (alpine is the reference).
49
+
50
+ ## reference/ — lookup specs (types & builders)
51
+
52
+ [`placement-props`](reference/mosaic-placement-props.md) ·
53
+ [`prop type: json`](reference/json-prop-type.md) ·
54
+ [`template flags`](reference/template-flags.md) ·
55
+ [`grid builder`](reference/grid.md) · [`MosaicColor`](reference/mosaic-color.md) ·
56
+ [`prop bindings`](reference/prop-bindings.md)
57
+
58
+ ## patterns/ — distilled experience
59
+
60
+ - [`perf-authoring-rules.md`](patterns/perf-authoring-rules.md) — R1–R11, each
61
+ paid for by a measured incident. Read before shipping any template.
62
+ - [`case-study-lessons.md`](patterns/case-study-lessons.md) — L1–L9 from the
63
+ early template generation.
64
+ - [`primitive-extraction-pattern.md`](patterns/primitive-extraction-pattern.md)
65
+ — when/how to extract primitives (incl. Rule 7: verify NESTED).
@@ -0,0 +1,72 @@
1
+ # Capability-tier templates
2
+
3
+ The tier contract itself (what `{ tier: "capability", caps }` unlocks, default-deny
4
+ caps, when to stay core) lives in [`philosophy-and-contract.md`](philosophy-and-contract.md).
5
+ This doc carries the two shipped patterns that go beyond the contract: app-runnable
6
+ self-fetch templates, and the `renderLite` preview rule.
7
+
8
+ > **Source of truth:** capability declaration shape in
9
+ > https://github.com/m0saic-project/m0saic-packages/blob/main/packages/template-utils/src/template/defineMosaicTemplate.ts; the shipped exemplar
10
+ > `@m0saic/github/weekly-pulse/v1` (https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/src/m0saic/github/weekly-pulse/v1).
11
+
12
+ ---
13
+
14
+ ## App-runnable capability templates (self-fetch)
15
+
16
+ The `.mosaicx`/cron chain (fetcher → adapter → runner) can't run from the Templates
17
+ UI — the core runner is core-tier (mock/upstream only) and the live chain only exists
18
+ as a `.mosaicx`. The shipped pattern: a single **capability-tier** template that IS the
19
+ whole chain in one pickable unit — `@m0saic/github/weekly-pulse/v1`. It
20
+ `fetch → derive → render`s itself when flipped to `source: "live"` (+ repo + token),
21
+ and replays a frozen sample by default (network-free).
22
+
23
+ - It's a **plain-object** `MosaicTemplate` (not `defineMosaicTemplate`) because it
24
+ returns a duration-follows-timings pipeline, so it skips the wrapper's
25
+ `assertTiming` (the CLI `make` path still applies assertTiming — a plain object
26
+ does NOT escape it there).
27
+ - It reuses the runner's extracted `buildPulsePipeline`, so the app-runnable version
28
+ and the cron runner emit the identical beat pipeline.
29
+ - A template can't be "core sometimes, capability sometimes" — the app-runnable live
30
+ version is a SEPARATE capability template, leaving the runner core-tier for the
31
+ cron path.
32
+
33
+ ### Live-mode clock exception
34
+
35
+ An app-runnable capability template MAY read the wall clock for ONE thing: a
36
+ live-mode default window (`props.window ?? lastFullWeek(now)`), documented as a
37
+ deliberate exception to the clock rule (this template is non-deterministic in live
38
+ mode by design). Pinning `window` or using the cron `{{lastFullWeek*}}` tokens keeps
39
+ it deterministic. Replay mode never reads the clock. A `livePreview: true` escape
40
+ hatch lets power users fetch real data into the geometric preview on demand.
41
+
42
+ ---
43
+
44
+ ## renderLite is PREVIEW-ONLY (RenderHero uses the real render geometry)
45
+
46
+ > `renderLite` is one of FOUR render entry points, and it is not a capability-tier
47
+ > feature — a core template may declare it too. The lifecycle contract for all
48
+ > four (including the opt-in `renderCover` / `renderTutorial` onboarding
49
+ > surfaces) is [`render-lifecycle.md`](render-lifecycle.md). This section keeps
50
+ > the part that IS capability-specific: why a self-fetching template's stand-in
51
+ > must never reach RenderHero, and the `renderable_ready` wiring that avoids it.
52
+
53
+ `renderLite()` returns a stand-in (a themed "ready" card) so selecting a
54
+ side-effecting template never runs its real work. It must feed ONLY the idle-state
55
+ GeometricPreview — NEVER the render-progress hero (RenderHero), which would otherwise
56
+ show the card while ffmpeg renders the real beats. Shipped wiring:
57
+
58
+ - `templates:get` returns `hasRenderLite: typeof tmpl.renderLite === "function"` so
59
+ the host/UI knows a preview is a stand-in.
60
+ - `templates:renderToFile`: right after the real `render()` (BEFORE ffmpeg's
61
+ `render_start`), for renderLite templates the host forwards the resolved renderable
62
+ via a `renderable_ready` renderEvent.
63
+ - MakePage captures it as `liveRenderable` and flattens THAT into the hero filmstrip;
64
+ a stand-in preview never drives the hero. Until `renderable_ready` lands (a few ms)
65
+ or if it can't be flattened, the hero shows the neutral loader — never the card.
66
+ Core templates (no renderLite) are unchanged: their preview already IS the render
67
+ geometry.
68
+
69
+ **Rule for authors/hosts:** if `render()` returns geometry structurally different
70
+ from `renderLite()` (as any self-fetching capability template does), the host must
71
+ not reuse the renderLite doc for render-progress; it must hook the real `render()`
72
+ output.