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.
- package/README.md +295 -49
- package/dist/chunk-2ZQ2RFFI.js +400 -0
- package/dist/chunk-33CKBEJH.js +186 -0
- package/dist/chunk-BJOQRPPX.js +382 -0
- package/dist/chunk-KYWOCAHK.js +205 -0
- package/dist/chunk-RJYRJ3LD.js +0 -0
- package/dist/chunk-SVWYWI7I.js +59 -0
- package/dist/chunk-VREK57S3.js +13 -0
- package/dist/chunk-ZVDE7PX4.js +222 -0
- package/dist/debugOverlay-pJpj0n5H.d.ts +125 -0
- package/dist/deus/index.d.ts +14 -0
- package/dist/deus/index.js +26 -0
- package/dist/dispatch/index.d.ts +49 -0
- package/dist/dispatch/index.js +217 -0
- package/dist/handoff/index.d.ts +44 -0
- package/dist/handoff/index.js +83 -0
- package/dist/index.d.ts +54 -236
- package/dist/index.js +753 -583
- package/dist/inspect/index.d.ts +8 -0
- package/dist/inspect/index.js +97 -0
- package/dist/react/index.d.ts +41 -0
- package/dist/react/index.js +9 -0
- package/dist/react-native/index.d.ts +30 -0
- package/dist/react-native/index.js +84 -0
- package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
- package/dist/text/index.d.ts +10 -0
- package/dist/text/index.js +9 -0
- package/dist/text/react/index.d.ts +14 -0
- package/dist/text/react/index.js +7 -0
- package/dist/text/react-native/index.d.ts +16 -0
- package/dist/text/react-native/index.js +155 -0
- package/dist/text/vue/index.d.ts +113 -0
- package/dist/text/vue/index.js +202 -0
- package/dist/types-B90jb3RW.d.ts +184 -0
- package/dist/types-C4poVJpR.d.ts +74 -0
- package/dist/types-DLYAhNXw.d.ts +32 -0
- package/dist/vue/index.d.ts +173 -0
- package/dist/vue/index.js +112 -0
- package/docs/adapter-packaging-a0-plan.md +352 -0
- package/docs/adapters.md +19 -0
- package/docs/api-coherence-m8-audit.md +397 -0
- package/docs/deusmachina.md +108 -0
- package/docs/error-codes.md +95 -0
- package/docs/grid-arrange-m5a-contract.md +480 -0
- package/docs/grid-arrange.md +51 -0
- package/docs/inspection-and-handoff.md +126 -0
- package/docs/layout-interpolation.md +52 -0
- package/docs/machina-dispatch-d0-contract.md +496 -0
- package/docs/machina-dispatch.md +143 -0
- package/docs/named-layers.md +40 -0
- package/docs/react-adapter.md +63 -58
- package/docs/react-native-adapter.md +56 -0
- package/docs/react-native-text-renderer.md +50 -0
- package/docs/reference-alignment-m7a-contract.md +384 -0
- package/docs/reference-alignment.md +44 -0
- package/docs/responsive-variants.md +54 -0
- package/docs/screen-catalog-and-viewports.md +124 -0
- package/docs/stack-geometry-helpers.md +115 -0
- package/docs/vue-adapter.md +55 -0
- package/docs/vue-text-renderer.md +55 -0
- package/package.json +127 -60
package/docs/react-adapter.md
CHANGED
|
@@ -1,26 +1,50 @@
|
|
|
1
|
-
# React
|
|
1
|
+
# React adapter (`machinalayout/react`)
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Shared model boundary
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
- The adapter
|
|
8
|
-
-
|
|
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 {
|
|
14
|
+
import { resolveLayoutRows } from "machinalayout";
|
|
15
|
+
import { MachinaReactView } from "machinalayout/react";
|
|
14
16
|
|
|
15
|
-
const
|
|
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
|
-
|
|
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
|
|
64
|
+
Core resolved rectangles are root-space coordinates.
|
|
38
65
|
|
|
39
|
-
The
|
|
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
|
|
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
|
-
|
|
73
|
+
## DOM renderer policy boundary
|
|
55
74
|
|
|
56
|
-
-
|
|
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
|
|
84
|
+
- optional debug/cosmetic wrapper styles
|
|
70
85
|
|
|
71
|
-
Not
|
|
86
|
+
Not used as geometry authority:
|
|
72
87
|
|
|
73
|
-
- flexbox
|
|
74
|
-
-
|
|
75
|
-
-
|
|
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
|
-
|
|
92
|
+
React components render payload UI inside adapter-owned rectangles; React does not own outer layout geometry.
|
|
81
93
|
|
|
82
|
-
##
|
|
94
|
+
## Inspection handoff surface
|
|
83
95
|
|
|
84
|
-
`
|
|
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
|
-
|
|
98
|
+
## Debug overlay
|
|
87
99
|
|
|
88
|
-
|
|
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={
|
|
101
|
-
|
|
102
|
-
viewData={{ Inspector: { sidebarLeft } }}
|
|
104
|
+
layout={layout}
|
|
105
|
+
debugOverlay={{ mode: "nonInteractiveOverlay", labels: true, borders: true }}
|
|
103
106
|
/>
|
|
104
107
|
```
|
|
105
108
|
|
|
106
|
-
|
|
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
|
-
- `
|
|
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.
|