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
@@ -1,26 +1,50 @@
1
- # React Adapter
1
+ # React adapter (`machinalayout/react`)
2
2
 
3
- ## Boundary
3
+ ## Shared model boundary
4
4
 
5
- - Core layout resolution has no React dependency.
6
- - The React adapter consumes a `ResolvedLayoutDocument`.
7
- - The adapter renders absolutely positioned wrappers.
8
- - Slot components render inside those wrappers.
5
+ - Layout geometry is authored and resolved by Machina core (`LayoutRow[]` -> resolved rectangles).
6
+ - React supplies components that render inside those resolved rectangles.
7
+ - The adapter does not introduce a React-specific layout model.
8
+ - Preferred import path is `machinalayout/react`.
9
+ - Root import compatibility remains during `0.x`.
9
10
 
10
11
  ## Basic usage
11
12
 
12
13
  ```tsx
13
- import { MachinaReactView, resolveLayoutRows } from "machinalayout";
14
+ import { resolveLayoutRows } from "machinalayout";
15
+ import { MachinaReactView } from "machinalayout/react";
14
16
 
15
- const resolved = resolveLayoutRows(rows, rootRect);
17
+ const layout = resolveLayoutRows(rows, rootRect);
18
+ const views = { Header, Sidebar, Inspector };
16
19
 
20
+ <MachinaReactView layout={layout} views={views} />;
21
+ ```
22
+
23
+ Effective render key is `view ?? slot`.
24
+
25
+ ## Stable view registry and data channels
26
+
27
+ `views` should hold stable component references. Pass dynamic values through data channels.
28
+
29
+ Good:
30
+
31
+ ```tsx
32
+ const views = { Inspector };
33
+
34
+ <MachinaReactView
35
+ layout={layout}
36
+ views={views}
37
+ viewData={{ Inspector: inspectorData }}
38
+ nodeData={{ sidebar: sidebarData }}
39
+ />;
40
+ ```
41
+
42
+ Bad (new component identity every render):
43
+
44
+ ```tsx
17
45
  const views = {
18
- header: HeaderView,
19
- sidebar: SidebarView,
20
- toolbarButton: ToolbarButtonView,
46
+ Inspector: () => <Inspector value={value} />,
21
47
  };
22
-
23
- <MachinaReactView layout={resolved} views={views} />;
24
48
  ```
25
49
 
26
50
  ## Slot props
@@ -31,33 +55,24 @@ Slot views receive:
31
55
  - `rect`
32
56
  - `debugLabel`
33
57
  - `node`
58
+ - `viewKey`
59
+ - `viewData`
60
+ - `nodeData`
34
61
 
35
62
  ## Coordinate normalization
36
63
 
37
- Core resolved rects are global/root-space coordinates.
64
+ Core resolved rectangles are root-space coordinates.
38
65
 
39
- The React adapter renders nested absolutely positioned DOM wrappers, so each node
40
- wrapper is positioned in its parent-local DOM coordinate space:
66
+ The adapter renders nested absolutely positioned DOM wrappers in parent-local space:
41
67
 
42
68
  - `left = node.rect.x - parent.rect.x`
43
69
  - `top = node.rect.y - parent.rect.y`
44
70
 
45
- The outer wrapper represents the root coordinate space and uses the resolved root
46
- width/height. The root node itself renders at local `left: 0` and `top: 0`.
47
-
48
- Examples:
49
-
50
- - root rect `{ x: 100, y: 200, width: 800, height: 600 }`
51
- - child rect `{ x: 116, y: 212, width: 100, height: 50 }`
52
- - rendered child CSS `left: 16px; top: 12px;`
71
+ The outer wrapper represents root coordinate space; the root node itself renders at local `left: 0`, `top: 0`.
53
72
 
54
- Nested example:
73
+ ## DOM renderer policy boundary
55
74
 
