machinalayout 0.1.0 → 0.3.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 (61) hide show
  1. package/README.md +295 -49
  2. package/dist/chunk-2ZQ2RFFI.js +400 -0
  3. package/dist/chunk-33CKBEJH.js +186 -0
  4. package/dist/chunk-BJOQRPPX.js +382 -0
  5. package/dist/chunk-KYWOCAHK.js +205 -0
  6. package/dist/chunk-RJYRJ3LD.js +0 -0
  7. package/dist/chunk-SVWYWI7I.js +59 -0
  8. package/dist/chunk-VREK57S3.js +13 -0
  9. package/dist/chunk-ZVDE7PX4.js +222 -0
  10. package/dist/debugOverlay-pJpj0n5H.d.ts +125 -0
  11. package/dist/deus/index.d.ts +14 -0
  12. package/dist/deus/index.js +26 -0
  13. package/dist/dispatch/index.d.ts +49 -0
  14. package/dist/dispatch/index.js +217 -0
  15. package/dist/handoff/index.d.ts +44 -0
  16. package/dist/handoff/index.js +83 -0
  17. package/dist/index.d.ts +54 -236
  18. package/dist/index.js +753 -583
  19. package/dist/inspect/index.d.ts +8 -0
  20. package/dist/inspect/index.js +97 -0
  21. package/dist/react/index.d.ts +41 -0
  22. package/dist/react/index.js +9 -0
  23. package/dist/react-native/index.d.ts +30 -0
  24. package/dist/react-native/index.js +84 -0
  25. package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
  26. package/dist/text/index.d.ts +10 -0
  27. package/dist/text/index.js +9 -0
  28. package/dist/text/react/index.d.ts +14 -0
  29. package/dist/text/react/index.js +7 -0
  30. package/dist/text/react-native/index.d.ts +16 -0
  31. package/dist/text/react-native/index.js +155 -0
  32. package/dist/text/vue/index.d.ts +113 -0
  33. package/dist/text/vue/index.js +202 -0
  34. package/dist/types-B90jb3RW.d.ts +184 -0
  35. package/dist/types-C4poVJpR.d.ts +74 -0
  36. package/dist/types-DLYAhNXw.d.ts +32 -0
  37. package/dist/vue/index.d.ts +173 -0
  38. package/dist/vue/index.js +112 -0
  39. package/docs/adapter-packaging-a0-plan.md +352 -0
  40. package/docs/adapters.md +19 -0
  41. package/docs/api-coherence-m8-audit.md +397 -0
  42. package/docs/deusmachina.md +108 -0
  43. package/docs/error-codes.md +95 -0
  44. package/docs/grid-arrange-m5a-contract.md +480 -0
  45. package/docs/grid-arrange.md +51 -0
  46. package/docs/inspection-and-handoff.md +126 -0
  47. package/docs/layout-interpolation.md +52 -0
  48. package/docs/machina-dispatch-d0-contract.md +496 -0
  49. package/docs/machina-dispatch.md +143 -0
  50. package/docs/named-layers.md +40 -0
  51. package/docs/react-adapter.md +63 -58
  52. package/docs/react-native-adapter.md +56 -0
  53. package/docs/react-native-text-renderer.md +50 -0
  54. package/docs/reference-alignment-m7a-contract.md +384 -0
  55. package/docs/reference-alignment.md +44 -0
  56. package/docs/responsive-variants.md +54 -0
  57. package/docs/screen-catalog-and-viewports.md +124 -0
  58. package/docs/stack-geometry-helpers.md +115 -0
  59. package/docs/vue-adapter.md +55 -0
  60. package/docs/vue-text-renderer.md +55 -0
  61. package/package.json +127 -60
