machinalayout 0.2.0 → 0.3.1

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 (37) hide show
  1. package/README.md +15 -0
  2. package/dist/MachinaReactView-Bau5bErA.d.ts +41 -0
  3. package/dist/chunk-2ZQ2RFFI.js +400 -0
  4. package/dist/chunk-33CKBEJH.js +186 -0
  5. package/dist/{chunk-HU6XYOH7.js → chunk-MSTBFD7H.js} +106 -17
  6. package/dist/{chunk-TR24ERZT.js → chunk-SVWYWI7I.js} +3 -10
  7. package/dist/chunk-VREK57S3.js +13 -0
  8. package/dist/chunk-ZIGW44D5.js +63 -0
  9. package/dist/debugOverlay-fWLv1cS7.d.ts +36 -0
  10. package/dist/deus/index.d.ts +15 -0
  11. package/dist/deus/index.js +26 -0
  12. package/dist/handoff/index.d.ts +44 -0
  13. package/dist/handoff/index.js +83 -0
  14. package/dist/index.d.ts +47 -5
  15. package/dist/index.js +169 -3
  16. package/dist/inspect/index.d.ts +8 -0
  17. package/dist/inspect/index.js +97 -0
  18. package/dist/react/index.d.ts +6 -33
  19. package/dist/react/index.js +9 -3
  20. package/dist/react-native/index.d.ts +3 -1
  21. package/dist/react-native/index.js +8 -2
  22. package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
  23. package/dist/types-CWaup8Z6.d.ts +92 -0
  24. package/dist/{types-BudfpzZX.d.ts → types-CYgsjDai.d.ts} +1 -1
  25. package/dist/types-bJlg6wno.d.ts +32 -0
  26. package/dist/useDeusMachine-2w2u_dki.d.ts +14 -0
  27. package/dist/vue/index.d.ts +15 -3
  28. package/dist/vue/index.js +39 -4
  29. package/docs/deusmachina.md +191 -0
  30. package/docs/error-codes.md +11 -0
  31. package/docs/inspection-and-handoff.md +126 -0
  32. package/docs/react-adapter.md +27 -0
  33. package/docs/react-native-adapter.md +4 -0
  34. package/docs/screen-catalog-and-viewports.md +124 -0
  35. package/docs/stack-geometry-helpers.md +115 -0
  36. package/docs/vue-adapter.md +4 -0
  37. package/package.json +127 -115
package/dist/vue/index.js CHANGED
@@ -1,9 +1,43 @@
1
1
  import {
2
2
  toResolvedTree
3
- } from "../chunk-TR24ERZT.js";
3
+ } from "../chunk-SVWYWI7I.js";
4
+ import "../chunk-VREK57S3.js";
5
+ import {
6
+ createDeusSnapshot,
7
+ stepDeusMachine
8
+ } from "../chunk-2ZQ2RFFI.js";
9
+
10
+ // src/vue/useDeusMachine.ts
11
+ import { computed, ref } from "vue";
12
+ function resolveInitialBoard(initialBoard) {
13
+ return typeof initialBoard === "function" ? initialBoard() : initialBoard;
14
+ }
15
+ function useDeusMachine(machine, initialBoard) {
16
+ const createSnapshot = (board) => createDeusSnapshot(machine, resolveInitialBoard(board ?? initialBoard));
17
+ const snapshot = ref(createSnapshot());
18
+ const lastTrace = ref(null);
19
+ const dispatch = (event) => {
20
+ const result = stepDeusMachine(machine, snapshot.value, event);
21
+ snapshot.value = result.snapshot;
22
+ lastTrace.value = result.trace;
23
+ return result;
24
+ };
25
+ const reset = (board) => {
26
+ snapshot.value = createSnapshot(board);
27
+ lastTrace.value = null;
28
+ };
29
+ return {
30
+ snapshot,
31
+ board: computed(() => snapshot.value.board),
32
+ state: computed(() => snapshot.value.state),
33
+ dispatch,
34
+ lastTrace,
35
+ reset
36
+ };
37
+ }
4
38
 