56
- - parent rect `{ x: 268, y: 88, width: 816, height: 616 }`
57
- - child rect `{ x: 284, y: 104, width: 784, height: 48 }`
58
- - rendered child CSS `left: 16px; top: 16px;`
59
-
60
- ## CSS boundary
75
+ Containment/content-visibility are DOM renderer policies implemented by the adapter layer.
61
76
 
62
77
  Allowed for Machina wrappers:
63
78
 
@@ -66,45 +81,35 @@ Allowed for Machina wrappers:
66
81
  - `box-sizing`
67
82
  - `z-index`
68
83
  - containment/content-visibility
69
- - cosmetic/debug styles
84
+ - optional debug/cosmetic wrapper styles
70
85
 
71
- Not allowed as Machina geometry authority:
86
+ Not used as geometry authority:
72
87
 
73
- - flexbox
74
- - grid
75
- - margins
76
- - transforms
77
- - DOM measurement
78
- - CSS classes determining geometry
88
+ - flexbox/grid/margins/transforms
89
+ - DOM measurement for layout solving
90
+ - CSS classes determining solved geometry
79
91
 
80
- Slot internals may use normal React/CSS/shadcn-style components. Machina controls only the outer rectangle.
92
+ React components render payload UI inside adapter-owned rectangles; React does not own outer layout geometry.
81
93
 
82
- ## Stable view registry and data channels
94
+ ## Inspection handoff surface
83
95
 
84
- `views` should be a stable registry of component types, not inline factories recreated from state.
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.
85
97
 
86
- Bad (new component identity each render):
98
+ ## Debug overlay
87
99
 
88
- ```tsx
89
- const views = {
90
- Inspector: () => <Inspector sidebarLeft={sidebarLeft} />,
91
- };
92
- ```
93
-
94
- Good (stable component type + dynamic data):
100
+ `MachinaReactView` accepts an optional controlled `debugOverlay` prop:
95
101
 
96
102
  ```tsx
97
- const views = { Inspector };
98
-
99
103
  <MachinaReactView
100
- layout={resolved}
101
- views={views}
102
- viewData={{ Inspector: { sidebarLeft } }}
104
+ layout={layout}
105
+ debugOverlay={{ mode: "nonInteractiveOverlay", labels: true, borders: true }}
103
106
  />
104
107
  ```
105
108
 
106
- Use adapter data channels for changing values:
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.
107
114
 
