machinalayout 0.1.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.
@@ -0,0 +1,60 @@
1
+ # Forbidden Concepts (M0/M1 Guardrails)
2
+
3
+ These concepts are forbidden in core M0/M1 unless a future milestone deliberately revisits them.
4
+
5
+ ## Flexbox negotiation features
6
+
7
+ Forbidden: `flex-basis`, `flex-shrink`, flex grow/shrink/basis triangle, `align-content`, `align-self`, `space-around`, `space-evenly`, stretch defaults, baseline alignment. FillFrame weights are not flexbox.
8
+
9
+ Why: this would reintroduce hidden layout negotiation and makes layout harder to reason about from records.
10
+
11
+ ## Margin-driven placement
12
+
13
+ Forbidden: auto margins, per-child margins, margin collapse. FillFrame does not add per-item margin behavior.
14
+
15
+ Why: margins introduce implicit inter-node coupling and hidden offsets that are not explicit in row geometry.
16
+
17
+ ## Stack wrapping and implicit sizing behavior
18
+
19
+ Forbidden: wrap on `Stack`, percent units, implicit stretch behaviors.
20
+
21
+ Why: this belongs in a future separate primitive, not Stack.
22
+
23
+ ## DOM/CSS-driven geometry authority
24
+
25
+ Forbidden: DOM measurement in core layout, selector-based layout, descendant selector semantics, CSS classes as layout inputs.
26
+
27
+ Why: renderer-dependent inputs break deterministic resolution and move authority away from typed records.
28
+
29
+ ## Authoring model drift
30
+
31
+ Forbidden: nested JSX as canonical authoring, recursive `children` on `LayoutNode`.
32
+
33
+ Why: Machina’s contract is flat record authoring; tree shape is derived output.
34
+
35
+ ## Out-of-scope platform concerns in core
36
+
37
+ Forbidden: routing inside layout core, state management inside layout core.
38
+
39
+ Why: layout core should stay a geometry engine, not an application framework.
40
+
41
+ ## Ordering and layering misuse
42
+
43
+ Forbidden: global z-index solver, z-based layout ordering.
44
+
45
+ Why: z is sibling-local paint metadata; geometry and order resolution must remain independent.
46
+
47
+ ## Silent correctness masking
48
+
49
+ Forbidden: silent clamping.
50
+
51
+ Why: silent repair hides authoring mistakes and weakens determinism guarantees.
52
+
53
+
54
+ ## Unit parsing guardrail
55
+
56
+ Forbidden: string percentages (e.g. `"50%"`), CSS unit parsing, and CSS `calc`.
57
+
58
+ Allowed in M1c: typed `UiLength` objects for `AnchorFrame` only (`px` and normalized `ui`), because conversion is explicit and deterministic.
59
+
60
+ M1d clarification: per-item margins remain forbidden. Use explicit node `offset` for local post-placement nudges when needed.
@@ -0,0 +1,135 @@
1
+ # Frames and Stack
2
+
3
+ ## Rects
4
+
5
+ Resolved rectangles are numeric records:
6
+
7
+ - `x`
8
+ - `y`
9
+ - `width`
10
+ - `height`
11
+
12
+ ## `AbsoluteFrame`
13
+
14
+ Absolute frame gives explicit parent-local position and size.
15
+
16
+ - `x = parent.x + frame.x`
17
+ - `y = parent.y + frame.y`
18
+ - `width = frame.width`
19
+ - `height = frame.height`
20
+
21
+ ## `AnchorFrame`
22
+
23
+ Anchor requires exactly two horizontal constraints from `left`, `right`, `width`, and exactly two vertical constraints from `top`, `bottom`, `height`.
24
+
25
+ Supported horizontal cases:
26
+
27
+ - `left + width`
28
+ - `right + width`
29
+ - `left + right`
30
+
31
+ Supported vertical cases:
32
+
33
+ - `top + height`
34
+ - `bottom + height`
35
+ - `top + bottom`
36
+
37
+ Invalid combinations fail. Negative resolved sizes fail.
38
+
39
+ ## `FixedFrame`
40
+
41
+ Fixed frame is size-only.
42
+
43
+ - It cannot resolve by itself.
44
+ - It is valid as a direct child of `StackArrange`.
45
+ - It is invalid on the root row (same compile-time rejection code as non-arranged fixed usage): `FixedFrameWithoutArranger`.
46
+ - Otherwise resolution fails with `FixedFrameWithoutArranger`.
47
+
48
+ ## `StackArrange`
49
+
50
+ Stack is ordered sequence arrangement over direct children.
51
+
52
+ - Direct children must be `FixedFrame` or `FillFrame`.
53
+ - Axis: `horizontal` or `vertical`.
54
+ - Supports `gap` and `padding`.
55
+ - `justify`: `start` | `center` | `end` | `space-between`
56
+ - `align`: `start` | `center` | `end`
57
+
58
+ Deliberate exclusions:
59
+
60
+ - no shrink
61
+ - no stretch
62
+ - no shrink/basis/min/max negotiation
63
+ - no wrap
64
+ - no margins
65
+
66
+ ### Toolbar example
67
+
68
+ ```ts
69
+ {
70
+ id: "toolbar",
71
+ frame: { kind: "anchor", left: 240, right: 0, top: 64, height: 56 },
72
+ arrange: {
73
+ kind: "stack",
74
+ axis: "horizontal",
75
+ gap: 8,
76
+ padding: { top: 8, right: 8, bottom: 8, left: 8 },
77
+ justify: "start",
78
+ align: "center",
79
+ },
80
+ }
81
+
82
+ { id: "button-a", parent: "toolbar", frame: { kind: "fixed", width: 120, height: 40 } }
83
+ { id: "button-b", parent: "toolbar", frame: { kind: "fixed", width: 96, height: 40 } }
84
+ ```
85
+
86
+ Stack computes positions by arithmetic over order, fixed sizes, gap, and padding.
87
+
88
+ > Stack is ordered arithmetic, not Flexbox.
89
+
90
+
91
+ ## RootFrame
92
+
93
+ `RootFrame` is `{ kind: "root" }` and is valid only on the root row (`parent === undefined`).
94
+
95
+ - Root geometry is still copied from `rootRect` passed by the caller.
96
+ - `resolveFrame(parent, { kind: "root" })` throws `RootFrameWithoutRoot`.
97
+ - Non-root rows with `RootFrame` are rejected during compile with `RootFrameNotRoot`.
98
+ - Root should use `RootFrame`; legacy root `AbsoluteFrame` and `AnchorFrame` remain accepted for compatibility.
99
+ - Root `FixedFrame` and root `FillFrame` are invalid because both require arranger/stack context.
100
+
101
+
102
+ ## `FillFrame`
103
+
104
+ `FillFrame` is valid only as a direct child of `StackArrange`.
105
+
106
+ - `weight` defaults to `1` and must be finite and `> 0`.
107
+ - `cross` defaults to `"fill"`.
108
+ - `cross: "fill"` uses stack content cross size.
109
+ - numeric `cross` is explicit cross size and must be finite/non-negative.
110
+
111
+ Fill children consume all remaining main-axis space after fixed sizes and gaps. When one or more fill children exist, `justify` has no additional free space to distribute.
112
+
113
+
114
+ ## Anchor UiLength (M1c)
115
+
116
+ Anchor fields `left/right/top/bottom/width/height` accept `UiLength`:
117
+ - plain number means px
118
+ - `{ unit: "px", value }` explicit px
119
+ - `{ unit: "ui", value }` normalized unit resolved deterministically during frame resolution
120
+
121
+ Axis mapping:
122
+ - horizontal (`left/right/width`) uses `parent.width`
123
+ - vertical (`top/bottom/height`) uses `parent.height`
124
+
125
+ Notes:
126
+ - no string percentages
127
+ - no CSS `calc`
128
+ - fractional pixels are preserved
129
+ - negative `left/right/top/bottom` positional constraints are deliberately allowed for intentional overflow/bleed
130
+ - explicit negative width/height are rejected
131
+ - derived negative size from `left+right` or `top+bottom` is rejected
132
+
133
+ ## Node offsets (M1d)
134
+
135
+ `offset` is applied after normal frame/stack placement and does not change stack distribution, sibling positions, or width/height. `offset.x` resolves against parent rect width; `offset.y` resolves against parent rect height.
@@ -0,0 +1,70 @@
1
+ # M0 Contract
2
+
3
+ ## Purpose
4
+
5
+ M0 defines a deterministic, framework-independent rectangle resolution pipeline with a narrow, explicit scope.
6
+
7
+ ## Architecture pipeline
8
+
9
+ `LayoutRow[]`
10
+ → `compileLayoutRows`
11
+ → `LayoutDocument`
12
+ → `resolveLayoutDocument`
13
+ → `ResolvedLayoutDocument`
14
+ → optional `toResolvedTree`
15
+ → renderer adapter
16
+
17
+ ## Core model
18
+
19
+ - **Flat rows** are the canonical input model.
20
+ - **Indexed documents** (`nodes`, `children`, `rootId`) are the compiled representation.
21
+ - **Flat resolved output** is the canonical resolved geometry model.
22
+ - **Derived tree** is for rendering/debugging convenience, not authoring.
23
+
24
+ ## Supported primitives
25
+
26
+ - `AbsoluteFrame`
27
+ - `AnchorFrame`
28
+ - `FixedFrame`
29
+ - `StackArrange`
30
+
31
+ ## Supported metadata
32
+
33
+ - `slot`
34
+ - `debugLabel`
35
+ - `z`
36
+
37
+ ## Adapter boundary
38
+
39
+ - The React adapter renders resolved rectangles as absolutely positioned wrappers.
40
+ - Slots are rendered via a slot registry (`views`).
41
+ - Coordinates are normalized to root-local space for embedding.
42
+ - Containment/content-visibility are adapter-level policy knobs.
43
+
44
+ ## Sample
45
+
46
+ The Control Room sample (`samples/control-room`) demonstrates the M0 contract in a runnable app.
47
+
48
+ ## Non-goals
49
+
50
+ - routing
51
+ - state framework
52
+ - full design system
53
+ - CSS replacement
54
+ - flexbox clone
55
+ - grid clone
56
+
57
+
58
+ > M1a note: Root rows may now declare `frame: { kind: "root" }`; root geometry still comes from caller-provided `rootRect`.
59
+
60
+
61
+ > M1b note: `FillFrame` is now supported as a direct child of `StackArrange` for weighted remaining-space distribution.
62
+
63
+
64
+ ## M1c implemented scope note
65
+
66
+ M1c adds typed `UiLength` support for `AnchorFrame` only; resolved rect outputs remain numeric pixels.
67
+
68
+ ## M1d implemented scope note
69
+
70
+ M1d adds optional node-level `OffsetSpec` for deterministic post-placement translation. Offsets are not margins and do not participate in stack distribution.
@@ -0,0 +1,319 @@
1
+ # MachinaText M2a Planning & Audit
2
+
3
+ ## Status note (M2c.1)
4
+
5
+ M2c.1 is implemented in React renderer scope: explicit `leading`, `blockGap`, `listGap`, and `valign` policy fields now control vertical rhythm inside owned rectangles without changing layout resolver APIs or parser syntax.
6
+
7
+ ## 1) Executive summary
8
+
9
+ MachinaText should be introduced as a **small, explicit text-content model** for authoring predictable text structures inside already-resolved Machina rectangles, without turning Machina into a document engine, Markdown engine, or font/layout measurement system.
10
+
11
+ For M2a, the least disruptive path is:
12
+
13
+ - add a **docs-first plan** (this file),
14
+ - keep MachinaText **independent from layout core resolution**,
15
+ - keep `LayoutRow` and resolver APIs unchanged,
16
+ - attach text policy at the **view payload layer** (application/renderer side) first,
17
+ - defer parser/runtime code to later milestones.
18
+
19
+ ## 2) Goals
20
+
21
+ - Provide a brutally limited, machine-authorable text format for LLM-friendly edits.
22
+ - Keep text content model renderer-agnostic.
23
+ - Preserve Machina contract: layout owns rectangles, text stays inside resolved rectangles.
24
+ - Support a narrow set of content primitives:
25
+ - plain text
26
+ - paragraph breaks
27
+ - strong
28
+ - emphasis
29
+ - inline code
30
+ - links
31
+ - unordered bullets with max depth 2
32
+ - Make policy explicit (variant/wrap/overflow/align) rather than hidden in syntax tricks.
33
+
34
+ ## 3) Non-goals
35
+
36
+ - No full Markdown compatibility.
37
+ - No headings in M2.
38
+ - No images/tables/raw HTML/blockquotes/ordered lists/task lists/footnotes.
39
+ - No routing/dispatch/state behavior.
40
+ - No rich text editor concerns (selection/caret/editing model).
41
+ - No glyph shaping/font fallback/bidi engine in Machina core.
42
+ - No intrinsic sizing or text-driven outer layout changes.
43
+
44
+ ## 4) Proposed package/module placement
45
+
46
+ ### Audit observations
47
+
48
+ - Core public API is exported from `src/index.ts`, and currently exports geometry + react adapter modules only. `src/index.ts`.
49
+ - Core types (`LayoutRow`, `FrameSpec`, resolved docs) are geometry-oriented and intentionally small. `src/types.ts`.
50
+ - React integration currently maps `view ?? slot` keys to app-provided components via `views` registry; this is the existing integration seam for payload rendering. `src/react/MachinaReactView.tsx`.
51
+
52
+ ### Recommendation (least disruptive M2 path)
53
+
54
+ 1. **Add `src/text/` in same package** for M2-series incubation (types/contracts/docs-aligned naming).
55
+ 2. Keep React-specific text helpers under **`src/text/react/`** (not mixed into current generic `src/react` root) to keep text concerns modular.
56
+ 3. Re-export from root only when implementation starts (M2b+), not required for M2a docs-only.
57
+ 4. Consider separate package later (`@machina/text`) only after API stabilizes and at least one non-React renderer exists.
58
+
59
+ Rationale: this keeps churn low, avoids premature package splitting, and preserves current adapter surface.
60
+
61
+ ## 5) Proposed public type surface
62
+
63
+ > M2a recommendation: define these in plan only; do not implement yet.
64
+
65
+ ```ts
66
+ export type MachinaTextSource =
67
+ | { kind: "plain"; text: string }
68
+ | { kind: "machina-text"; text: string };
69
+
70
+ export type MachinaTextVariant = "body" | "label" | "caption" | "title" | "mono";
71
+ export type MachinaTextWrap = "word" | "none";
72
+ export type MachinaTextOverflow = "clip" | "ellipsis" | "scroll";
73
+ export type MachinaTextAlign = "start" | "center" | "end";
74
+
75
+ export type MachinaTextSpec = {
76
+ kind: "text";
77
+ source: MachinaTextSource;
78
+ variant?: MachinaTextVariant;
79
+ wrap?: MachinaTextWrap;
80
+ overflow?: MachinaTextOverflow;
81
+ align?: MachinaTextAlign;
82
+ };
83
+ ```
84
+
85
+ AST recommendation:
86
+
87
+ ```ts
88
+ export type MachinaTextDocument = {
89
+ blocks: MachinaTextBlock[];
90
+ };
91
+
92
+ export type MachinaTextBlock =
93
+ | { kind: "paragraph"; inline: MachinaInline[] }
94
+ | { kind: "bulletList"; items: MachinaBulletItem[] };
95
+
96
+ export type MachinaBulletItem = {
97
+ inline: MachinaInline[];
98
+ children?: MachinaBulletItem[];
99
+ };
100
+
101
+ export type MachinaInline =
102
+ | { kind: "text"; text: string }
103
+ | { kind: "strong"; children: MachinaInline[] }
104
+ | { kind: "emphasis"; children: MachinaInline[] }
105
+ | { kind: "code"; text: string }
106
+ | { kind: "link"; href: string; children: MachinaInline[] };
107
+ ```
108
+
109
+ Type-shape decisions:
110
+
111
+ - `strong` / `emphasis` should contain **children**, not raw text, to allow nested emphasis/code/link segments while keeping one consistent inline model.
112
+ - `link` should contain **inline children**, not label-only text, for parity and explicit structure.
113
+ - Bullets should allow nested inline formatting (same `MachinaInline[]` as paragraphs).
114
+ - Bullet max depth should be enforced by parser validation/diagnostics, not encoded in types.
115
+ - `variant/wrap/overflow/align` belong in `MachinaTextSpec` (content policy contract), while renderers decide exact visual implementation details.
116
+ - `plain` source should normalize into one-paragraph AST during parse.
117
+
118
+ ## 6) Allowed syntax
119
+
120
+ MachinaText syntax (M2 target):
121
+
122
+ - plain text
123
+ - paragraph breaks (blank line separation)
124
+ - `**strong**`
125
+ - `*emphasis*`
126
+ - `` `code` ``
127
+ - `[label](href)` links
128
+ - unordered bullets (`-` prefix)
129
+ - nested bullets max depth 2
130
+
131
+ ## 7) Forbidden syntax
132
+
133
+ - headings (`#`, `##`, etc.)
134
+ - ordered lists (`1.`)
135
+ - task list checkboxes
136
+ - blockquotes (`>`)
137
+ - fenced code blocks
138
+ - images (`![...]`)
139
+ - tables
140
+ - raw HTML
141
+ - extension syntax / custom directives
142
+ - CSS classes/styles inside text syntax
143
+ - any layout declarations
144
+ - route/action/dispatch syntax
145
+
146
+ ## 8) Heading decision and rationale
147
+
148
+ **Decision: headings are explicitly out of scope in M2.**
149
+
150
+ Rationale:
151
+
152
+ - Heading markers smuggle hidden policy (typography scale, spacing, semantic levels).
153
+ - MachinaText should keep policy explicit and inspectable.
154
+ - “Title-like” rendering should come from explicit fields (`variant: "title"`) or explicit views, not punctuation shortcuts.
155
+
156
+ ## 9) Bullet list decision and max-depth rationale
157
+
158
+ Decision:
159
+
160
+ - support unordered bullets only,
161
+ - allow nesting up to depth 2,
162
+ - reject/diagnose deeper nesting.
163
+
164
+ Rationale:
165
+
166
+ - Depth cap preserves predictable rendering across hosts.
167
+ - Prevents gradual drift toward document-authoring complexity.
168
+ - Meets common UI copy needs (short status/checklist-like structure) without introducing full markdown list semantics.
169
+
170
+ ## 10) Parser behavior recommendation
171
+
172
+ Recommendation:
173
+
174
+ - Parser should return a **result object with diagnostics**, not throw for normal authoring errors.
175
+ - Hard throw only for programmer misuse (invalid API call types), not syntax mistakes in content strings.
176
+
177
+ Suggested shape:
178
+
179
+ ```ts
180
+ export type MachinaTextDiagnosticLevel = "error" | "warning";
181
+
182
+ export type MachinaTextDiagnostic = {
183
+ code:
184
+ | "unsupported_syntax"
185
+ | "heading_forbidden"
186
+ | "max_list_depth_exceeded"
187
+ | "malformed_link"
188
+ | "unclosed_inline"
189
+ | "invalid_escape";
190
+ message: string;
191
+ index: number;
192
+ length: number;
193
+ line: number;
194
+ column: number;
195
+ level: MachinaTextDiagnosticLevel;
196
+ };
197
+
198
+ export type ParseMachinaTextResult = {
199
+ ok: boolean;
200
+ document: MachinaTextDocument;
201
+ diagnostics: MachinaTextDiagnostic[];
202
+ };
203
+ ```
204
+
205
+ Fallback policy:
206
+
207
+ - Unsupported constructs should be preserved as literal text where possible, with diagnostics.
208
+ - Optional strict mode can fail `ok=false` on first unsupported construct.
209
+
210
+ ## 11) Renderer boundary
211
+
212
+ Core MachinaText model owns:
213
+
214
+ - content AST semantics,
215
+ - small text policy flags (`variant`, `wrap`, `overflow`, `align`),
216
+ - parser diagnostics contract.
217
+
218
+ React/DOM renderer owns:
219
+
220
+ - actual text rendering via browser engine,
221
+ - mapping policy flags to CSS/DOM behavior,
222
+ - link interaction callbacks passed from app,
223
+ - accessibility annotations specific to host components.
224
+
225
+ Future non-DOM renderer owns:
226
+
227
+ - platform-specific shaping/layout internals,
228
+ - equivalent wrap/clip/ellipsis/scroll behavior inside rectangle,
229
+ - platform accessibility mapping.
230
+
231
+ ## 12) Layout boundary
232
+
233
+ Hard boundary:
234
+
235
+ - MachinaLayout resolves outer rectangles.
236
+ - MachinaText operates inside resolved rectangle only.
237
+
238
+ Not allowed:
239
+
240
+ - requesting larger parent/sibling/root sizes,
241
+ - intrinsic measurement feedback into resolver,
242
+ - creating new layout nodes from text blocks.
243
+
244
+ Allowed:
245
+
246
+ - in-rectangle wrap/clip/ellipsis/scroll policy.
247
+
248
+ ## 13) Link behavior boundary
249
+
250
+ Links are text semantics only:
251
+
252
+ - link node stores `href` + label children,
253
+ - no route syntax,
254
+ - no command dispatch tables,
255
+ - no state transition contract in MachinaText.
256
+
257
+ Application/renderers decide click behavior.
258
+
259
+ ## 14) Suggested M2 milestone decomposition
260
+
261
+ - **M2a (this pass):** audit + plan doc + agreed type contracts (docs only).
262
+ - **M2b:** implement parser + diagnostics + unit tests (no renderer).
263
+ - **M2c:** React/DOM rendering adapter for MachinaText AST inside node views.
264
+ - **M2d:** sample integration in control-room-style demo with explicit text specs.
265
+ - **M2e:** docs polish, migration notes, edge-case hardening, non-DOM renderer contract notes.
266
+
267
+ ## 15) Risks and mitigations
268
+
269
+ 1. **Heading semantics creep**
270
+ Mitigation: explicit prohibition + diagnostic code `heading_forbidden`.
271
+
272
+ 2. **Markdown dialect creep**
273
+ Mitigation: closed grammar and “unsupported as literal+diagnostic” policy.
274
+
275
+ 3. **Text measurement creep into core**
276
+ Mitigation: hard boundary that no parser/model API takes font metrics or container reflow callbacks.
277
+
278
+ 4. **Routing/event creep via links**
279
+ Mitigation: keep links as `href` semantics only, no action ids.
280
+
281
+ 5. **HTML passthrough pressure**
282
+ Mitigation: explicit ban with diagnostics; no raw HTML node type.
283
+
284
+ 6. **Intrinsic sizing pressure**
285
+ Mitigation: tests/docs asserting no resolver API change and no text->layout feedback path.
286
+
287
+ 7. **Accessibility overpromises in core**
288
+ Mitigation: document that host renderers/apps own semantics and interaction behavior.
289
+
290
+ 8. **Renderer leakage into core types**
291
+ Mitigation: keep core text types free of CSS/DOM-specific properties.
292
+
293
+ ## 16) Recommended next implementation prompt
294
+
295
+ “Implement M2b parser-only MachinaText foundation (no renderer):
296
+
297
+ - Add `src/text/types.ts` and `src/text/parseMachinaText.ts`.
298
+ - Implement only allowed syntax: paragraphs, strong/emphasis/code/link, unordered bullets depth<=2.
299
+ - Return `ParseMachinaTextResult` with diagnostics; no throws for authoring errors.
300
+ - Forbid headings/raw HTML/ordered lists/etc. with explicit diagnostic codes.
301
+ - Add focused Vitest coverage for success and failure cases.
302
+ - Do not modify layout resolver APIs, `LayoutRow`, or React adapter behavior yet.”
303
+
304
+ ---
305
+
306
+ ## Repo-specific audit notes
307
+
308
+ - Current core shape is layout-only and should remain unchanged for M2a. `src/types.ts`.
309
+ - Current React integration seam (`view ?? slot` mapping) is a suitable place for app-level text view components in early milestones, avoiding immediate core API expansion. `src/react/MachinaReactView.tsx`.
310
+ - Current docs already enforce similar anti-creep guardrails (no intrinsic sizing/DOM authority), and MachinaText should align with that philosophy. `docs/forbidden-concepts.md`.
311
+
312
+ ## M2b status note (implemented)
313
+
314
+ M2b parser-only foundation is now implemented in `src/text`:
315
+
316
+ - public MachinaText types are exported,
317
+ - `parseMachinaText` returns AST + diagnostics,
318
+ - no renderer integration added,
319
+ - no layout resolver API changes made.
@@ -0,0 +1,24 @@
1
+ # MachinaText Parser
2
+
3
+ MachinaText supports a narrow inline syntax: emphasis, strong, code spans, links, and bullets.
4
+
5
+ ## Escapes (M3a)
6
+
7
+ Supported escapes in normal inline text:
8
+
9
+ - `\\` => `\`
10
+ - `\*` => `*`
11
+ - ``\` `` => `` ` ``
12
+ - `\[` => `[`
13
+ - `\]` => `]`
14
+ - `\(` => `(`
15
+ - `\)` => `)`
16
+ - `\-` => `-`
17
+
18
+ Escapes are processed before inline marker detection, so escaped markers stay literal (example: `\*not emphasis\*`).
19
+
20
+ Unsupported escapes (example: `\q`) emit diagnostic code `invalid_escape` and preserve visible content as literal text.
21
+
22
+ A trailing dangling backslash also emits `invalid_escape` and preserves a literal backslash.
23
+
24
+ Code spans do not process escapes. Content inside backticks is taken literally.
@@ -0,0 +1,68 @@
1
+ # MachinaText React Renderer (M2c)
2
+
3
+ `MachinaTextView` renders MachinaText content inside a rectangle already owned by a parent view. It does not participate in Machina layout resolution, sizing, or measurement.
4
+
5
+ ## Component
6
+
7
+ - `MachinaTextView`
8
+ - `MachinaTextViewProps`
9
+
10
+ ### `text` input forms
11
+
12
+ - `string`: parsed as `{ kind: "machina-text", text }`
13
+ - `MachinaTextSource`: parsed directly (`plain` stays literal)
14
+ - `MachinaTextSpec`: parses `spec.source` and applies text policy
15
+ - `MachinaTextDocument`: rendered directly with default policy
16
+
17
+ ## Policy fields
18
+
19
+ Supported policy fields:
20
+
21
+ - `variant`: `body | label | caption | title | mono`
22
+ - `wrap`: `word | none`
23
+ - `overflow`: `clip | ellipsis | scroll`
24
+ - `align`: `start | center | end`
25
+ - `leading`: `tight | normal | loose | number`
26
+ - `blockGap`: non-negative number (pixels)
27
+ - `listGap`: non-negative number (pixels)
28
+ - `valign`: `top | center | bottom`
29
+
30
+ Defaults are `body`, `word`, `clip`, `start`, `normal`, `8`, `2`, `top`.
31
+
32
+ Vertical rhythm policy is internal to the text renderer inside an already-owned rectangle. It does not change outer Machina layout, resolver behavior, or sizing.
33
+
34
+ - `leading` maps to `line-height` (`tight=1.15`, `loose=1.6`, `normal=variant default`, numeric uses provided value when valid).
35
+ - `blockGap` controls spacing between top-level text blocks (paragraph/list blocks).
36
+ - `listGap` controls spacing between list items (including nested list items).
37
+ - `valign` controls vertical placement of content in the wrapper rectangle.
38
+
39
+ `MachinaTextView` may use internal flex layout for this vertical positioning only. This does not imply flex/grid-based Machina node placement.
40
+
41
+ ## Link behavior boundary
42
+
43
+ Links render as standard `<a>` tags.
44
+
45
+ - optional `linkTarget`
46
+ - if `_blank`, renderer sets `rel="noreferrer noopener"`
47
+ - optional `onLinkClick(href, event)` callback
48
+
49
+ No routing, dispatch, or action semantics are implemented.
50
+
51
+ ## Diagnostics
52
+
53
+ Parsing diagnostics never throw. Best-effort content still renders.
54
+
55
+ Set `showDiagnostics` to render parser diagnostics for development.
56
+
57
+
58
+ ## Overflow ellipsis policy (M3a)
59
+
60
+ `overflow: "ellipsis"` is defined as **single-line ellipsis** in M3.
61
+
62
+ When `overflow` is `ellipsis`, renderer normalization forces:
63
+
64
+ - `white-space: nowrap`
65
+ - `overflow: hidden`
66
+ - `text-overflow: ellipsis`
67
+
68
+ This overrides `wrap` for deterministic behavior. Multi-line ellipsis / line clamp is not supported.