@@ -0,0 +1,480 @@
1
+ # GridArrange design contract (M5a)
2
+
3
+ ## 1. Executive summary
4
+
5
+ This document defines the **design contract** for a future `GridArrange` runtime implementation in M5b.
6
+
7
+ > Status (May 11, 2026): M5b runtime implementation is now available in core.
8
+
9
+ `GridArrange` is introduced as an explicit, deterministic 2D arranger that fits the existing Machina model:
10
+
11
+ - rows are still authored as flat `LayoutRow[]`,
12
+ - parents own coordinate spaces,
13
+ - arrangers place direct children,
14
+ - resolved output remains rectangles,
15
+ - adapters (including React) continue to render resolved rectangles.
16
+
17
+ `GridArrange` is a narrow, explicit model for row/column placement and **is not** a CSS Grid clone.
18
+
19
+ ## 2. Goals
20
+
21
+ - Add a first-class 2D arranger as `ArrangeSpec`.
22
+ - Keep frame vs arrange responsibility intact:
23
+ - `frame` defines node rectangle,
24
+ - `arrange` defines how node places direct children.
25
+ - Require explicit child placement using a new `CellFrame` for direct children of grid parents.
26
+ - Keep resolution parent-rect driven and deterministic.
27
+ - Integrate with existing features: responsive variants, offset, z metadata, resolved tree, interpolation.
28
+ - Keep v1 scope narrow and implementation-ready for M5b.
29
+
30
+ ## 3. Non-goals
31
+
32
+ M5a and M5b v1 explicitly do **not** include:
33
+
34
+ - CSS Grid syntax or compatibility,
35
+ - auto-placement,
36
+ - implicit row/column creation,
37
+ - template areas,
38
+ - named lines,
39
+ - `minmax` or intrinsic track sizing,
40
+ - content measurement / DOM measurement,
41
+ - subgrid,
42
+ - masonry,
43
+ - per-cell margins,
44
+ - percentage string units,
45
+ - `UiLength` tracks,
46
+ - grid-level align/justify negotiation,
47
+ - graph mutation (add/remove nodes via variants).
48
+
49
+ ## 4. Type proposal
50
+
51
+ ### 4.1 `ArrangeSpec`
52
+
53
+ `GridArrange` is added as an arrange type.
54
+
55
+ ```ts
56
+ export type ArrangeSpec =
57
+ | StackArrange
58
+ | GridArrange;
59
+ ```
60
+
61
+ ### 4.2 `CellFrame`
62
+
63
+ Direct children of grid parents use a new `CellFrame`.
64
+
65
+ ```ts
66
+ export type CellFrame = {
67
+ kind: "cell";
68
+ col: number;
69
+ row: number;
70
+ colSpan?: number;
71
+ rowSpan?: number;
72
+ };
73
+ ```
74
+
75
+ `FrameSpec` proposal:
76
+
77
+ ```ts
78
+ export type FrameSpec =
79
+ | RootFrame
80
+ | AbsoluteFrame
81
+ | AnchorFrame
82
+ | FixedFrame
83
+ | FillFrame
84
+ | CellFrame;
85
+ ```
86
+
87
+ ### 4.3 `GridTrack`
88
+
89
+ Grid tracks are explicit records (not `number | "fill"`).
90
+
91
+ ```ts
92
+ export type GridTrack =
93
+ | { kind: "fixed"; size: number }
94
+ | { kind: "fill"; weight?: number };
95
+ ```
96
+
97
+ ### 4.4 `GridArrange`
98
+
99
+ ```ts
100
+ export type GridArrange = {
101
+ kind: "grid";
102
+ columns: GridTrack[];
103
+ rows: GridTrack[];
104
+ columnGap?: number;
105
+ rowGap?: number;
106
+ padding?: number | EdgeInsets;
107
+ };
108
+ ```
109
+
110
+ Defaults:
111
+
112
+ - `columnGap = 0`
113
+ - `rowGap = 0`
114
+ - `padding = 0`
115
+
116
+ ## 5. Semantics
117
+
118
+ ### 5.1 Placement model
119
+
120
+ - `GridArrange` only affects placement of **direct children**.
121
+ - If a parent has `arrange.kind === "grid"`, every direct child frame must be `CellFrame`.
122
+ - Children of those child nodes resolve using normal existing rules (their own frame + optional arrange).
123
+
124
+ ### 5.2 Indexing and spans
125
+
126
+ - `row` and `col` are **zero-based** indexes.
127
+ - `colSpan` defaults to `1`.
128
+ - `rowSpan` defaults to `1`.
129
+ - Spans must be positive integers.
130
+ - No implicit placement, no next-cell flow.
131
+
132
+ ### 5.3 Track semantics
133
+
134
+ - `fixed`: explicit px size, finite and `>= 0`.
135
+ - `fill`: proportional share of remaining space by weight.
136
+ - `fill.weight` default is `1`, must be finite and `> 0`.
137
+ - Fractional resolved sizes are allowed.
138
+ - No rounding step.
139
+ - No `UiLength` tracks in v1.
140
+
141
+ ### 5.4 Content rect semantics
142
+
143
+ Given parent rect `R` and normalized padding `P`:
144
+
145
+ - `content.x = R.x + P.left`
146
+ - `content.y = R.y + P.top`
147
+ - `content.width = R.width - P.left - P.right`
148
+ - `content.height = R.height - P.top - P.bottom`
149
+
150
+ If `content.width < 0` or `content.height < 0`, resolution fails with `GridContentNegative`.
151
+
152
+ ## 6. Validation and error codes
153
+
154
+ New grid-specific error codes:
155
+
156
+ ```ts
157
+ | "CellFrameWithoutGrid"
158
+ | "GridChildMustBeCell"
159
+ | "InvalidGridTrack"
160
+ | "InvalidGridCell"
161
+ | "GridContentNegative"
162
+ | "GridOverflow"
163
+ ```
164
+
165
+ ### 6.1 `InvalidGridTrack`
166
+
167
+ Used for invalid grid arrange/track config, including:
168
+
169
+ - `columns.length === 0`,
170
+ - `rows.length === 0`,
171
+ - fixed size non-finite or `< 0`,
172
+ - fill weight non-finite or `<= 0`,
173
+ - `columnGap`/`rowGap` non-finite or `< 0`.
174
+
175
+ ### 6.2 `InvalidGridCell`
176
+
177
+ Used for invalid cell coordinate/span config, including:
178
+
179
+ - `row`/`col` non-integer,
180
+ - `row`/`col < 0`,
181
+ - span non-integer,
182
+ - span `<= 0`,
183
+ - out-of-range span (`row + rowSpan > rows.length`, `col + colSpan > columns.length`).
184
+
185
+ ### 6.3 `GridChildMustBeCell`
186
+
187
+ Used when a node under a `grid` parent has any non-`cell` frame kind.
188
+
189
+ ### 6.4 `CellFrameWithoutGrid`
190
+
191
+ Used when:
192
+
193
+ - `resolveFrame` is called directly with `CellFrame`,
194
+ - or a child uses `CellFrame` under a non-grid parent.
195
+
196
+ ### 6.5 `GridContentNegative`
197
+
198
+ Used when normalized padding yields negative content width or height.
199
+
200
+ ### 6.6 `GridOverflow`
201
+
202
+ Used when, for an axis:
203
+
204
+ `contentAxisSize - fixedTotal - gapTotal < 0`.
205
+
206
+ ## 7. Resolution algorithm
207
+
208
+ ### 7.1 Axis track resolution (columns or rows)
209
+
210
+ Inputs:
211
+
212
+ - `contentAxisSize`,
213
+ - `tracks`,
214
+ - `gap`,
215
+ - axis label for diagnostics.
216
+
217
+ Compute:
218
+
219
+ 1. `fixedTotal = sum(fixed.size)`
220
+ 2. `gapTotal = gap * max(0, tracks.length - 1)`
221
+ 3. `remaining = contentAxisSize - fixedTotal - gapTotal`
222
+
223
+ Rules:
224
+
225
+ - If `remaining < 0` -> `GridOverflow`.
226
+ - If fill tracks exist:
227
+ - `totalWeight = sum(fill.weight ?? 1)`
228
+ - each fill size = `remaining * weight / totalWeight`
229
+ - If no fill tracks exist:
230
+ - leave trailing `remaining` unused at axis end.
231
+ - No track justification in v1.
232
+ - No auto gap expansion.
233
+
234
+ Output per track:
235
+
236
+ - resolved `size`,
237
+ - `start` offset from content-axis origin.
238
+
239
+ ### 7.2 Child cell rectangle resolution
240
+
241
+ For child `CellFrame` with defaults applied (`rowSpan = 1`, `colSpan = 1`):
242
+
243
+ 1. Validate cell coordinates/spans.
244
+ 2. Resolve position:
245
+ - `x = content.x + columnStarts[col]`
246
+ - `y = content.y + rowStarts[row]`
247
+ 3. Resolve span size:
248
+ - `width = sum(columnSizes[col..col+colSpan-1]) + columnGap * (colSpan - 1)`
249
+ - `height = sum(rowSizes[row..row+rowSpan-1]) + rowGap * (rowSpan - 1)`
250
+ 4. Apply node `offset` post-placement using existing semantics:
251
+ - x offset resolves against **parent rect width**,
252
+ - y offset resolves against **parent rect height**,
253
+ - offset does not affect sibling placement or track resolution.
254
+
255
+ ## 8. Interaction with existing features
256
+
257
+ ### 8.1 StackArrange
258
+
259
+ - Stack remains the ordered 1D arranger.
260
+ - Grid is an additional arranger type, not a stack replacement.
261
+ - No behavior change to existing stack resolution.
262
+
263
+ ### 8.2 Responsive variants
264
+
265
+ - `variants` may override `arrange` to `GridArrange`.
266
+ - `variants` may override child `frame` to `CellFrame`.
267
+ - No special-case variant resolver logic is required.
268
+ - Existing constraint remains: variants cannot add/remove rows.
269
+
270
+ ### 8.3 Offset
271
+
272
+ - Offset stays node-local post-placement.
273
+ - Grid placement determines base rect, then existing offset machinery is applied.
274
+
275
+ ### 8.4 z
276
+
277
+ - Existing sibling-local bounded `z` behavior is unchanged.
278
+ - Grid impacts geometry only.
279
+
280
+ ### 8.5 Resolved tree
281
+
282
+ - `ResolvedLayoutDocument` preserves selected `arrange`/`frame` metadata, including `grid` and `cell`.
283
+ - `toResolvedTree` and flattening require no grid-specific branching beyond type acceptance.
284
+
285
+ ### 8.6 lerp
286
+
287
+ - `lerpResolvedLayouts` already interpolates resolved rectangles.
288
+ - No special interpolation path required for grid metadata.
289
+
290
+ ### 8.7 React adapter
291
+
292
+ - No adapter changes are required.
293
+ - Adapter consumes resolved rectangles; grid is core resolver-only behavior.
294
+
295
+ ## 9. Examples
296
+
297
+ ### 9.1 Simple 2x2 grid
298
+
299
+ ```ts
300
+ {
301
+ id: "grid",
302
+ parent: "root",
303
+ frame: { kind: "anchor", left: 0, right: 0, top: 0, bottom: 0 },
304
+ arrange: {
305
+ kind: "grid",
306
+ columns: [{ kind: "fill" }, { kind: "fill" }],
307
+ rows: [{ kind: "fixed", size: 100 }, { kind: "fill" }],
308
+ columnGap: 8,
309
+ rowGap: 8,
310
+ padding: 16,
311
+ },
312
+ }
313
+
314
+ {
315
+ id: "a",
316
+ parent: "grid",
317
+ frame: { kind: "cell", row: 0, col: 0 },
318
+ view: "Card",
319
+ }
320
+ ```
321
+
322
+ ### 9.2 Dashboard cards with fill columns
323
+
324
+ ```ts
325
+ {
326
+ id: "dashboard",
327
+ parent: "root",
328
+ frame: { kind: "anchor", left: 0, right: 0, top: 0, bottom: 0 },
329
+ arrange: {
330
+ kind: "grid",
331
+ columns: [
332
+ { kind: "fill", weight: 2 },
333
+ { kind: "fill", weight: 1 },
334
+ { kind: "fill", weight: 1 },
335
+ ],
336
+ rows: [{ kind: "fixed", size: 160 }, { kind: "fixed", size: 220 }],
337
+ columnGap: 12,
338
+ rowGap: 12,
339
+ padding: { top: 16, right: 16, bottom: 16, left: 16 },
340
+ },
341
+ }
342
+ ```
343
+
344
+ ### 9.3 Spanning child
345
+
346
+ ```ts
347
+ {
348
+ id: "chart",
349
+ parent: "grid",
350
+ frame: { kind: "cell", row: 1, col: 0, colSpan: 2 },
351
+ view: "Chart",
352
+ }
353
+ ```
354
+
355
+ ### 9.4 Responsive variant example
356
+
357
+ Base:
358
+
359
+ - parent uses a 3-column grid,
360
+ - cards A/B/C have explicit `CellFrame` positions.
361
+
362
+ Narrow variant:
363
+
364
+ - parent switches to one column,
365
+ - A/B/C each override `frame` to new `row`/`col` values.
366
+
367
+ ```ts
368
+ {
369
+ id: "grid",
370
+ parent: "root",
371
+ frame: { kind: "anchor", left: 0, right: 0, top: 0, bottom: 0 },
372
+ arrange: {
373
+ kind: "grid",
374
+ columns: [{ kind: "fill" }, { kind: "fill" }, { kind: "fill" }],
375
+ rows: [{ kind: "fixed", size: 120 }, { kind: "fixed", size: 120 }],
376
+ columnGap: 8,
377
+ rowGap: 8,
378
+ },
379
+ variants: [
380
+ {
381
+ when: { maxWidth: 700 },
382
+ arrange: {
383
+ kind: "grid",
384
+ columns: [{ kind: "fill" }],
385
+ rows: [
386
+ { kind: "fixed", size: 120 },
387
+ { kind: "fixed", size: 120 },
388
+ { kind: "fixed", size: 120 },
389
+ ],
390
+ rowGap: 8,
391
+ },
392
+ },
393
+ ],
394
+ }
395
+
396
+ {
397
+ id: "cardA",
398
+ parent: "grid",
399
+ frame: { kind: "cell", row: 0, col: 0 },
400
+ variants: [{ when: { maxWidth: 700 }, frame: { kind: "cell", row: 0, col: 0 } }],
401
+ }
402
+
403
+ {
404
+ id: "cardB",
405
+ parent: "grid",
406
+ frame: { kind: "cell", row: 0, col: 1 },
407
+ variants: [{ when: { maxWidth: 700 }, frame: { kind: "cell", row: 1, col: 0 } }],
408
+ }
409
+ ```
410
+
411
+ ## 10. Implementation plan for M5b
412
+
413
+ 1. Add `GridTrack`, `GridArrange`, `CellFrame` types and new error codes.
414
+ 2. Update `resolveFrame` to reject `CellFrame` outside grid path (`CellFrameWithoutGrid`).
415
+ 3. Add an internal grid axis track resolution helper.
416
+ 4. Add an internal grid child-cell rectangle helper.
417
+ 5. Extend `resolveLayoutDocument` parent-arranger branch for `arrange.kind === "grid"`.
418
+ 6. Preserve metadata through existing resolved document/tree/flatten/lerp paths.
419
+ 7. Add resolver and integration tests.
420
+ 8. Update docs/README references for new runtime support once landed.
421
+
422
+ ## 11. Test plan for M5b
423
+
424
+ ### 11.1 Types/API
425
+
426
+ - `GridTrack`, `GridArrange`, `CellFrame` importability/public typing coverage.
427
+
428
+ ### 11.2 Track resolution
429
+
430
+ - fixed tracks,
431
+ - fill tracks,
432
+ - weighted fill,
433
+ - mixed fixed + fill,
434
+ - gap handling,
435
+ - padding handling,
436
+ - overflow -> `GridOverflow`.
437
+
438
+ ### 11.3 Cell placement
439
+
440
+ - basic cell placement,
441
+ - row/column starts,
442
+ - `colSpan`,
443
+ - `rowSpan`,
444
+ - gaps included across spans,
445
+ - fractional fill size propagation.
446
+
447
+ ### 11.4 Validation
448
+
449
+ - empty rows/columns,
450
+ - invalid fixed size,
451
+ - invalid fill weight,
452
+ - negative or non-finite gaps,
453
+ - padding causing negative content,
454
+ - invalid row/col values,
455
+ - invalid spans,
456
+ - out-of-range cell spans,
457
+ - non-cell direct child under grid,
458
+ - cell child under non-grid.
459
+
460
+ ### 11.5 Integration
461
+
462
+ - nested grids,
463
+ - grid child with its own `StackArrange`,
464
+ - grid + offsets,
465
+ - grid + sibling-local `z`,
466
+ - responsive variant selecting grid arrange and cell frames,
467
+ - `lerpResolvedLayouts` between grid-resolved documents.
468
+
469
+ ## 12. Risks and mitigations
470
+
471
+ - **Risk: scope drift toward CSS Grid.**
472
+ **Mitigation:** keep explicit forbidden list and grid-specific validation/errors.
473
+ - **Risk: ambiguity around child frame legality.**
474
+ **Mitigation:** enforce strict direct-child `CellFrame` rule with dedicated errors.
475
+ - **Risk: hidden behavior via auto-placement pressure.**
476
+ **Mitigation:** explicit row/col required in all cases.
477
+ - **Risk: incompatibility concerns with existing adapters.**
478
+ **Mitigation:** preserve resolved-rectangle boundary; no adapter coupling.
479
+ - **Risk: future extension pressure (`UiLength` tracks, alignment modes).**
480
+ **Mitigation:** document as deferred v2 topics and keep M5b implementation narrow.
@@ -0,0 +1,51 @@
1
+ # GridArrange runtime (M5b)
2
+
3
+ `GridArrange` adds explicit deterministic 2D placement to MachinaLayout. It places direct children into declared rows/columns and resolves normal rectangles for downstream renderers.
4
+
5
+ ## Types
6
+
7
+ - `GridTrack`: `{ kind: "fixed", size }` or `{ kind: "fill", weight? }`
8
+ - `GridArrange`: `{ kind: "grid", columns, rows, columnGap?, rowGap?, padding? }`
9
+ - `CellFrame`: `{ kind: "cell", row, col, rowSpan?, colSpan? }`
10
+
11
+ ## Behavior
12
+
13
+ - Only direct children of a grid parent may use `CellFrame`.
14
+ - Grid computes a content rect from parent rect minus padding.
15
+ - Fixed tracks consume fixed space; fill tracks split remaining space by weight.
16
+ - Gaps are explicit; no justify, no auto-gap expansion.
17
+ - Cell spans include internal gaps.
18
+ - Offset is applied after cell placement using existing `applyOffset` semantics.
19
+
20
+ ## Validation / error codes
21
+
22
+ - `InvalidGridTrack`: invalid columns/rows/track values/gaps.
23
+ - `InvalidGridCell`: invalid row/col/spans or out-of-range.
24
+ - `GridChildMustBeCell`: grid parent has non-cell direct child.
25
+ - `CellFrameWithoutGrid`: cell resolved outside grid arranger path.
26
+ - `GridContentNegative`: padding makes content width/height negative.
27
+ - `GridOverflow`: fixed tracks + gaps exceed content size.
28
+
29
+ ## Example
30
+
31
+ ```ts
32
+ {
33
+ id: "root",
34
+ frame: { kind: "root" },
35
+ arrange: {
36
+ kind: "grid",
37
+ columns: [{ kind: "fixed", size: 100 }, { kind: "fill" }],
38
+ rows: [{ kind: "fixed", size: 40 }, { kind: "fill" }],
39
+ columnGap: 8,
40
+ rowGap: 8,
41
+ padding: 8,
42
+ },
43
+ }
44
+ ```
45
+
46
+ ## Limitations
47
+
48
+ - Not CSS Grid compatible.
49
+ - No auto-placement.
50
+ - No implicit tracks.
51
+ - No template areas/named lines/minmax/subgrid.
@@ -0,0 +1,126 @@
1
+ # Inspection and handoff bundles
2
+
3
+ MachinaLayout inspection helpers provide a small, framework-light contract for handing UI work from one person or model to another. The utilities standardize the data shapes that app-local workflows can combine with screenshots and layout snapshots, without adding browser automation to MachinaLayout itself.
4
+
5
+ ## Purpose
6
+
7
+ A useful UI handoff often contains four artifacts:
8
+
9
+ - a screenshot for visual truth, produced by userland tooling;
10
+ - a compact DOM summary for semantic/browser truth;
11
+ - a Machina layout snapshot for layout truth;
12
+ - a handoff manifest for route, viewport, screen, and artifact metadata.
13
+
14
+ M25c standardizes the DOM summary and handoff manifest/writer pieces. It does not capture screenshots, launch browsers, drive routes, run viewport matrices, or perform visual diffs.
15
+
16
+ ## DOM summary
17
+
18
+ Import DOM-safe helpers from the inspect subpath:
19
+
20
+ ```ts
21
+ import { summarizeMachinaDom } from "machinalayout/inspect";
22
+
23
+ const summary = summarizeMachinaDom({
24
+ root: document,
25
+ includeA11y: true,
26
+ includeTextExcerpt: true,
27
+ generatedAt: "2026-01-01T00:00:00.000Z",
28
+ });
29
+ ```
30
+
31
+ By default, `summarizeMachinaDom` selects `[data-machina-node-id]`, reads only compact debug metadata, calls `getBoundingClientRect()` for each selected element, and reconstructs the nearest matching ancestor hierarchy. It intentionally does not dump full HTML.
32
+
33
+ The standard Machina browser debug attributes are:
34
+
35
+ - `data-machina-node-id`
36
+ - `data-machina-view`
37
+ - `data-machina-slot`
38
+ - `data-machina-debug-label`
39
+ - `data-machina-layer`
40
+
41
+ When `includeA11y` is enabled, the summary includes `role` and `aria-label` if present. When `includeTextExcerpt` is enabled, the summary includes normalized `textContent` excerpts. Text excerpts are compact hints, not a lossless DOM serialization; the simple text collection may include descendant text in ancestor excerpts.
42
+
43
+ You can also provide a custom selector for app-specific annotations:
44
+
45
+ ```ts
46
+ const summary = summarizeMachinaDom({
47
+ selector: "[data-debug-node]",
48
+ includeTextExcerpt: true,
49
+ });
50
+ ```
51
+
52
+ ## Handoff bundle writer
53
+
54
+ Import Node-only handoff helpers from the handoff subpath:
55
+
56
+ ```ts
57
+ import { writeMachinaHandoffBundle } from "machinalayout/handoff";
58
+
59
+ await writeMachinaHandoffBundle({
60
+ outputDir: "./artifacts/provider-setup-phone",
61
+ artifactBaseName: "Provider Setup / Phone!",
62
+ screenshotPath: "./artifacts/screenshot.png",
63
+ domSummary: summary,
64
+ layoutSnapshot,
65
+ route: "/provider/setup",
66
+ tags: ["handoff", "phone"],
67
+ });
68
+ ```
69
+
70
+ `writeMachinaHandoffBundle` ensures the output directory exists, writes JSON with two-space indentation, copies an existing screenshot when one is supplied, and returns absolute output paths. The manifest stores relative artifact file names so the bundle can move as a directory.
71
+
72
+ The writer uses deterministic artifact names based on a slugged base name:
73
+
74
+ - `${base}__screenshot.<ext>`
75
+ - `${base}__dom-summary.json`
76
+ - `${base}__machina-snapshot.json`
77
+ - `${base}__handoff.json`
78
+
79
+ If no explicit base name is supplied, the writer uses `task.artifactBaseName`, then route/fixture/viewport metadata, then `machina-handoff`.
80
+
81
+ ## Manifest shape
82
+
83
+ The manifest has `schemaVersion: 1`, a `createdAt` timestamp, optional route/fixture/screen/viewport metadata, optional tags and metadata, and an `artifacts` object containing relative file names.
84
+
85
+ ```json
86
+ {
87
+ "schemaVersion": 1,
88
+ "createdAt": "2026-01-01T00:00:00.000Z",
89
+ "route": "/provider/setup",
90
+ "viewportKey": "phone",
91
+ "artifactBaseName": "provider-setup-phone",
92
+ "artifacts": {
93
+ "screenshot": "provider-setup-phone__screenshot.png",
94
+ "domSummary": "provider-setup-phone__dom-summary.json",
95
+ "layoutSnapshot": "provider-setup-phone__machina-snapshot.json",
96
+ "manifest": "provider-setup-phone__handoff.json"
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Composition with screen tasks
102
+
103
+ The handoff writer accepts a `MachinaScreenViewportTask` from the screen catalog and viewport matrix helpers. When provided, the writer copies `route`, `fixture`, `screenKey`, `viewportKey`, `viewport`, `task.artifactBaseName`, and task tags into the manifest. Input tags are merged after task tags with duplicates removed while preserving order.
104
+
105
+ ```ts
106
+ await writeMachinaHandoffBundle({
107
+ outputDir: "./artifacts",
108
+ task,
109
+ domSummary,
110
+ layoutSnapshot,
111
+ });
112
+ ```
113
+
114
+ ## Limitations and boundaries
115
+
116
+ These utilities are intentionally narrow:
117
+
118
+ - no Playwright dependency;
119
+ - no browser launch or browser automation;
120
+ - no screenshot capture;
121
+ - no viewport matrix runner;
122
+ - no visual diff;
123
+ - no adapter behavior changes;
124
+ - no layout resolver semantics changes.
125
+
126
+ Userland tooling remains responsible for route navigation, viewport setup, screenshots, and any visual comparison workflow. MachinaLayout provides the lightweight schemas and writing helpers that make those artifacts predictable.
@@ -0,0 +1,52 @@
1
+ # Layout interpolation (M4a)
2
+
3
+ MachinaLayout includes pure helpers for interpolating **already resolved** layout documents:
4
+
5
+ - `lerpNumber(a, b, t)`
6
+ - `lerpRect(a, b, t)`
7
+ - `lerpResolvedLayouts(a, b, t)`
8
+
9
+ ## Boundary
10
+
11
+ Interpolation happens after layout resolution. MachinaLayout remains geometry authority, but it does **not** own animation timing.
12
+
13
+ Typical flow:
14
+
15
+ 1. Author collapsed rows and resolve.
16
+ 2. Author expanded rows and resolve.
17
+ 3. Drive `t` from your own animation source (spring/RAF/timeline).
18
+ 4. Call `lerpResolvedLayouts(collapsed, expanded, t)`.
19
+ 5. Render the result via `MachinaReactView`.
20
+
21
+ ## Compatibility requirement
22
+
23
+ `lerpResolvedLayouts` only supports compatible resolved documents:
24
+
25
+ - same `rootId`
26
+ - same node id set
27
+ - same parent/children structure
28
+ - same child ordering for every parent
29
+ - root id exists in both node maps
30
+
31
+ If compatibility fails, it throws `MachinaLayoutError` with code `"IncompatibleLayouts"`.
32
+
33
+ ## Numeric behavior
34
+
35
+ - linear interpolation only: `a + (b - a) * t`
36
+ - no clamping and no rounding
37
+ - `t < 0` and `t > 1` are allowed (overshoot supported)
38
+ - non-finite numbers throw `"NonFiniteNumber"`
39
+
40
+ ## Metadata behavior
41
+
42
+ `lerpResolvedLayouts` interpolates only `rect` fields. Other node metadata is preserved from the **end** layout `b` so `t=1` matches `b` structurally and semantically.
43
+
44
+ ## Current limitations
45
+
46
+ M4a does not support:
47
+
48
+ - graph morphing (enter/exit nodes)
49
+ - reordering children during interpolation
50
+ - metadata interpolation (`z`, `view`, `frame`, etc.)
51
+ - text/style/color interpolation
52
+ - animation loops, timers, or spring ownership