5
39
  // src/vue/MachinaVueView.ts
6
- import { computed, defineComponent, h } from "vue";
40
+ import { computed as computed2, defineComponent, h } from "vue";
7
41
  var normalizeLayerZ = (v) => v === void 0 || !Number.isFinite(v) || !Number.isInteger(v) || v < -5 || v > 5 ? 0 : v;
8
42
  var getEffectiveLayer = (n, d) => n.layer ?? d;
9
43
  var getEffectiveLayerZ = (n, l, d) => normalizeLayerZ(l[getEffectiveLayer(n, d)]?.z);
@@ -32,7 +66,7 @@ var MachinaVueView = defineComponent({
32
66
  nodeContainIntrinsicSize: { type: String, default: void 0 }
33
67
  },
34
68
  setup(props) {
35
- const tree = computed(() => toResolvedTree(props.layout));
69
+ const tree = computed2(() => toResolvedTree(props.layout));
36
70
  const renderNode = (node, parentRect) => {
37
71
  const viewKey = node.view ?? node.slot;
38
72
  const View = viewKey ? props.views[viewKey] : void 0;
@@ -107,5 +141,6 @@ var MachinaVueView = defineComponent({
107
141
  }
108
142
  });
109
143
  export {
110
- MachinaVueView
144
+ MachinaVueView,
145
+ useDeusMachine
111
146
  };
@@ -0,0 +1,191 @@
1
+ # DeusMachina
2
+
3
+ DeusMachina is MachinaLayout's tiny behavioral kernel. It is deliberately small: utility judgment, explicit row-first state machines, stack-style state paths, deterministic stepping, and trace output.
4
+
5
+ ```ts
6
+ import { defineDeusMachine, judgeUtility, stepDeusMachine } from "machinalayout/deus";
7
+ ```
8
+
9
+ ## Contract summary
10
+
11
+ DeusMachina combines three narrow pieces:
12
+
13
+ 1. `judgeUtility(context, candidates, options)` for deterministic utility selection.
14
+ 2. `defineDeusMachine(machine)` for validating row-first stack HFSM definitions.
15
+ 3. `stepDeusMachine(machine, snapshot, event)` for one synchronous deterministic step with trace output.
16
+
17
+ State paths are non-empty arrays of non-empty strings. Segments are not trimmed; empty or whitespace-only segments are invalid. `formatDeusPath(["a", "b"])` formats paths as `a/b`. DeusMachina treats a path as its own ancestor for transition candidate collection.
18
+
19
+ ## Row-first authoring
20
+
21
+ Machines are authored as arrays, not nested objects:
22
+
23
+ ```ts
24
+ defineDeusMachine({
25
+ initial: ["debugOverlay", "collapsed"],
26
+ states: [{ path: ["debugOverlay", "collapsed"] }],
27
+ transitions: [{ key: "show", from: ["debugOverlay", "collapsed"], event: "showOverlay" }],
28
+ });
29
+ ```
30
+
31
+ Hierarchy comes from stack paths such as `debugOverlay/nonInteractiveOverlay`, not from nested authoring syntax.
32
+
33
+ ## Mutable board convention
34
+
35
+ Snapshots keep the board by reference. `stepDeusMachine` returns a new snapshot object and a copied state path, but it does not clone the board. User actions may mutate the board intentionally. Machine definitions are treated as immutable and are copied where DeusMachina normalizes paths.
36
+
37
+ ## Purity rules
38
+
39
+ `when`, `score`, and `reason` should be pure. They may read the board and event, but should not mutate them. `do`, `onEnter`, and `onExit` may mutate the board. DeusMachina does not sandbox user functions, so accidental mutation from guards, scores, or reasons is possible and is the caller's responsibility.
40
+
41
+ User errors are not swallowed. If a guard, score, reason, action, enter hook, or exit hook throws, the error propagates to the caller.
42
+
43
+ ## Transition selection semantics
44
+
45
+ A step gathers candidate transitions from the exact current state path, then parent paths upward to root. Candidate grouping is leaf first, then parent, then grandparent. Within each `from` path, author transition order is preserved.
46
+
47
+ An omitted transition `event` accepts any event. Otherwise, the transition event must equal `event.type`. An omitted `when` means eligible; a false `when` makes the transition ineligible and keeps it in the trace with score `0`.
48
+
49
+ Eligible transitions score as follows:
50
+
51
+ - omitted transition `score`: `1`
52
+ - numeric or function transition `score`: that finite score
53
+ - utility transition with no explicit transition `score`: selected utility candidate score
54
+
55
+ All gathered eligible candidates compete by score regardless of depth. Highest score wins. Ties are stable by candidate search order, so a leaf transition beats an equal-scored parent transition, and earlier author rows beat later rows within the same path.
56
+
57
+ If no transition is selected, state and board reference are unchanged, but `stepIndex` increments because a step occurred. If a selected transition has no `to`, state remains the same. Same-state transitions do not run exit or enter hooks.
58
+
59
+ Dynamic `to` functions are validated at runtime. They must return a valid path that exists in the machine.
60
+
61
+ ## Utility judgment semantics
62
+
63
+ `judgeUtility` preserves candidate array order in its trace. Omitted `when` means eligible. A false `when` makes the candidate ineligible with score `0`, and score functions are evaluated only for eligible candidates. Reason strings or functions are included in trace entries; reason functions are evaluated while building trace entries.
64
+
65
+ Scores must be finite. If no candidate is eligible, `selected` is `null`. Highest score wins and ties are stable by candidate order.
66
+
67
+ Hysteresis accepts a finite margin greater than or equal to `0`. If `previousKey` is present and the previous candidate is still eligible, a challenger must satisfy `challengerScore >= previousScore + margin` to replace it. If margin is `0`, normal highest-score semantics apply. If the previous candidate is missing or ineligible, normal selection applies.
68
+
69
+ `judgeUtility` does not mutate candidate definitions or the context itself, though user-provided functions can mutate external objects if they choose to.
70
+
71
+ ## Utility transition semantics
72
+
73
+ A transition with `utility` evaluates utility candidates only after its event and `when` checks pass. If no utility candidate is selected, the transition is not eligible and neither utility `do` nor transition `do` runs.
74
+
75
+ If a utility candidate is selected, its judgment appears in the transition trace. The transition score is the explicit transition score when provided; otherwise it is the selected utility candidate score. Transition `hysteresis` is passed through to the utility judgment via `hysteresis.previous(board)` and `hysteresis.margin`; `undefined` from `previous` means no previous key.
76
+
77
+ Utility candidate `do` receives the original board and event and runs before the outer transition `do`.
78
+
79
+ ## Enter, exit, and action order
80
+
81
+ When state changes, execution order is:
82
+
83
+ 1. `onExit` for states being exited, deepest first.
84
+ 2. Selected utility candidate `do`, if present.
85
+ 3. Transition `do`, if present.
86
+ 4. `onEnter` for states being entered, shallow to deep.
87
+
88
+ For `root/a/x -> root/b/y`, exit order is `root/a/x`, then `root/a`; enter order is `root/b`, then `root/b/y`. For `root/a/x -> root/a/y`, only `root/a/x` exits and only `root/a/y` enters. For `root/a -> root/a`, no exit or enter hooks run.
89
+
90
+ ## Trace contract
91
+
92
+ `stepDeusMachine` returns a trace containing state before, state after, event type, considered transitions, selected transition, transition eligibility, transition score, transition search index, reasons when provided, and inner utility judgment when applicable.
93
+
94
+ Trace data is intended to be JSON-serializable. It does not include function references, the board object, or arbitrary event payloads. `formatDeusStepTrace(trace)` provides a small deterministic one-line summary for selected and unselected steps.
95
+
96
+ ## Debug overlay helper
97
+
98
+ `createMachinaDebugOverlayMachine()` returns a validated DeusMachina machine for the controlled React debug overlay modes. `getMachinaDebugOverlayBehavior(board)` maps the board to rendering behavior:
99
+
100
+ - `collapsed`: not visible, `pointerEvents: "none"`, consumes no layout space.
101
+ - `nonInteractiveOverlay`: visible, `pointerEvents: "none"`, consumes no layout space.
102
+ - `interactivePanel`: visible, `pointerEvents: "auto"`, consumes layout space.
103
+
104
+ Labels and borders remain controlled by the board booleans; `false` stays false in overlay and panel modes.
105
+
106
+ ## Non-goals
107
+
108
+ DeusMachina intentionally does not include async workflows, tools, actors, persistence, LLM calls, schedulers, nested authoring syntax, uncontrolled React state, or a visual editor.
109
+
110
+ ## Framework bindings
111
+
112
+ M26b adds thin framework bindings for the DeusMachina kernel from the existing adapter subpaths:
113
+
114
+ ```ts
115
+ import { useDeusMachine } from "machinalayout/react";
116
+ import { useDeusMachine as useNativeDeusMachine } from "machinalayout/react-native";
117
+ import { useDeusMachine as useVueDeusMachine } from "machinalayout/vue";
118
+ ```
119
+
120
+ The bindings wrap `createDeusSnapshot` and `stepDeusMachine`; `machinalayout/deus` remains framework-free and does not import React, React Native, or Vue. They do not add stores, context providers, async workflows, persistence, actors, or router integration.
121
+
122
+ All bindings return the current `snapshot`, `board`, `state`, `dispatch`, `lastTrace`, and `reset`. Deus actions keep the kernel's mutable board convention: user actions may mutate the board in place, and the bindings trigger framework updates by replacing the snapshot wrapper after every dispatch. They do not deep-clone, freeze, proxy, or sandbox the board.
123
+
124
+ Define machines outside render/setup when possible. The React and React Native hooks reset the snapshot when the `machine` reference changes; the Vue composable expects a stable machine input.
125
+
126
+ `reset()` recreates the snapshot with `stepIndex: 0` and clears `lastTrace`. Pass a board value or factory to reset to that board; omit the argument to reuse the original initial board/factory.
127
+
128
+ ### React debug overlay example
129
+
130
+ ```tsx
131
+ import {
132
+ createMachinaDebugOverlayMachine,
133
+ getMachinaDebugOverlayBehavior,
134
+ type MachinaDebugOverlayBoard,
135
+ type MachinaDebugOverlayEvent,
136
+ } from "machinalayout/deus";
137
+ import { useDeusMachine } from "machinalayout/react";
138
+
139
+ const debugMachine = createMachinaDebugOverlayMachine();
140
+
141
+ function DebugControls() {
142
+ const debug = useDeusMachine<MachinaDebugOverlayBoard, MachinaDebugOverlayEvent>(debugMachine, {
143
+ mode: "collapsed",
144
+ labels: true,
145
+ borders: true,
146
+ });
147
+ const behavior = getMachinaDebugOverlayBehavior(debug.board);
148
+
149
+ return (
150
+ <>
151
+ <button onClick={() => debug.dispatch({ type: "showOverlay" })}>Overlay</button>
152
+ <button onClick={() => debug.dispatch({ type: "openPanel" })}>Panel</button>
153
+ <button onClick={() => debug.dispatch({ type: "collapse" })}>Collapse</button>
154
+ <pre>{JSON.stringify(behavior, null, 2)}</pre>
155
+ </>
156
+ );
157
+ }
158
+ ```
159
+
160
+ ### React Native debug overlay example
161
+
162
+ ```tsx
163
+ import { Button, Text, View } from "react-native";
164
+ import { createMachinaDebugOverlayMachine, getMachinaDebugOverlayBehavior } from "machinalayout/deus";
165
+ import { useDeusMachine } from "machinalayout/react-native";
166
+
167
+ const debugMachine = createMachinaDebugOverlayMachine();
168
+
169
+ function NativeDebugControls() {
170
+ const debug = useDeusMachine(debugMachine, { mode: "collapsed", labels: true, borders: true });
171
+ const behavior = getMachinaDebugOverlayBehavior(debug.board);
172
+ return (
173
+ <View>
174
+ <Button title="Overlay" onPress={() => debug.dispatch({ type: "showOverlay" })} />
175
+ <Text>{behavior.visible ? "visible" : "hidden"}</Text>
176
+ </View>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ### Vue debug overlay example
182
+
183
+ ```ts
184
+ import { computed } from "vue";
185
+ import { createMachinaDebugOverlayMachine, getMachinaDebugOverlayBehavior } from "machinalayout/deus";
186
+ import { useDeusMachine } from "machinalayout/vue";
187
+
188
+ const debugMachine = createMachinaDebugOverlayMachine();
189
+ const debug = useDeusMachine(debugMachine, { mode: "collapsed", labels: true, borders: true });
190
+ const behavior = computed(() => getMachinaDebugOverlayBehavior(debug.board.value));
191
+ ```
@@ -47,6 +47,8 @@ This document summarizes public layout and text diagnostic codes.
47
47
  - Note: this code name is historical and stable; do not rename it.
48
48
  - `StackContentNegative` — stack content space became negative. Typical cause: padding/gaps exceed container space.
49
49
  - `StackOverflow` — stack children exceed available axis space. Typical cause: fixed sizes + gaps exceed container.
50
+ - `ExpectedStackArrange` — a stack query helper was called on a non-stack node. Typical cause: using stack-only geometry helpers with a plain or grid parent.
51
+ - `StackQueryInvalidRange` — a remaining stack rectangle query produced a negative interval. Typical cause: `afterChildren` resolve after `beforeChildren`.
50
52
 
51
53
  ### Grid
52
54
 
@@ -66,6 +68,15 @@ This document summarizes public layout and text diagnostic codes.
66
68
  - `InvalidGuideFrame` — guide frame declaration is malformed. Typical cause: incomplete or conflicting guide spec.
67
69
  - `GuideTargetUnresolved` — guide target exists but was not resolved when needed. Typical cause: invalid dependency order/cycle.
68
70
 
71
+ ### Screen catalog and viewport matrix
72
+
73
+ - `InvalidViewport` — viewport metadata is malformed. Typical cause: blank key, non-positive dimensions, invalid `deviceScaleFactor`, or invalid lightweight metadata.
74
+ - `DuplicateViewportKey` — two viewport presets share one key. Typical cause: duplicate matrix entries.
75
+ - `UnknownViewportKey` — a requested or screen-referenced viewport key is absent from the matrix. Typical cause: typo or filtered matrix mismatch.
76
+ - `InvalidScreen` — screen catalog metadata is malformed. Typical cause: blank key, blank route, or invalid lightweight metadata.
77
+ - `DuplicateScreenKey` — two screen definitions share one key. Typical cause: duplicate catalog entries.
78
+ - `UnknownScreenKey` — a requested screen key is absent from the catalog. Typical cause: typo in an expansion filter.
79
+
69
80
  ### Variants
70
81
 
71
82
  - `InvalidVariantCondition` — variant condition is invalid. Typical cause: unsupported operator/value shape.
@@ -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.
@@ -90,3 +90,30 @@ Not used as geometry authority:
90
90
  - CSS classes determining solved geometry
91
91
 
92
92
  React components render payload UI inside adapter-owned rectangles; React does not own outer layout geometry.
93
+
94
+ ## Inspection handoff surface
95
+
96
+ React DOM rendering includes the standard Machina `data-machina-*` debug attributes used by the framework-light DOM summary helpers. See [Inspection and handoff bundles](inspection-and-handoff.md) for the `machinalayout/inspect` and `machinalayout/handoff` workflow.
97
+
98
+ ## Debug overlay
99
+
100
+ `MachinaReactView` accepts an optional controlled `debugOverlay` prop:
101
+
102
+ ```tsx
103
+ <MachinaReactView
104
+ layout={layout}
105
+ debugOverlay={{ mode: "nonInteractiveOverlay", labels: true, borders: true }}
106
+ />
107
+ ```
108
+
109
+ Modes:
110
+
111
+ - `collapsed`: no overlay labels or borders are rendered and app interactions are not blocked.
112
+ - `nonInteractiveOverlay`: overlay labels and borders render when enabled, do not consume layout space, and use `pointer-events: none`, making the mode suitable for screenshots and browser automation.
113
+ - `interactivePanel`: overlay labels/borders can render with a small panel and `pointer-events: auto` for human inspection.
114
+
115
+ The prop is controlled. M26 does not add React state management or hooks; DeusMachina provides the standalone behavior helpers used to derive the rendering semantics. In `nonInteractiveOverlay`, overlay artifacts use `pointer-events: none` and do not consume layout space; in `collapsed`, overlay artifacts are not rendered. Labels and borders remain controlled booleans and do not change existing `data-machina-*` attributes on node wrappers.
116
+
117
+ ## DeusMachina hook
118
+
119
+ `machinalayout/react` exports `useDeusMachine(machine, initialBoard)`. The hook is a thin wrapper around the DeusMachina kernel and returns `snapshot`, `board`, `state`, `dispatch`, `lastTrace`, and `reset`. It follows the mutable board contract: actions may mutate the board, while the hook replaces the snapshot object after dispatch so React re-renders. Keep machine definitions stable; changing the machine reference resets the hook snapshot.
@@ -54,3 +54,7 @@ Keep `views` stable (component references). Send changing values through `viewDa
54
54
  - this package only renders layout boxes; text rendering is provided separately by `machinalayout/text/react-native`
55
55
  - portals/reparenting
56
56
  - DOM-only features
57
+
58
+ ## DeusMachina hook
59
+
60
+ `machinalayout/react-native` exports the same `useDeusMachine(machine, initialBoard)` contract as the React adapter without importing the DOM adapter. It returns `snapshot`, `board`, `state`, `dispatch`, `lastTrace`, and `reset`, and re-renders by replacing the snapshot wrapper while preserving the mutable board convention.
@@ -0,0 +1,124 @@
1
+ # Screen catalog and viewport matrix
2
+
3
+ Machina screen catalog helpers describe named app screens and responsive viewport presets as plain TypeScript data. They are intended for future capture, inspection, and handoff tooling, but they do not perform any browser automation themselves.
4
+
5
+ These utilities model this relationship:
6
+
7
+ ```ts
8
+ screen + viewport -> task metadata
9
+ ```
10
+
11
+ A future runner can consume each task to navigate an app route, set a viewport, capture screenshots, inspect DOM, collect layout snapshots, or write handoff bundles.
12
+
13
+ ## Screen catalog
14
+
15
+ A `MachinaScreen` is a stable, named screen entry:
16
+
17
+ ```ts
18
+ const screens = defineMachinaScreens([
19
+ {
20
+ key: "provider-setup",
21
+ route: "/apps/scheduling/setup",
22
+ fixture: "provider-setup",
23
+ viewports: ["desktop", "tablet", "phone"],
24
+ tags: ["scheduling", "setup"],
25
+ },
26
+ ]);
27
+ ```
28
+
29
+ The `route` value is opaque app metadata. Machina does not parse it, validate URL semantics, or integrate with a router. `fixture`, `tags`, `title`, and `metadata` are also app-owned metadata for downstream tools.
30
+
31
+ `defineMachinaScreens` validates screen keys and routes, rejects duplicate keys, returns fresh screen objects, and preserves author order in `catalog.order`.
32
+
33
+ ## Viewport matrix
34
+
35
+ A `MachinaViewport` is a stable viewport preset:
36
+
37
+ ```ts
38
+ const viewports = createViewportMatrix("standard-responsive");
39
+ ```
40
+
41
+ Built-in presets:
42
+
43
+ | Preset | Order | Sizes |
44
+ | --- | --- | --- |
45
+ | `standard-responsive` | desktop, tablet, phone | 1440×900, 1024×768, 390×844 |
46
+ | `desktop-only` | desktop | 1440×900 |
47
+ | `mobile-first` | phone, tablet, desktop | 390×844, 1024×768, 1440×900 |
48
+
49
+ Default preset is `standard-responsive`. Built-in viewport tags are:
50
+
51
+ - desktop: `desktop`
52
+ - tablet: `tablet`
53
+ - phone: `phone`, `mobile`
54
+
55
+ Use `defineMachinaViewports` for custom matrices. It validates unique non-empty keys, positive finite width/height, optional positive finite `deviceScaleFactor`, and preserves input order.
56
+
57
+ ## Task expansion
58
+
59
+ `expandScreenViewportTasks` expands a screen catalog and viewport matrix into deterministic `MachinaScreenViewportTask[]` values:
60
+
61
+ ```ts
62
+ const screens = defineMachinaScreens([
63
+ {
64
+ key: "provider-setup",
65
+ route: "/apps/scheduling/setup",
66
+ fixture: "provider-setup",
67
+ viewports: ["desktop", "tablet", "phone"],
68
+ tags: ["scheduling", "setup"],
69
+ },
70
+ ]);
71
+
72
+ const viewports = createViewportMatrix("standard-responsive");
73
+ const tasks = expandScreenViewportTasks(screens, viewports);
74
+ ```
75
+
76
+ Task ordering is always catalog order first, then viewport matrix order. One task represents one screen at one viewport and includes the raw screen route, fixture, copied screen/viewport references, merged tags, a deterministic task key, and a filesystem-safe artifact base name.
77
+
78
+ ## Filtering semantics
79
+
80
+ Expansion accepts optional filters:
81
+
82
+ ```ts
83
+ const phoneSchedulingTasks = expandScreenViewportTasks(screens, viewports, {
84
+ screenKeys: ["provider-setup"],
85
+ viewportKeys: ["phone"],
86
+ tags: ["scheduling", "mobile"],
87
+ });
88
+ ```
89
+
90
+ Filtering is deterministic:
91
+
92
+ 1. Start with screens in catalog order.
93
+ 2. If `screenKeys` is present, include only those screens. Unknown requested screen keys throw `UnknownScreenKey`.
94
+ 3. A screen's `viewports` list limits the default eligible viewport set for that screen.
95
+ 4. If `viewportKeys` is present, it further filters eligible viewport keys. Unknown requested viewport keys throw `UnknownViewportKey`.
96
+ 5. Screen-referenced viewport keys must exist in the provided viewport matrix or expansion throws `UnknownViewportKey`.
97
+ 6. Tags merge screen tags first, then viewport tags, with duplicates removed while preserving order.
98
+ 7. If `tags` is present, a task is included only when every requested tag is in the merged task tags.
99
+
100
+ ## Artifact base names
101
+
102
+ Task keys use raw keys joined by `__`, for example `provider-setup__phone`. Artifact base names slug each side independently:
103
+
104
+ ```ts
105
+ slugMachinaArtifactName("Provider Setup!"); // "provider-setup"
106
+ ```
107
+
108
+ A task for screen key `Provider Setup` and viewport key `Phone XL` receives `artifactBaseName: "provider-setup__phone-xl"`.
109
+
110
+ ## Limitations
111
+
112
+ This is a framework-independent metadata layer only. It intentionally does not include:
113
+
114
+ - browser automation,
115
+ - screenshot capture,
116
+ - DOM inspection or DOM summaries,
117
+ - handoff bundle writing,
118
+ - router integration,
119
+ - route parsing,
120
+ - fixture serving,
121
+ - adapter behavior,
122
+ - layout resolver semantic changes.
123
+
124
+ The helpers work in Node or the browser and have no Playwright, DOM, React, Vue, or React Native dependency.