@archie/devtools 0.0.26 → 0.0.27
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/dist/chunk-4VGQJ3PW.mjs +577 -0
- package/dist/client/client.js +22 -27
- package/dist/client/client.mjs +22 -27
- package/dist/collection-binding.d.mts +156 -0
- package/dist/collection-binding.d.ts +156 -0
- package/dist/collection-binding.js +553 -0
- package/dist/collection-binding.mjs +521 -0
- package/dist/index.js +9 -10
- package/dist/index.mjs +7 -575
- package/dist/inspector.standalone.js +232 -7
- package/package.json +6 -1
package/dist/client/client.mjs
CHANGED
|
@@ -4063,6 +4063,19 @@ function dispatchRuntimeEvent(detail) {
|
|
|
4063
4063
|
if (typeof window === "undefined") return;
|
|
4064
4064
|
window.dispatchEvent(new CustomEvent(DND_RUNTIME_EVENT, { detail }));
|
|
4065
4065
|
}
|
|
4066
|
+
function resolveRowBinding(el) {
|
|
4067
|
+
let node = el;
|
|
4068
|
+
for (let depth = 0; node && depth < MAX_ROW_BINDING_ANCESTOR_DEPTH; depth += 1) {
|
|
4069
|
+
const rowId = node.getAttribute("data-agent-row");
|
|
4070
|
+
if (rowId !== null && rowId !== "") {
|
|
4071
|
+
const entity = node.getAttribute("data-agent-entity");
|
|
4072
|
+
return entity ? { entity, rowId } : null;
|
|
4073
|
+
}
|
|
4074
|
+
node = node.parentElement;
|
|
4075
|
+
}
|
|
4076
|
+
return null;
|
|
4077
|
+
}
|
|
4078
|
+
var MAX_ROW_BINDING_ANCESTOR_DEPTH = 12;
|
|
4066
4079
|
function mergeInspectorTheme(theme) {
|
|
4067
4080
|
return {
|
|
4068
4081
|
...DEFAULT_INSPECTOR_THEME,
|
|
@@ -4449,16 +4462,6 @@ function createCommitLaneIdentitySnapshot(params) {
|
|
|
4449
4462
|
committable: true
|
|
4450
4463
|
};
|
|
4451
4464
|
}
|
|
4452
|
-
function hasSafeOccurrenceIdentities(lane, duplicateNodeKey) {
|
|
4453
|
-
const effectiveNodeIds = lane.commitNodeIds.filter(
|
|
4454
|
-
(commitNodeId) => lane.nodeKeysByCommitNodeId.get(commitNodeId) === duplicateNodeKey
|
|
4455
|
-
).map(
|
|
4456
|
-
(commitNodeId) => lane.effectiveNodeIdsByCommitNodeId.get(commitNodeId) ?? null
|
|
4457
|
-
);
|
|
4458
|
-
return effectiveNodeIds.length > 1 && effectiveNodeIds.every(
|
|
4459
|
-
(effectiveNodeId) => typeof effectiveNodeId === "string" && effectiveNodeId.length > 0
|
|
4460
|
-
) && new Set(effectiveNodeIds).size === effectiveNodeIds.length;
|
|
4461
|
-
}
|
|
4462
4465
|
function buildLanePersistenceDiagnostics(params) {
|
|
4463
4466
|
const duplicateNodeKeys = detectDuplicateSourceBackedNodeKeys(
|
|
4464
4467
|
buildNodeKeysFromCommitIds(
|
|
@@ -4469,24 +4472,10 @@ function buildLanePersistenceDiagnostics(params) {
|
|
|
4469
4472
|
if (duplicateNodeKeys.length === 0) {
|
|
4470
4473
|
return null;
|
|
4471
4474
|
}
|
|
4472
|
-
const safeDuplicateNodeKeys = duplicateNodeKeys.filter(
|
|
4473
|
-
(nodeKey) => hasSafeOccurrenceIdentities(params.lane, nodeKey)
|
|
4474
|
-
);
|
|
4475
|
-
if (safeDuplicateNodeKeys.length === duplicateNodeKeys.length) {
|
|
4476
|
-
return {
|
|
4477
|
-
notes: safeDuplicateNodeKeys.map(
|
|
4478
|
-
(nodeKey) => `Arrange lane ${params.lane.laneContainerId} repeats source-backed nodeKey ${nodeKey}, but runtime assigned explicit occurrence identities for each commit root.`
|
|
4479
|
-
)
|
|
4480
|
-
};
|
|
4481
|
-
}
|
|
4482
4475
|
return {
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
),
|
|
4487
|
-
notes: [
|
|
4488
|
-
`Lane ${params.lane.laneContainerId} duplicates source-backed nodeKeys: ${duplicateNodeKeys.join(", ")}`
|
|
4489
|
-
]
|
|
4476
|
+
notes: duplicateNodeKeys.map(
|
|
4477
|
+
(nodeKey) => `Arrange lane ${params.lane.laneContainerId} repeats source-backed nodeKey ${nodeKey}, but runtime assigned explicit occurrence identities for each commit root.`
|
|
4478
|
+
)
|
|
4490
4479
|
};
|
|
4491
4480
|
}
|
|
4492
4481
|
function removeCommitNodeId(commitNodeIds, commitNodeId) {
|
|
@@ -4701,6 +4690,7 @@ var useDesignModeStore = create((set, get) => ({
|
|
|
4701
4690
|
beforeSibling,
|
|
4702
4691
|
afterSibling,
|
|
4703
4692
|
diagnostics,
|
|
4693
|
+
binding,
|
|
4704
4694
|
componentName,
|
|
4705
4695
|
before,
|
|
4706
4696
|
after
|
|
@@ -4727,6 +4717,7 @@ var useDesignModeStore = create((set, get) => ({
|
|
|
4727
4717
|
beforeSibling: beforeSibling ?? null,
|
|
4728
4718
|
afterSibling: afterSibling ?? null,
|
|
4729
4719
|
diagnostics: diagnostics ?? null,
|
|
4720
|
+
binding: binding ?? null,
|
|
4730
4721
|
payload: { before, after },
|
|
4731
4722
|
at: now
|
|
4732
4723
|
};
|
|
@@ -6416,6 +6407,8 @@ function serializeArrangeChange(event, index) {
|
|
|
6416
6407
|
sourceBeforeSibling: event.kind === "move" ? event.sourceBeforeSibling : void 0,
|
|
6417
6408
|
sourceAfterSibling: event.kind === "move" ? event.sourceAfterSibling : void 0,
|
|
6418
6409
|
diagnostics: event.kind === "move" ? event.diagnostics : void 0,
|
|
6410
|
+
// ARCH-1065 D17 — which slice row this move addresses.
|
|
6411
|
+
binding: event.kind === "move" ? event.binding : void 0,
|
|
6419
6412
|
// Insert-specific fields
|
|
6420
6413
|
displayName: event.kind === "insert" ? event.displayName : void 0,
|
|
6421
6414
|
html: event.kind === "insert" ? event.html : void 0,
|
|
@@ -7946,6 +7939,7 @@ function recordCommittedMove(args) {
|
|
|
7946
7939
|
});
|
|
7947
7940
|
const componentName = getEditorMeta(anchorEl)?.componentName;
|
|
7948
7941
|
const operationId = `${command.at}:${anchorNodeId}`;
|
|
7942
|
+
const binding = resolveRowBinding(selectedEl);
|
|
7949
7943
|
const beforePayload = buildMovePayloadWithEffectiveIdentities({
|
|
7950
7944
|
placement: command.from,
|
|
7951
7945
|
containerIdentity: sourceContainerIdentity,
|
|
@@ -7975,6 +7969,7 @@ function recordCommittedMove(args) {
|
|
|
7975
7969
|
sourceBeforeSibling: sourceBeforeSiblingIdentity,
|
|
7976
7970
|
sourceAfterSibling: sourceAfterSiblingIdentity,
|
|
7977
7971
|
diagnostics,
|
|
7972
|
+
binding,
|
|
7978
7973
|
container: containerIdentity,
|
|
7979
7974
|
beforeSibling: beforeSiblingIdentity,
|
|
7980
7975
|
afterSibling: afterSiblingIdentity,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { types, PluginObj } from '@babel/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* babel-plugin-collection-binding (ARCH-1065 M2)
|
|
5
|
+
*
|
|
6
|
+
* Build-time codemod that stamps the DATA-BINDING GRAMMAR (ARCH-1065 design
|
|
7
|
+
* v2, D1) onto `.map()` collections whose source statically resolves back to
|
|
8
|
+
* the screen's slice — so the runtime inspector (M3, `window.__slice`) and the
|
|
9
|
+
* kit editing surfaces can offer per-row, per-field edits over bespoke
|
|
10
|
+
* (non-kit-component) rendered collections, the same way hand-written kit
|
|
11
|
+
* components (M1) already do.
|
|
12
|
+
*
|
|
13
|
+
* Applied in kit `build-d.mjs` to `s.source` BEFORE the SSR + client
|
|
14
|
+
* transforms, so the stamped attributes are present in both the SSR HTML and
|
|
15
|
+
* the hydrated client tree (hydration-safe by construction — nothing here
|
|
16
|
+
* reads the DOM or depends on a render pass).
|
|
17
|
+
*
|
|
18
|
+
* ── Grammar (D1) ──────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* data-agent-entity static string, on the row root
|
|
21
|
+
* data-agent-row {String(param?.id)}, on the row root
|
|
22
|
+
* data-agent-field static string OR a runtime expression
|
|
23
|
+
* (??/ternary field selection, D5.5)
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
+
* data-agent-field-parts comma-separated field list (mixed leaves)
|
|
26
|
+
* data-agent-field-slot text-node index of the dynamic part
|
|
27
|
+
* (label+value leaves, D6)
|
|
28
|
+
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
29
|
+
*
|
|
30
|
+
* ── What counts as a slice source ────────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* `slice.<Entity>` or `slice['Entity Name']` / `slice["Entity Name"]`
|
|
33
|
+
* (both quote styles — Babel normalizes both to the same StringLiteral
|
|
34
|
+
* value, entities may contain spaces), threaded through any combination of
|
|
35
|
+
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
|
+
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
|
+
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
+
* `React.useMemo(() => …, deps)` unwrap (the common `() => expr` shape only —
|
|
39
|
+
* see `resolveSliceSource`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
|
+
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
|
+
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
|
+
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
43
|
+
* binding entered scope (prop, destructure, import), only that its name at
|
|
44
|
+
* the point of use is literally `slice`.
|
|
45
|
+
*
|
|
46
|
+
* ── Row identity ──────────────────────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* The `.map()` callback's first parameter is either a bare identifier (the
|
|
49
|
+
* whole row object — `data-agent-row` becomes `String(<param>?.id)`) or an
|
|
50
|
+
* ObjectPattern destructuring it (`data-agent-row` becomes `String(<local>)`
|
|
51
|
+
* using whichever local name the pattern bound the `id` key to). A
|
|
52
|
+
* destructured pattern that never binds `id` cannot form a row identity, so
|
|
53
|
+
* the ENTIRE map is left unstamped — row identity is foundational; there is
|
|
54
|
+
* no such thing as a field-only stamp without it.
|
|
55
|
+
*
|
|
56
|
+
* ── Row-component projection (D5.4) ─────────────────────────────────────
|
|
57
|
+
*
|
|
58
|
+
* `{list.map(e => <EventCard event={e} />)}` with a MODULE-LOCAL
|
|
59
|
+
* `function EventCard({ event }) { … }` co-located in the same source file
|
|
60
|
+
* (kit `build-d.mjs` hands this plugin the whole page/screen source, so
|
|
61
|
+
* screen-local helper components live in the same AST) resolves ACROSS that
|
|
62
|
+
* boundary: the row root is stamped on EventCard's OWN returned host
|
|
63
|
+
* element, using EventCard's own local parameter name for row-id and field
|
|
64
|
+
* resolution. The row-root projection specifically requires the WHOLE mapped
|
|
65
|
+
* item to be forwarded through one prop, verbatim (`event={e}`) — a partial
|
|
66
|
+
* field forward at that point cannot carry row identity, so the projection
|
|
67
|
+
* (and with it the whole map) fails closed.
|
|
68
|
+
*
|
|
69
|
+
* The SAME projection mechanism runs recursively for NESTED leaf components
|
|
70
|
+
* inside an already-established row (`<StatusBadge status={a.status} />`):
|
|
71
|
+
* there it is permissive about WHICH fields are forwarded (whole item OR a
|
|
72
|
+
* specific field OR several), because no row identity is at stake — only
|
|
73
|
+
* further field leaves.
|
|
74
|
+
*
|
|
75
|
+
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
|
+
*
|
|
77
|
+
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
78
|
+
* same purity classifier both plugins must agree on) to find LEAF elements:
|
|
79
|
+
* JSX elements whose only meaningful children are text/expressions, never
|
|
80
|
+
* nested JSX elements. A leaf's dynamic part(s) classify as:
|
|
81
|
+
*
|
|
82
|
+
* - a bare member expression off a "whole row object" binding
|
|
83
|
+
* (`a.title`) → static field, UNLESS the member is `id` (id is NEVER
|
|
84
|
+
* stamped as a field — it already carries the row identity);
|
|
85
|
+
* - `a.x ?? a.y [?? …]` (an N-part nullish chain of member fields) →
|
|
86
|
+
* a RUNTIME field-selector expression mirroring the chain
|
|
87
|
+
* (`a.x != null ? "x" : a.y != null ? "y" : "z"`), or, when the chain
|
|
88
|
+
* ends in a static literal fallback, a plain static field;
|
|
89
|
+
* - `a.x || 'fallback'` → static field (literal-fallback only; `||`
|
|
90
|
+
* does not get the N-part runtime treatment `??` does);
|
|
91
|
+
* - `cond ? a.x : a.y` (both branches member fields of the row object) →
|
|
92
|
+
* a RUNTIME field-selector expression duplicating `cond` verbatim
|
|
93
|
+
* (`cond ? "x" : "y"`) — CONSTRAINED (C3): `cond` must be CALL-FREE
|
|
94
|
+
* (checked recursively over the whole test subtree); a call anywhere in
|
|
95
|
+
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
96
|
+
* so the whole leaf is left unstamped instead;
|
|
97
|
+
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
98
|
+
* member (a boolean-typed field driving two literal branches) → static
|
|
99
|
+
* field named by `cond` — the FE's typed controls (D7, a different
|
|
100
|
+
* mechanism, M3) read the boolean's true/false rendering from
|
|
101
|
+
* `window.__slice`, not from this attribute;
|
|
102
|
+
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
103
|
+
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
104
|
+
* row-scoped data, but which field is unknown" signal — never silent).
|
|
105
|
+
*
|
|
106
|
+
* A leaf with exactly one meaningful child (no surrounding text) gets a bare
|
|
107
|
+
* `data-agent-field`. A leaf with 2+ meaningful children (label + value,
|
|
108
|
+
* D6) where EXACTLY ONE is dynamic gets the same `data-agent-field` PLUS
|
|
109
|
+
* `data-agent-field-slot`: the dynamic part's 0-based index among the
|
|
110
|
+
* element's own meaningful children. This is deliberately equal to its
|
|
111
|
+
* text-node index in the eventual SSR/hydrated DOM: every meaningful JSXText
|
|
112
|
+
* or JSXExpressionContainer child renders as exactly one text node (React 18
|
|
113
|
+
* inserts `<!-- -->` comment separators BETWEEN adjacent text-producing
|
|
114
|
+
* outputs to keep them independently locatable during hydration, but those
|
|
115
|
+
* comments are not text nodes and do not shift text-node indices). A leaf
|
|
116
|
+
* with 2+ dynamic parts that all resolve to STATIC fields with more than one
|
|
117
|
+
* DISTINCT field name gets `data-agent-field-state="mixed"` plus
|
|
118
|
+
* `data-agent-field-parts` (a comma-separated, source-order field list); any
|
|
119
|
+
* other multi-dynamic-part shape (an unresolvable part, or every part naming
|
|
120
|
+
* the same field) is left unstamped.
|
|
121
|
+
*
|
|
122
|
+
* Recursion into a leaf's structural siblings stops at the nearest NESTED
|
|
123
|
+
* `.map()` collection (a JSXExpressionContainer wrapping a call, not a JSX
|
|
124
|
+
* element child, so it is never a "structural child" this walker descends
|
|
125
|
+
* into) — the innermost-stamped-param rule: a field leaf only ever binds
|
|
126
|
+
* against its nearest enclosing map's own parameter. A nested collection
|
|
127
|
+
* that is itself slice-rooted gets discovered and stamped independently by
|
|
128
|
+
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
129
|
+
*
|
|
130
|
+
* ── Failure posture ─────────────────────────────────────────────────────
|
|
131
|
+
*
|
|
132
|
+
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
133
|
+
* not sure about degrades to NO STAMP, never a guessed one — a fragment or
|
|
134
|
+
* conditional expression at the callback's JSX root, an unresolvable
|
|
135
|
+
* component usage, a non-slice source, an ObjectPattern without an `id` key,
|
|
136
|
+
* a call inside a duplicated ternary test, ambiguous control flow (2+ return
|
|
137
|
+
* statements) — all leave the subtree untouched.
|
|
138
|
+
*
|
|
139
|
+
* ── Idempotence ─────────────────────────────────────────────────────────
|
|
140
|
+
*
|
|
141
|
+
* The plugin never rewrites SOURCE (D15) and never touches an existing
|
|
142
|
+
* attribute: every stamp is guarded by a same-name attribute-presence check,
|
|
143
|
+
* and a row root that already carries `data-agent-entity` short-circuits the
|
|
144
|
+
* whole map (no re-resolution, no field re-walk) — running the plugin twice
|
|
145
|
+
* produces byte-identical output.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
interface CollectionBindingPluginOptions {
|
|
149
|
+
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
|
+
fileExclude?: RegExp;
|
|
151
|
+
}
|
|
152
|
+
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
|
+
types: typeof types;
|
|
154
|
+
}, opts?: CollectionBindingPluginOptions): PluginObj;
|
|
155
|
+
|
|
156
|
+
export { type CollectionBindingPluginOptions, babelPluginCollectionBinding };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { types, PluginObj } from '@babel/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* babel-plugin-collection-binding (ARCH-1065 M2)
|
|
5
|
+
*
|
|
6
|
+
* Build-time codemod that stamps the DATA-BINDING GRAMMAR (ARCH-1065 design
|
|
7
|
+
* v2, D1) onto `.map()` collections whose source statically resolves back to
|
|
8
|
+
* the screen's slice — so the runtime inspector (M3, `window.__slice`) and the
|
|
9
|
+
* kit editing surfaces can offer per-row, per-field edits over bespoke
|
|
10
|
+
* (non-kit-component) rendered collections, the same way hand-written kit
|
|
11
|
+
* components (M1) already do.
|
|
12
|
+
*
|
|
13
|
+
* Applied in kit `build-d.mjs` to `s.source` BEFORE the SSR + client
|
|
14
|
+
* transforms, so the stamped attributes are present in both the SSR HTML and
|
|
15
|
+
* the hydrated client tree (hydration-safe by construction — nothing here
|
|
16
|
+
* reads the DOM or depends on a render pass).
|
|
17
|
+
*
|
|
18
|
+
* ── Grammar (D1) ──────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* data-agent-entity static string, on the row root
|
|
21
|
+
* data-agent-row {String(param?.id)}, on the row root
|
|
22
|
+
* data-agent-field static string OR a runtime expression
|
|
23
|
+
* (??/ternary field selection, D5.5)
|
|
24
|
+
* data-agent-field-state "mixed" | "unresolved"
|
|
25
|
+
* data-agent-field-parts comma-separated field list (mixed leaves)
|
|
26
|
+
* data-agent-field-slot text-node index of the dynamic part
|
|
27
|
+
* (label+value leaves, D6)
|
|
28
|
+
* data-agent-collection-order "stable" | "derived" (D18), on the row root
|
|
29
|
+
*
|
|
30
|
+
* ── What counts as a slice source ────────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* `slice.<Entity>` or `slice['Entity Name']` / `slice["Entity Name"]`
|
|
33
|
+
* (both quote styles — Babel normalizes both to the same StringLiteral
|
|
34
|
+
* value, entities may contain spaces), threaded through any combination of
|
|
35
|
+
* `.filter()/.slice()/.sort()/.map()` chain calls, through single-assignment
|
|
36
|
+
* (`const`, never reassigned) local-variable hops (bounded to
|
|
37
|
+
* `MAX_SOURCE_CHAIN_HOPS`), and through a `useMemo(() => …, deps)` /
|
|
38
|
+
* `React.useMemo(() => …, deps)` unwrap (the common `() => expr` shape only —
|
|
39
|
+
* see `resolveSliceSource`). A `.sort(` ANYWHERE in the chain marks the
|
|
40
|
+
* collection `data-agent-collection-order="derived"`; otherwise `"stable"`
|
|
41
|
+
* (D18). The identifier root must be exactly `slice` — this mirrors the
|
|
42
|
+
* runtime's `window.__slice` (M3); the codemod does not trace how a `slice`
|
|
43
|
+
* binding entered scope (prop, destructure, import), only that its name at
|
|
44
|
+
* the point of use is literally `slice`.
|
|
45
|
+
*
|
|
46
|
+
* ── Row identity ──────────────────────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* The `.map()` callback's first parameter is either a bare identifier (the
|
|
49
|
+
* whole row object — `data-agent-row` becomes `String(<param>?.id)`) or an
|
|
50
|
+
* ObjectPattern destructuring it (`data-agent-row` becomes `String(<local>)`
|
|
51
|
+
* using whichever local name the pattern bound the `id` key to). A
|
|
52
|
+
* destructured pattern that never binds `id` cannot form a row identity, so
|
|
53
|
+
* the ENTIRE map is left unstamped — row identity is foundational; there is
|
|
54
|
+
* no such thing as a field-only stamp without it.
|
|
55
|
+
*
|
|
56
|
+
* ── Row-component projection (D5.4) ─────────────────────────────────────
|
|
57
|
+
*
|
|
58
|
+
* `{list.map(e => <EventCard event={e} />)}` with a MODULE-LOCAL
|
|
59
|
+
* `function EventCard({ event }) { … }` co-located in the same source file
|
|
60
|
+
* (kit `build-d.mjs` hands this plugin the whole page/screen source, so
|
|
61
|
+
* screen-local helper components live in the same AST) resolves ACROSS that
|
|
62
|
+
* boundary: the row root is stamped on EventCard's OWN returned host
|
|
63
|
+
* element, using EventCard's own local parameter name for row-id and field
|
|
64
|
+
* resolution. The row-root projection specifically requires the WHOLE mapped
|
|
65
|
+
* item to be forwarded through one prop, verbatim (`event={e}`) — a partial
|
|
66
|
+
* field forward at that point cannot carry row identity, so the projection
|
|
67
|
+
* (and with it the whole map) fails closed.
|
|
68
|
+
*
|
|
69
|
+
* The SAME projection mechanism runs recursively for NESTED leaf components
|
|
70
|
+
* inside an already-established row (`<StatusBadge status={a.status} />`):
|
|
71
|
+
* there it is permissive about WHICH fields are forwarded (whole item OR a
|
|
72
|
+
* specific field OR several), because no row identity is at stake — only
|
|
73
|
+
* further field leaves.
|
|
74
|
+
*
|
|
75
|
+
* ── Field resolution ────────────────────────────────────────────────────
|
|
76
|
+
*
|
|
77
|
+
* Reuses `meaningfulChildren` from `babel-plugin-editor-meta.ts` (the exact
|
|
78
|
+
* same purity classifier both plugins must agree on) to find LEAF elements:
|
|
79
|
+
* JSX elements whose only meaningful children are text/expressions, never
|
|
80
|
+
* nested JSX elements. A leaf's dynamic part(s) classify as:
|
|
81
|
+
*
|
|
82
|
+
* - a bare member expression off a "whole row object" binding
|
|
83
|
+
* (`a.title`) → static field, UNLESS the member is `id` (id is NEVER
|
|
84
|
+
* stamped as a field — it already carries the row identity);
|
|
85
|
+
* - `a.x ?? a.y [?? …]` (an N-part nullish chain of member fields) →
|
|
86
|
+
* a RUNTIME field-selector expression mirroring the chain
|
|
87
|
+
* (`a.x != null ? "x" : a.y != null ? "y" : "z"`), or, when the chain
|
|
88
|
+
* ends in a static literal fallback, a plain static field;
|
|
89
|
+
* - `a.x || 'fallback'` → static field (literal-fallback only; `||`
|
|
90
|
+
* does not get the N-part runtime treatment `??` does);
|
|
91
|
+
* - `cond ? a.x : a.y` (both branches member fields of the row object) →
|
|
92
|
+
* a RUNTIME field-selector expression duplicating `cond` verbatim
|
|
93
|
+
* (`cond ? "x" : "y"`) — CONSTRAINED (C3): `cond` must be CALL-FREE
|
|
94
|
+
* (checked recursively over the whole test subtree); a call anywhere in
|
|
95
|
+
* the duplicated test would double-evaluate it across SSR and hydration,
|
|
96
|
+
* so the whole leaf is left unstamped instead;
|
|
97
|
+
* - `cond ? 'Literal' : 'Other'` where `cond` is itself a bare field
|
|
98
|
+
* member (a boolean-typed field driving two literal branches) → static
|
|
99
|
+
* field named by `cond` — the FE's typed controls (D7, a different
|
|
100
|
+
* mechanism, M3) read the boolean's true/false rendering from
|
|
101
|
+
* `window.__slice`, not from this attribute;
|
|
102
|
+
* - a COMPUTED member (`a[expr]`, `expr` not a string literal) → no field
|
|
103
|
+
* name, `data-agent-field-state="unresolved"` (a positive "this is
|
|
104
|
+
* row-scoped data, but which field is unknown" signal — never silent).
|
|
105
|
+
*
|
|
106
|
+
* A leaf with exactly one meaningful child (no surrounding text) gets a bare
|
|
107
|
+
* `data-agent-field`. A leaf with 2+ meaningful children (label + value,
|
|
108
|
+
* D6) where EXACTLY ONE is dynamic gets the same `data-agent-field` PLUS
|
|
109
|
+
* `data-agent-field-slot`: the dynamic part's 0-based index among the
|
|
110
|
+
* element's own meaningful children. This is deliberately equal to its
|
|
111
|
+
* text-node index in the eventual SSR/hydrated DOM: every meaningful JSXText
|
|
112
|
+
* or JSXExpressionContainer child renders as exactly one text node (React 18
|
|
113
|
+
* inserts `<!-- -->` comment separators BETWEEN adjacent text-producing
|
|
114
|
+
* outputs to keep them independently locatable during hydration, but those
|
|
115
|
+
* comments are not text nodes and do not shift text-node indices). A leaf
|
|
116
|
+
* with 2+ dynamic parts that all resolve to STATIC fields with more than one
|
|
117
|
+
* DISTINCT field name gets `data-agent-field-state="mixed"` plus
|
|
118
|
+
* `data-agent-field-parts` (a comma-separated, source-order field list); any
|
|
119
|
+
* other multi-dynamic-part shape (an unresolvable part, or every part naming
|
|
120
|
+
* the same field) is left unstamped.
|
|
121
|
+
*
|
|
122
|
+
* Recursion into a leaf's structural siblings stops at the nearest NESTED
|
|
123
|
+
* `.map()` collection (a JSXExpressionContainer wrapping a call, not a JSX
|
|
124
|
+
* element child, so it is never a "structural child" this walker descends
|
|
125
|
+
* into) — the innermost-stamped-param rule: a field leaf only ever binds
|
|
126
|
+
* against its nearest enclosing map's own parameter. A nested collection
|
|
127
|
+
* that is itself slice-rooted gets discovered and stamped independently by
|
|
128
|
+
* the same Program-wide `.map()` scan, with its own entity/row identity.
|
|
129
|
+
*
|
|
130
|
+
* ── Failure posture ─────────────────────────────────────────────────────
|
|
131
|
+
*
|
|
132
|
+
* Every resolution step is an explicit allowlist; anything the resolver is
|
|
133
|
+
* not sure about degrades to NO STAMP, never a guessed one — a fragment or
|
|
134
|
+
* conditional expression at the callback's JSX root, an unresolvable
|
|
135
|
+
* component usage, a non-slice source, an ObjectPattern without an `id` key,
|
|
136
|
+
* a call inside a duplicated ternary test, ambiguous control flow (2+ return
|
|
137
|
+
* statements) — all leave the subtree untouched.
|
|
138
|
+
*
|
|
139
|
+
* ── Idempotence ─────────────────────────────────────────────────────────
|
|
140
|
+
*
|
|
141
|
+
* The plugin never rewrites SOURCE (D15) and never touches an existing
|
|
142
|
+
* attribute: every stamp is guarded by a same-name attribute-presence check,
|
|
143
|
+
* and a row root that already carries `data-agent-entity` short-circuits the
|
|
144
|
+
* whole map (no re-resolution, no field re-walk) — running the plugin twice
|
|
145
|
+
* produces byte-identical output.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
interface CollectionBindingPluginOptions {
|
|
149
|
+
/** Skip files matching this pattern. @default /node_modules/ */
|
|
150
|
+
fileExclude?: RegExp;
|
|
151
|
+
}
|
|
152
|
+
declare function babelPluginCollectionBinding({ types: t }: {
|
|
153
|
+
types: typeof types;
|
|
154
|
+
}, opts?: CollectionBindingPluginOptions): PluginObj;
|
|
155
|
+
|
|
156
|
+
export { type CollectionBindingPluginOptions, babelPluginCollectionBinding };
|