@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,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.
|