108
- - `viewData`: keyed by effective `view ?? slot` key.
109
- - `nodeData`: keyed by concrete node id.
110
- - slot props include `viewKey`, `viewData`, and `nodeData`.
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.
@@ -0,0 +1,56 @@
1
+ # React Native adapter (`machinalayout/react-native`)
2
+
3
+ `MachinaReactNativeView` renders a `ResolvedLayoutDocument` into nested React Native `View` wrappers.
4
+
5
+ ## Shared model boundary
6
+
7
+ - Uses the same Machina records and resolved rectangles as web/React/Vue adapters.
8
+ - The adapter maps resolved rectangles into React Native style objects.
9
+ - React Native components are the view payloads rendered inside those rectangles.
10
+
11
+ ## Import
12
+
13
+ ```ts
14
+ import { MachinaReactNativeView } from "machinalayout/react-native";
15
+ ```
16
+
17
+ ## Peer dependency
18
+
19
+ Install React Native in your app. `machinalayout` declares `react-native` as an optional peer.
20
+
21
+ ## Basic example
22
+
23
+ ```tsx
24
+ const views = { Panel: PanelView };
25
+
26
+ <MachinaReactNativeView
27
+ layout={layout}
28
+ views={views}
29
+ viewData={{ Panel: { title: "Now Playing" } }}
30
+ nodeData={{ sidebar: { selected: true } }}
31
+ />;
32
+ ```
33
+
34
+ ## Stable view registry guidance
35
+
36
+ Keep `views` stable (component references). Send changing values through `viewData` and `nodeData`, not by recreating inline component functions.
37
+
38
+ ## Supported concepts
39
+
40
+ - `view ?? slot` lookup
41
+ - `viewData` / `nodeData`
42
+ - layer + node z sorting
43
+ - parent-local coordinate normalization
44
+ - optional debug wrappers
45
+
46
+ ## Host renderer differences vs DOM adapters
47
+
48
+ - Uses React Native `View` wrappers and numeric style values.
49
+ - No DOM attributes/class hooks.
50
+ - No DOM containment/content-visibility behavior because RN is not DOM.
51
+
52
+ ## Not included
53
+
54
+ - this package only renders layout boxes; text rendering is provided separately by `machinalayout/text/react-native`
55
+ - portals/reparenting
56
+ - DOM-only features
@@ -0,0 +1,50 @@
1
+ # React Native MachinaText renderer (`machinalayout/text/react-native`)
2
+
3
+ `MachinaNativeTextView` renders MachinaText content inside an already-owned rectangle using React Native primitives (`View`, `Text`). It is a renderer only: no sizing, measurement, or layout resolution.
4
+
5
+ ## Import
6
+
7
+ ```ts
8
+ import { MachinaNativeTextView } from "machinalayout/text/react-native";
9
+ ```
10
+
11
+ ## Peer dependency
12
+
13
+ Install `react-native` in your app. `machinalayout` declares it as an optional peer.
14
+
15
+ ## Accepted `text` inputs
16
+
17
+ - `string`
18
+ - `MachinaTextSource`
19
+ - `MachinaTextSpec`
20
+ - `MachinaTextDocument`
21
+
22
+ ## Supported rendering
23
+
24
+ - Paragraph blocks
25
+ - Bullet lists (including nested items)
26
+ - Inline nodes: strong, emphasis, code, link
27
+
28
+ ## Policy support
29
+
30
+ - `variant`
31
+ - `wrap`
32
+ - `overflow`
33
+ - `align`
34
+ - `leading`
35
+ - `blockGap` / `listGap`
36
+ - `valign`
37
+
38
+ ## React Native differences from DOM renderer
39
+
40
+ - Uses `Text`/`View` (no DOM nodes).
41
+ - No `href`/`target`/`rel` handling.
42
+ - Link interaction uses `onLinkPress(href)`.
43
+ - `overflow: scroll` is not implemented with `ScrollView` in this renderer.
44
+
45
+ ## Non-goals
46
+
47
+ - Text measurement
48
+ - Intrinsic sizing
49
+ - Routing or navigation dispatch
50
+ - Editor/caret/selection behavior
@@ -0,0 +1,384 @@
1
+ # Reference-based alignment design contract (M7a)
2
+
3
+ ## 1) Executive summary
4
+
5
+ This contract proposes a **narrow, explicit, Machina-native** reference alignment feature for M7b that solves real authoring pain (edge-following and attachment) **without** turning MachinaLayout into a general constraint solver.
6
+
7
+ **Recommendation (staged):**
8
+
9
+ - **M7b-1 (primary): `GuideFrame`** for one-axis/two-axis edge-based alignment with strict limits.
10
+ - **M7b-2 (optional follow-up): `AttachFrame`** for tooltip/popover point-to-point attachment if user demand remains high after GuideFrame ships.
11
+
12
+ Why staged: `GuideFrame` directly solves “start after inspector edge” (the most common structural pain), preserves existing `AnchorFrame` simplicity, and keeps dependency behavior isolated in one new frame kind.
13
+
14
+ Core invariant remains explicit and non-negotiable:
15
+
16
+ - **parent = coordinate owner**
17
+ - **reference/guide/target = read-only alignment input**
18
+
19
+ A reference is never ownership, traversal, state inheritance, event ownership, data ownership, or semantic DOM ownership.
20
+
21
+ ## 2) Actual customer/user pain
22
+
23
+ The core pain is **authoring drift**: relationships between nodes are expressed as duplicated constants rather than declarative geometry dependencies.
24
+
25
+ Typical failures today:
26
+
27
+ - “toolbar starts after inspector” encoded as magic numbers (`left: 312`) that break when inspector width changes.
28
+ - tooltip/popover placements manually recomputed in userland and desynchronized from resolved layout.
29
+ - badge/corner alignment repeated across breakpoints with brittle arithmetic.
30
+
31
+ Users are not asking for global solver behavior; they are asking for **read-only edge following** and **targeted attachment** while preserving deterministic layout.
32
+
33
+ ## 3) Non-goals
34
+
35
+ M7b must not introduce:
36
+
37
+ - a general linear/nonlinear constraint system,
38
+ - width/height references to arbitrary nodes (v1),
39
+ - cross-cutting dependency semantics in `AnchorFrame`,
40
+ - ownership ambiguity (multi-parent semantics),
41
+ - adapter-level geometry solving,
42
+ - hidden auto-portals/clipping escapes.
43
+
44
+ ## 4) Existing features that already solve some cases
45
+
46
+ Current primitives already solve many structural layouts:
47
+
48
+ - `StackArrange + FillFrame` for directional allocation,
49
+ - `GridArrange + CellFrame` for regular 2D placement,
50
+ - responsive row variants for breakpoint-specific frame switching,
51
+ - `OffsetSpec` for post-placement nudging,
52
+ - named `layer` + sibling `z` for paint ordering.
53
+
54
+ These do **not** fully solve cross-node edge following for floating/overlay alignment.
55
+
56
+ ## 5) Candidate designs
57
+
58
+ ### Candidate A — Edge refs inside `AnchorFrame`
59
+
60
+ **Verdict: reject for M7b v1.**
61
+
62
+ Pros: direct syntax, no new frame kind.
63
+
64
+ Cons:
65
+
66
+ - pollutes the simplest parent-local frame with graph semantics,
67
+ - mixes “simple anchor math” and “reference dependency” in one type,
68
+ - increases risk of accidental multi-source constraints,
69
+ - weakens mental model and pushes toward solver creep.
70
+
71
+ ### Candidate B — `AttachFrame`
72
+
73
+ **Verdict: good, but secondary.**
74
+
75
+ Excellent for tooltips/popovers/badges; constrained and clear (single target, point mapping). Less natural for one-axis “start after edge” content-region layouts.
76
+
77
+ ### Candidate C — `GuideFrame` / `ReferenceAnchorFrame`
78
+
79
+ **Verdict: recommend for M7b-1.**
80
+
81
+ Best fit for structural edge-following while preserving `AnchorFrame` purity. Can reuse anchor-like axis rules with bounded dependency semantics.
82
+
83
+ ### Candidate D — Named guides
84
+
85
+ **Verdict: defer.**
86
+
87
+ Potentially useful semantic layer, but introduces additional naming/export surface and lookup complexity before baseline semantics are proven.
88
+
89
+ ### Candidate E — Do nothing
90
+
91
+ **Verdict: reject.**
92
+
93
+ Fails legitimate authoring pain for floating and edge-following relationships; forces brittle duplicated arithmetic.
94
+
95
+ ### Additional candidate (F) — “Attach only” in M7b
96
+
97
+ **Verdict: plausible alternative but incomplete.**
98
+
99
+ Safer than Anchor refs, but does not cleanly solve the one-axis “region starts after another region edge” use case.
100
+
101
+ ## 6) Recommended design
102
+
103
+ ### M7b scope recommendation
104
+
105
+ Ship **`GuideFrame` only** in M7b, with strict constraints. Defer `AttachFrame` to M7c unless needed immediately.
106
+
107
+ Rationale:
108
+
109
+ 1. Solves the broadest real pain (`left = target.right + offset`) with low surface area.
110
+ 2. Keeps `AnchorFrame` unchanged and parent-local.
111
+ 3. Limits dependency complexity to one explicit frame kind.
112
+ 4. Avoids overcommitting to two new models in one release.
113
+
114
+ If product requires tooltip-first workflows immediately, an acceptable variant is:
115
+
116
+ - M7b: `GuideFrame`
117
+ - M7b.1: `AttachFrame` (additive, same dependency engine)
118
+
119
+ ## 7) Type proposal
120
+
121
+ ```ts
122
+ export type RectEdge =
123
+ | "left"
124
+ | "right"
125
+ | "top"
126
+ | "bottom"
127
+ | "centerX"
128
+ | "centerY";
129
+
130
+ export type EdgeRef = {
131
+ ref: NodeId;
132
+ edge: RectEdge;
133
+ offset?: UiLength; // resolves on referencing node's parent axis (see semantics)
134
+ };
135
+
136
+ export type GuideLength = UiLength | EdgeRef;
137
+
138
+ export type GuideFrame = {
139
+ kind: "guide";
140
+ left?: GuideLength;
141
+ right?: GuideLength;
142
+ top?: GuideLength;
143
+ bottom?: GuideLength;
144
+ width?: UiLength;
145
+ height?: UiLength;
146
+ };
147
+ ```
148
+
149
+ Validation limits (v1):
150
+
151
+ - Exactly two horizontal constraints from `{ left, right, width }`.
152
+ - Exactly two vertical constraints from `{ top, bottom, height }`.
153
+ - `width/height` are `UiLength` only (no refs).
154
+ - Horizontal keys (`left/right`) may reference only `left/right/centerX`.
155
+ - Vertical keys (`top/bottom`) may reference only `top/bottom/centerY`.
156
+ - **At most one `EdgeRef` per axis** (horizontal max 1, vertical max 1).
157
+ - Self-reference forbidden (`ref === current node id`).
158
+
159
+ (Explicitly no `AnchorFrame` changes in v1.)
160
+
161
+ ## 8) Semantics
162
+
163
+ ### Ownership semantics
164
+
165
+ - Logical parent still owns coordinate system and hierarchy.
166
+ - Reference target is geometry input only.
167
+
168
+ ### Coordinate space
169
+
170
+ - Resolved rectangles remain global/root-space.
171
+ - `EdgeRef` reads target edge in global/root-space.
172
+ - `UiLength` inside `EdgeRef.offset` resolves against the **referencing node’s logical parent axis**:
173
+ - horizontal edge refs use parent width axis,
174
+ - vertical edge refs use parent height axis.
175
+
176
+ Reason: keeps `ui` behavior consistent with existing parent-owned coordinate semantics.
177
+
178
+ ### Placement semantics
179
+
180
+ - Compute numeric values for each present field:
181
+ - If field is `UiLength`, resolve in parent axis as today.
182
+ - If field is `EdgeRef`, evaluate target edge in global space and add resolved `offset` (default 0).
183
+ - Solve axis exactly like anchor math once concrete numbers are known.
184
+ - Apply node-level `offset` via existing post-placement path (`applyOffset`) after frame solve.
185
+ - Final rect may lie partially/fully outside logical parent (allowed).
186
+
187
+ ## 9) Dependency model
188
+
189
+ Use a **restricted dependency pass for reference frames only**.
190
+
191
+ Dependencies for a `GuideFrame` node:
192
+
193
+ - its logical parent must be resolved,
194
+ - each referenced target node must be resolved.
195
+
196
+ Algorithm:
197
+
198
+ 1. Resolve all non-guide nodes via existing traversal path.
199
+ 2. Collect unresolved guide nodes.
200
+ 3. Iteratively resolve guide nodes whose dependencies are satisfied.
201
+ 4. Repeat until no progress.
202
+ 5. Remaining unresolved guide nodes -> classify as cycle vs missing/unresolvable target.
203
+
204
+ This is not a general solver:
205
+
206
+ - no symbolic equation balancing,
207
+ - no iterative numeric relaxation,
208
+ - no width/height references,
209
+ - bounded, acyclic dependency requirement.
210
+
211
+ ## 10) Validation/error model
212
+
213
+ Add explicit error codes (separate from parent-cycle errors):
214
+
215
+ - `GuideTargetNotFound`
216
+ - `GuideSelfReference`
217
+ - `GuideReferenceCycle`
218
+ - `GuideInvalidEdgeForAxis`
219
+ - `GuideTooManyReferencesPerAxis`
220
+ - `InvalidGuideFrame`
221
+ - `GuideTargetUnresolved`
222
+
223
+ Notes:
224
+
225
+ - Parent graph cycle remains existing parent-cycle error.
226
+ - Reference-cycle is distinct and must report involved node ids.
227
+ - `GuideTargetUnresolved` covers cases where target exists but cannot resolve due to its own invalid frame/dependency chain.
228
+
229
+ ## 11) Interaction with existing features
230
+
231
+ - **StackArrange:** direct stack children remain `FixedFrame`/`FillFrame` only. `GuideFrame` as direct stack child is invalid (reuse existing stack child-frame validation path).
232
+ - **GridArrange:** direct grid children remain `CellFrame` only. `GuideFrame` as direct grid child invalid (reuse existing grid child-frame validation path).
233
+ - **Responsive variants:** variant selection occurs first; whichever frame is selected is validated/resolved. Switching between `anchor` and `guide` is supported.
234
+ - **OffsetSpec:** applied after guide placement (single source of post-placement nudge).
235
+ - **Named layers:** paint order only; no impact on guide geometry.
236
+ - **Future portals:** unaffected; references do not imply reparenting.
237
+ - **`lerpResolvedLayouts`:** unchanged; interpolates resolved rectangles regardless of source frame kind.
238
+ - **React adapter:** no geometry logic changes required; existing parent-local normalization remains.
239
+
240
+ ## 12) Examples
241
+
242
+ ### A) Start after inspector edge
243
+
244
+ ```ts
245
+ {
246
+ id: "main-toolbar",
247
+ parent: "root",
248
+ frame: {
249
+ kind: "guide",
250
+ left: { ref: "inspector", edge: "right", offset: 8 },
251
+ right: 16,
252
+ top: 16,
253
+ height: 48,
254
+ },
255
+ }
256
+ ```
257
+
258
+ ### B) Tooltip below button (with node-level offset)
259
+
260
+ Using `GuideFrame` approximation in M7b (true point attachment deferred):
261
+
262
+ ```ts
263
+ {
264
+ id: "help-tooltip",
265
+ parent: "overlay-root",
266
+ frame: {
267
+ kind: "guide",
268
+ left: { ref: "help-button", edge: "left" },
269
+ width: 220,
270
+ top: { ref: "help-button", edge: "bottom" },
271
+ height: 64,
272
+ },
273
+ offset: { x: 0, y: 6 },
274
+ }
275
+ ```
276
+
277
+ ### C) Badge at card corner
278
+
279
+ ```ts
280
+ {
281
+ id: "card-badge",
282
+ parent: "root",
283
+ frame: {
284
+ kind: "guide",
285
+ right: { ref: "card-42", edge: "right" },
286
+ width: 18,
287
+ top: { ref: "card-42", edge: "top" },
288
+ height: 18,
289
+ },
290
+ offset: { x: 6, y: -6 },
291
+ }
292
+ ```
293
+
294
+ ### D) Responsive variant
295
+
296
+ ```ts
297
+ {
298
+ id: "context-toolbar",
299
+ parent: "root",
300
+ frame: { kind: "anchor", left: 16, right: 16, top: 72, height: 44 },
301
+ variants: [
302
+ {
303
+ when: { minWidth: 1024 },
304
+ frame: {
305
+ kind: "guide",
306
+ left: { ref: "inspector", edge: "right", offset: { unit: "ui", value: 0.01 } },
307
+ right: 16,
308
+ top: 16,
309
+ height: 44,
310
+ },
311
+ },
312
+ ],
313
+ }
314
+ ```
315
+
316
+ ## 13) M7b implementation plan
317
+
318
+ 1. Add `GuideFrame`, `RectEdge`, `EdgeRef` types (public API surface only as contract defines).
319
+ 2. Add validation rules and new error codes for guide semantics.
320
+ 3. Add edge-evaluation helper (`getRectEdgeValue`) + `GuideLength` resolver.
321
+ 4. Extend document resolver with restricted guide dependency pass.
322
+ 5. Keep `resolveFrame` behavior explicit:
323
+ - either reject `guide` in direct frame resolver with explicit guard,
324
+ - or route through document-level resolver path only.
325
+ 6. Ensure resolved output schema unchanged (flat rectangles + existing metadata).
326
+ 7. Ensure variant selection precedes guide resolution.
327
+ 8. Keep adapter untouched.
328
+ 9. Add docs (runtime guide in M7b) and migration notes.
329
+
330
+ ## 14) M7b test plan
331
+
332
+ Planned tests:
333
+
334
+ - API/type visibility for new guide types.
335
+ - Basic guide horizontal/vertical placement.
336
+ - One-axis relation: `left = target.right + offset` with parent-local top/height.
337
+ - Multiple targets across axes (one per axis).
338
+ - Missing target (`GuideTargetNotFound`).
339
+ - Self-reference (`GuideSelfReference`).
340
+ - Reference cycle (`GuideReferenceCycle`).
341
+ - Target exists but unresolved (`GuideTargetUnresolved`).
342
+ - Invalid edge axis mapping (`GuideInvalidEdgeForAxis`).
343
+ - Too many refs per axis (`GuideTooManyReferencesPerAxis`).
344
+ - Stack direct-child rejection.
345
+ - Grid direct-child rejection.
346
+ - Responsive variant selecting guide frame.
347
+ - Node-level offset applies after guide solve.
348
+ - Negative offsets allowed.
349
+ - Rect outside parent allowed.
350
+ - Resolved tree/flatten metadata preserved.
351
+ - `lerpResolvedLayouts` compatibility.
352
+ - Input immutability.
353
+
354
+ ## 15) Risks and mitigations
355
+
356
+ 1. **Solver creep risk**
357
+ - Mitigation: separate `GuideFrame`, strict axis rules, no size refs, bounded refs.
358
+
359
+ 2. **One-parent model erosion**
360
+ - Mitigation: explicit invariant in docs/errors: parent owns coordinates; reference is read-only geometry input.
361
+
362
+ 3. **Hidden graph coupling**
363
+ - Mitigation: explicit target ids, explicit errors, bounded dependency pass, no implicit nearest-node lookup.
364
+
365
+ 4. **Cycles/unresolvable chains**
366
+ - Mitigation: dedicated cycle detection and error codes distinct from parent cycles.
367
+
368
+ 5. **Clipping/layer confusion**
369
+ - Mitigation: docs clarify layers affect paint order only; guide does not portal or alter clipping.
370
+
371
+ 6. **AnchorFrame complexity regression**
372
+ - Mitigation: no anchor reference support in v1.
373
+
374
+ ---
375
+
376
+ ## Decision record
377
+
378
+ - **M7b recommendation:** `GuideFrame` only.
379
+ - **Deferred:** `AttachFrame` and named guides.
380
+ - **Invariant preserved:** one parent per node; references are read-only alignment inputs.
381
+
382
+
383
+ ## M7b runtime status
384
+ GuideFrame runtime dependency resolution, validation, and errors are implemented in core resolver.