@archie/devtools 0.0.22 → 0.0.24
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 +6 -0
- package/dist/client/client.js +10 -4
- package/dist/client/client.mjs +10 -4
- package/dist/index.d.mts +182 -1
- package/dist/index.d.ts +182 -1
- package/dist/index.js +322 -38
- package/dist/index.mjs +322 -38
- package/dist/inspector.standalone.js +223 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,12 @@ export default function App() {
|
|
|
67
67
|
- React Router DOM 6.4+
|
|
68
68
|
- Vite 5+
|
|
69
69
|
|
|
70
|
+
## Deploy order
|
|
71
|
+
|
|
72
|
+
Changes to `babel-plugin-editor-meta.ts`'s `__editorMeta` prop shape and
|
|
73
|
+
`inspector.standalone.js`'s meta-read paths have a load-bearing publish
|
|
74
|
+
order — see [`docs/deploy-order.md`](docs/deploy-order.md).
|
|
75
|
+
|
|
70
76
|
## Local linking
|
|
71
77
|
|
|
72
78
|
```bash
|
package/dist/client/client.js
CHANGED
|
@@ -4147,6 +4147,12 @@ var FIBER_KEY_PREFIX = "__reactFiber$";
|
|
|
4147
4147
|
var PROPS_KEY_PREFIX = "__reactProps$";
|
|
4148
4148
|
var metaCache = /* @__PURE__ */ new WeakMap();
|
|
4149
4149
|
var directMetaCache = /* @__PURE__ */ new WeakMap();
|
|
4150
|
+
function readEditorMetaValue(value) {
|
|
4151
|
+
if (typeof value === "function") {
|
|
4152
|
+
return value();
|
|
4153
|
+
}
|
|
4154
|
+
return value ?? null;
|
|
4155
|
+
}
|
|
4150
4156
|
function hasEditorMetaInFiberChain(fiber) {
|
|
4151
4157
|
let current = fiber;
|
|
4152
4158
|
while (current) {
|
|
@@ -4262,8 +4268,8 @@ function getDirectEditorMeta(el) {
|
|
|
4262
4268
|
const selection = getFiberSelectionFromElement(el);
|
|
4263
4269
|
const cached = readFiberAwareCache(directMetaCache, el, selection);
|
|
4264
4270
|
if (cached !== void 0) return cached;
|
|
4265
|
-
const meta = selection.fiber?.memoizedProps?.__editorMeta;
|
|
4266
|
-
return writeFiberAwareCache(directMetaCache, el, selection, meta
|
|
4271
|
+
const meta = readEditorMetaValue(selection.fiber?.memoizedProps?.__editorMeta);
|
|
4272
|
+
return writeFiberAwareCache(directMetaCache, el, selection, meta);
|
|
4267
4273
|
}
|
|
4268
4274
|
function getEditorMeta(el) {
|
|
4269
4275
|
const selection = getFiberSelectionFromElement(el);
|
|
@@ -4271,7 +4277,7 @@ function getEditorMeta(el) {
|
|
|
4271
4277
|
if (cached !== void 0) return cached;
|
|
4272
4278
|
let fiber = selection.fiber;
|
|
4273
4279
|
while (fiber) {
|
|
4274
|
-
const meta = fiber.memoizedProps?.__editorMeta;
|
|
4280
|
+
const meta = readEditorMetaValue(fiber.memoizedProps?.__editorMeta);
|
|
4275
4281
|
if (meta) {
|
|
4276
4282
|
return writeFiberAwareCache(metaCache, el, selection, meta);
|
|
4277
4283
|
}
|
|
@@ -5089,7 +5095,7 @@ function projectUsageMeta(el) {
|
|
|
5089
5095
|
const owner = getEditorMetaOwnerFiber(el);
|
|
5090
5096
|
if (!owner) return null;
|
|
5091
5097
|
if (getSingleHostElement(owner) !== el) return null;
|
|
5092
|
-
return owner.memoizedProps?.__editorMeta
|
|
5098
|
+
return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
|
|
5093
5099
|
}
|
|
5094
5100
|
function isRuntimeVisualInternal(el) {
|
|
5095
5101
|
if (shouldIgnoreAsVirtualRuntime(el)) return true;
|
package/dist/client/client.mjs
CHANGED
|
@@ -4112,6 +4112,12 @@ var FIBER_KEY_PREFIX = "__reactFiber$";
|
|
|
4112
4112
|
var PROPS_KEY_PREFIX = "__reactProps$";
|
|
4113
4113
|
var metaCache = /* @__PURE__ */ new WeakMap();
|
|
4114
4114
|
var directMetaCache = /* @__PURE__ */ new WeakMap();
|
|
4115
|
+
function readEditorMetaValue(value) {
|
|
4116
|
+
if (typeof value === "function") {
|
|
4117
|
+
return value();
|
|
4118
|
+
}
|
|
4119
|
+
return value ?? null;
|
|
4120
|
+
}
|
|
4115
4121
|
function hasEditorMetaInFiberChain(fiber) {
|
|
4116
4122
|
let current = fiber;
|
|
4117
4123
|
while (current) {
|
|
@@ -4227,8 +4233,8 @@ function getDirectEditorMeta(el) {
|
|
|
4227
4233
|
const selection = getFiberSelectionFromElement(el);
|
|
4228
4234
|
const cached = readFiberAwareCache(directMetaCache, el, selection);
|
|
4229
4235
|
if (cached !== void 0) return cached;
|
|
4230
|
-
const meta = selection.fiber?.memoizedProps?.__editorMeta;
|
|
4231
|
-
return writeFiberAwareCache(directMetaCache, el, selection, meta
|
|
4236
|
+
const meta = readEditorMetaValue(selection.fiber?.memoizedProps?.__editorMeta);
|
|
4237
|
+
return writeFiberAwareCache(directMetaCache, el, selection, meta);
|
|
4232
4238
|
}
|
|
4233
4239
|
function getEditorMeta(el) {
|
|
4234
4240
|
const selection = getFiberSelectionFromElement(el);
|
|
@@ -4236,7 +4242,7 @@ function getEditorMeta(el) {
|
|
|
4236
4242
|
if (cached !== void 0) return cached;
|
|
4237
4243
|
let fiber = selection.fiber;
|
|
4238
4244
|
while (fiber) {
|
|
4239
|
-
const meta = fiber.memoizedProps?.__editorMeta;
|
|
4245
|
+
const meta = readEditorMetaValue(fiber.memoizedProps?.__editorMeta);
|
|
4240
4246
|
if (meta) {
|
|
4241
4247
|
return writeFiberAwareCache(metaCache, el, selection, meta);
|
|
4242
4248
|
}
|
|
@@ -5054,7 +5060,7 @@ function projectUsageMeta(el) {
|
|
|
5054
5060
|
const owner = getEditorMetaOwnerFiber(el);
|
|
5055
5061
|
if (!owner) return null;
|
|
5056
5062
|
if (getSingleHostElement(owner) !== el) return null;
|
|
5057
|
-
return owner.memoizedProps?.__editorMeta
|
|
5063
|
+
return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
|
|
5058
5064
|
}
|
|
5059
5065
|
function isRuntimeVisualInternal(el) {
|
|
5060
5066
|
if (shouldIgnoreAsVirtualRuntime(el)) return true;
|
package/dist/index.d.mts
CHANGED
|
@@ -10,9 +10,44 @@ import { types, PluginObj } from '@babel/core';
|
|
|
10
10
|
*
|
|
11
11
|
* The injected prop contains:
|
|
12
12
|
* nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
|
|
13
|
+
* + (ARCH-883 F1) slot, propBindings, propOwner
|
|
13
14
|
*
|
|
14
15
|
* At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
|
|
15
16
|
* element identity without any DOM attributes.
|
|
17
|
+
*
|
|
18
|
+
* ARCH-883 F1 (SDD §2.3/§2.6) adds three compile-time-only things:
|
|
19
|
+
*
|
|
20
|
+
* 1. `propBindings` — which of this node's rendered expressions trace back to
|
|
21
|
+
* a prop of the enclosing component, and whether that trace is safe enough
|
|
22
|
+
* to OFFER as an edit. It never decides what code to write; the write goes
|
|
23
|
+
* through the existing server write-paths. Anything the resolver is not
|
|
24
|
+
* certain about degrades to a GAP, never to a wrong binding.
|
|
25
|
+
* 2. `data-kit-slot` consumption — the curated slot label is lifted into the
|
|
26
|
+
* meta and the ATTRIBUTE IS STRIPPED, so it never reaches the DOM. The
|
|
27
|
+
* plugin treats the string as opaque; validating it against the canonical
|
|
28
|
+
* vocabulary (kit-runtime's `slot-vocabulary` export) belongs to the
|
|
29
|
+
* consumer. devtools deliberately takes no dependency on kit-runtime.
|
|
30
|
+
* 3. `movable: false` for kit-origin nodes — reordering inside a kit template
|
|
31
|
+
* affects N usages, a permanent non-objective (PRD §1.3).
|
|
32
|
+
*
|
|
33
|
+
* `parentNodeId` stays INTRA-FILE by construction (it only ever walks JSX
|
|
34
|
+
* ancestors within the file being transformed), so it never links a kit template
|
|
35
|
+
* node to a screen usage; per SDD §2.3 it must not be used across that seam.
|
|
36
|
+
* See the type docs in `types/editor-meta.ts`.
|
|
37
|
+
*
|
|
38
|
+
* ARCH-883 P0-1: the prop value is emitted as a ZERO-ARG ARROW FUNCTION that
|
|
39
|
+
* returns the meta object literal — `__editorMeta={() => ({...})}` — NOT the
|
|
40
|
+
* object literal directly. React serializes unknown OBJECT-valued props on
|
|
41
|
+
* host elements into the DOM (`__editorMeta="[object Object]"` on every
|
|
42
|
+
* published prototype page — a live production bug, proven by an executed
|
|
43
|
+
* SSR test in `src/client/dnd/design-mode/__tests__/editor-meta-react-behavior.test.ts`).
|
|
44
|
+
* Function- and symbol-valued unknown props are silently dropped by React in
|
|
45
|
+
* SSR, hydration, and client render — never written to the DOM — while
|
|
46
|
+
* remaining fully readable on `fiber.memoizedProps.__editorMeta` via a call.
|
|
47
|
+
* Every reader in the Fiber bridge (`fiber-bridge.ts`, `useElementScanner.ts`,
|
|
48
|
+
* `inspector.standalone.js`) invokes the value when it is a function and
|
|
49
|
+
* tolerates a raw object when it is not, so bundles built before this change
|
|
50
|
+
* keep resolving correctly until they are rebuilt.
|
|
16
51
|
*/
|
|
17
52
|
|
|
18
53
|
interface EditorMetaPluginOptions {
|
|
@@ -22,11 +57,157 @@ interface EditorMetaPluginOptions {
|
|
|
22
57
|
instrumentImported?: boolean;
|
|
23
58
|
/** Include exact host source refs for runtime DOM lookup. */
|
|
24
59
|
includeHostSourceRef?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Where the transformed sources come from (ARCH-883 F1).
|
|
62
|
+
*
|
|
63
|
+
* - `'screen'` (default) — per-project `screens/<slug>.jsx`. Reorder is a real
|
|
64
|
+
* operation there, so `movable` keeps its structural meaning.
|
|
65
|
+
* - `'kit'` — the shared kit component library (the `vendor.editor.js` babel
|
|
66
|
+
* pre-pass over the kit's `.gen/` sources, SDD §2.1). Every node emits
|
|
67
|
+
* `movable: false`: one template backs N usages across N projects, so
|
|
68
|
+
* reordering it is a permanent non-objective (PRD §1.3).
|
|
69
|
+
*
|
|
70
|
+
* Independently of this flag, a slot-carrying node is kit-origin by definition
|
|
71
|
+
* and also emits `movable: false`.
|
|
72
|
+
*/
|
|
73
|
+
origin?: 'screen' | 'kit';
|
|
25
74
|
}
|
|
26
75
|
declare function babelPluginEditorMeta({ types: t }: {
|
|
27
76
|
types: typeof types;
|
|
28
77
|
}, opts?: EditorMetaPluginOptions): PluginObj;
|
|
29
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A link between a rendered JSX expression and a prop of the component whose
|
|
81
|
+
* template contains it (ARCH-883 F1, SDD §2.3).
|
|
82
|
+
*
|
|
83
|
+
* `propBindings` decides what UI the editor may OFFER — never what code gets
|
|
84
|
+
* written. The server resolves the actual write through the existing
|
|
85
|
+
* `rebuild-screen` / `screen-slice` / `update-shell` paths.
|
|
86
|
+
*
|
|
87
|
+
* ROUTABILITY IS AN ALLOWLIST with THREE conditions, both required:
|
|
88
|
+
*
|
|
89
|
+
* const routable =
|
|
90
|
+
* (b.kind === 'direct' || b.kind === 'derived') &&
|
|
91
|
+
* usageValueIsStringLiteralOrAbsent(usage, b.prop) && // consumer-side
|
|
92
|
+
* propOwnerMatchesUsageComponent(meta, usage); // consumer-side
|
|
93
|
+
*
|
|
94
|
+
* Everything else is a gap: a `kind: 'formatted'` entry (prop-reachable but not
|
|
95
|
+
* safely routable) or no entry at all for that target. A resolver false positive
|
|
96
|
+
* must always degrade to a gap, never to a wrong binding.
|
|
97
|
+
*
|
|
98
|
+
* THE USAGE-VALUE LAW APPLIES TO BOTH ROUTABLE KINDS, not only to `derived`.
|
|
99
|
+
* The plugin sees the kit template, never the usage site, so it cannot enforce
|
|
100
|
+
* it — the consumer must, for `direct` too. Real-corpus evidence for why: on the
|
|
101
|
+
* kit, `direct` outnumbers `derived` ~26:1, and a `direct` binding whose usage
|
|
102
|
+
* value is a computed object (`config={{…}}`, `widgets={list}`) is exactly as
|
|
103
|
+
* unwritable as a `derived` one. Scoping the law to `derived` would leave the
|
|
104
|
+
* overwhelming majority of bindings unguarded.
|
|
105
|
+
*
|
|
106
|
+
* Attributes that are machine contracts rather than content (`on*` handlers,
|
|
107
|
+
* `data-*` — including the durable `data-agent-id` key — and structural ones like
|
|
108
|
+
* `id`/`htmlFor`/`name`/`type`/`role`/`slot`) are downgraded to `formatted` by the
|
|
109
|
+
* PRODUCER, because no usage value could make them a text edit. The consumer does
|
|
110
|
+
* not need to re-derive that list; it only needs the allowlist above.
|
|
111
|
+
*/
|
|
112
|
+
interface PropBinding {
|
|
113
|
+
/**
|
|
114
|
+
* Where the bound expression renders: the element's children, or an attribute.
|
|
115
|
+
*
|
|
116
|
+
* Parse `attr:` by splitting on the FIRST colon only — namespaced JSX attributes
|
|
117
|
+
* keep their own colon (`attr:xlink:href` is the attribute `xlink:href`). A
|
|
118
|
+
* naive `split(':')[1]` silently truncates those.
|
|
119
|
+
*/
|
|
120
|
+
target: 'children' | `attr:${string}`;
|
|
121
|
+
/**
|
|
122
|
+
* Prop name on the OWNING component — the one named by `EditorMeta.propOwner`,
|
|
123
|
+
* NOT necessarily the component at the usage site. The consumer MUST verify
|
|
124
|
+
* `propOwner` against the usage component it resolved from the fiber chain and
|
|
125
|
+
* treat a mismatch as a gap.
|
|
126
|
+
*/
|
|
127
|
+
prop: string;
|
|
128
|
+
/**
|
|
129
|
+
* Routing verdict.
|
|
130
|
+
*
|
|
131
|
+
* - `direct` — the expression is a bare identifier that is a prop of the
|
|
132
|
+
* owning component (`{headline}`). Routable.
|
|
133
|
+
* - `derived` — the expression's root is a SINGLE prop identifier carrying a
|
|
134
|
+
* string-literal fallback, reached directly or through one
|
|
135
|
+
* local-const hop (`{title}` where `const title = headline || '…'`).
|
|
136
|
+
* Subject to the same consumer-side usage-value law as `direct`
|
|
137
|
+
* (see above) — being `derived` is not a stricter licence, just a
|
|
138
|
+
* longer trace.
|
|
139
|
+
* - `formatted`— prop-reachable but NOT routable: member expressions, ternaries,
|
|
140
|
+
* template literals, calls (`{brand.slice(0, 1)}`), more than one
|
|
141
|
+
* prop identifier, or an expression among mixed children. This is
|
|
142
|
+
* the explicit gap marker; `prop` on it is telemetry only.
|
|
143
|
+
*/
|
|
144
|
+
kind: 'direct' | 'derived' | 'formatted';
|
|
145
|
+
}
|
|
146
|
+
/** Metadata injected into every JSX element by babel-plugin-editor-meta. */
|
|
147
|
+
interface EditorMeta {
|
|
148
|
+
/** Stable identifier: "file:line:col:componentName" */
|
|
149
|
+
nodeId: string;
|
|
150
|
+
/** Relative file path from project root. */
|
|
151
|
+
file: string;
|
|
152
|
+
line: number;
|
|
153
|
+
col: number;
|
|
154
|
+
/** PascalCase component name or lowercase HTML tag. */
|
|
155
|
+
componentName: string;
|
|
156
|
+
/** Safely serializable literal prop values. Dynamic expressions are "__dynamic__". */
|
|
157
|
+
staticProps: Record<string, unknown> | null;
|
|
158
|
+
/**
|
|
159
|
+
* Whether the JSX node is in a reorderable position (2+ siblings).
|
|
160
|
+
*
|
|
161
|
+
* ARCH-883 F1: forced to `false` for every kit-origin node (slot-carrying
|
|
162
|
+
* nodes, and everything emitted with `origin: 'kit'`). Reordering inside a kit
|
|
163
|
+
* template affects N usages across N projects — a permanent non-objective per
|
|
164
|
+
* the PRD (§1.3), not a missing feature.
|
|
165
|
+
*/
|
|
166
|
+
movable: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* nodeId of the enclosing JSX parent element, or null for top-level.
|
|
169
|
+
*
|
|
170
|
+
* INTRA-FILE BY CONSTRUCTION: the plugin only walks JSX ancestors inside the
|
|
171
|
+
* file it is transforming, so this never links a kit template node to a screen
|
|
172
|
+
* usage. Per SDD §2.3 it must NOT be used across the kit↔screen seam — the
|
|
173
|
+
* editor tree there is derived from the fiber chain, and projection from the
|
|
174
|
+
* DOM. The root node of a kit template emits `null` here.
|
|
175
|
+
*/
|
|
176
|
+
parentNodeId: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* Curated kit slot label, e.g. `"AuthFrame/aside/headline"` (SDD §2.6).
|
|
179
|
+
*
|
|
180
|
+
* Consumed from a `data-kit-slot` attribute at compile time; the attribute is
|
|
181
|
+
* STRIPPED from the output and never reaches the DOM — it travels on the fiber.
|
|
182
|
+
* A UI/telemetry label, NEVER a persisted key.
|
|
183
|
+
*
|
|
184
|
+
* The plugin treats any well-formed slot string as OPAQUE (non-empty, `/`
|
|
185
|
+
* separated, ≤4 segments). Validating it against the canonical vocabulary
|
|
186
|
+
* (`slot-vocabulary`, published by kit-runtime) is the consumer's job — devtools
|
|
187
|
+
* deliberately takes no dependency on kit-runtime.
|
|
188
|
+
*
|
|
189
|
+
* Absent (`undefined`) on nodes with no slot and on bundles built before F1.
|
|
190
|
+
*/
|
|
191
|
+
slot?: string | null;
|
|
192
|
+
/**
|
|
193
|
+
* Prop bindings for this node's rendered expressions (SDD §2.3).
|
|
194
|
+
*
|
|
195
|
+
* Emitted only when non-empty — read it as `meta.propBindings ?? []`. Absent on
|
|
196
|
+
* bundles built before ARCH-883 F1.
|
|
197
|
+
*/
|
|
198
|
+
propBindings?: PropBinding[];
|
|
199
|
+
/**
|
|
200
|
+
* Name of the component whose props `PropBinding.prop` refers to — the nearest
|
|
201
|
+
* enclosing, module-level component function that destructures its props.
|
|
202
|
+
*
|
|
203
|
+
* Emitted only alongside `propBindings`. The consumer MUST check it against the
|
|
204
|
+
* usage component before offering an edit: a kit-internal helper component
|
|
205
|
+
* (`function TableRow({ row })`) yields bindings in ITS prop namespace, which is
|
|
206
|
+
* not the usage's, and routing those would write the wrong prop. Mismatch = gap.
|
|
207
|
+
*/
|
|
208
|
+
propOwner?: string | null;
|
|
209
|
+
}
|
|
210
|
+
|
|
30
211
|
interface ArchieDevToolsOptions {
|
|
31
212
|
/**
|
|
32
213
|
* Enable debug logging.
|
|
@@ -61,4 +242,4 @@ interface ArchieDevToolsPlugin {
|
|
|
61
242
|
}
|
|
62
243
|
declare function archieDevTools(options?: ArchieDevToolsOptions): ArchieDevToolsPlugin;
|
|
63
244
|
|
|
64
|
-
export { type EditorMetaPluginOptions, archieDevTools, babelPluginEditorMeta };
|
|
245
|
+
export { type EditorMeta, type EditorMetaPluginOptions, type PropBinding, archieDevTools, babelPluginEditorMeta };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,9 +10,44 @@ import { types, PluginObj } from '@babel/core';
|
|
|
10
10
|
*
|
|
11
11
|
* The injected prop contains:
|
|
12
12
|
* nodeId, file, line, col, componentName, staticProps, movable, parentNodeId
|
|
13
|
+
* + (ARCH-883 F1) slot, propBindings, propOwner
|
|
13
14
|
*
|
|
14
15
|
* At runtime the Fiber bridge reads `memoizedProps.__editorMeta` to resolve
|
|
15
16
|
* element identity without any DOM attributes.
|
|
17
|
+
*
|
|
18
|
+
* ARCH-883 F1 (SDD §2.3/§2.6) adds three compile-time-only things:
|
|
19
|
+
*
|
|
20
|
+
* 1. `propBindings` — which of this node's rendered expressions trace back to
|
|
21
|
+
* a prop of the enclosing component, and whether that trace is safe enough
|
|
22
|
+
* to OFFER as an edit. It never decides what code to write; the write goes
|
|
23
|
+
* through the existing server write-paths. Anything the resolver is not
|
|
24
|
+
* certain about degrades to a GAP, never to a wrong binding.
|
|
25
|
+
* 2. `data-kit-slot` consumption — the curated slot label is lifted into the
|
|
26
|
+
* meta and the ATTRIBUTE IS STRIPPED, so it never reaches the DOM. The
|
|
27
|
+
* plugin treats the string as opaque; validating it against the canonical
|
|
28
|
+
* vocabulary (kit-runtime's `slot-vocabulary` export) belongs to the
|
|
29
|
+
* consumer. devtools deliberately takes no dependency on kit-runtime.
|
|
30
|
+
* 3. `movable: false` for kit-origin nodes — reordering inside a kit template
|
|
31
|
+
* affects N usages, a permanent non-objective (PRD §1.3).
|
|
32
|
+
*
|
|
33
|
+
* `parentNodeId` stays INTRA-FILE by construction (it only ever walks JSX
|
|
34
|
+
* ancestors within the file being transformed), so it never links a kit template
|
|
35
|
+
* node to a screen usage; per SDD §2.3 it must not be used across that seam.
|
|
36
|
+
* See the type docs in `types/editor-meta.ts`.
|
|
37
|
+
*
|
|
38
|
+
* ARCH-883 P0-1: the prop value is emitted as a ZERO-ARG ARROW FUNCTION that
|
|
39
|
+
* returns the meta object literal — `__editorMeta={() => ({...})}` — NOT the
|
|
40
|
+
* object literal directly. React serializes unknown OBJECT-valued props on
|
|
41
|
+
* host elements into the DOM (`__editorMeta="[object Object]"` on every
|
|
42
|
+
* published prototype page — a live production bug, proven by an executed
|
|
43
|
+
* SSR test in `src/client/dnd/design-mode/__tests__/editor-meta-react-behavior.test.ts`).
|
|
44
|
+
* Function- and symbol-valued unknown props are silently dropped by React in
|
|
45
|
+
* SSR, hydration, and client render — never written to the DOM — while
|
|
46
|
+
* remaining fully readable on `fiber.memoizedProps.__editorMeta` via a call.
|
|
47
|
+
* Every reader in the Fiber bridge (`fiber-bridge.ts`, `useElementScanner.ts`,
|
|
48
|
+
* `inspector.standalone.js`) invokes the value when it is a function and
|
|
49
|
+
* tolerates a raw object when it is not, so bundles built before this change
|
|
50
|
+
* keep resolving correctly until they are rebuilt.
|
|
16
51
|
*/
|
|
17
52
|
|
|
18
53
|
interface EditorMetaPluginOptions {
|
|
@@ -22,11 +57,157 @@ interface EditorMetaPluginOptions {
|
|
|
22
57
|
instrumentImported?: boolean;
|
|
23
58
|
/** Include exact host source refs for runtime DOM lookup. */
|
|
24
59
|
includeHostSourceRef?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Where the transformed sources come from (ARCH-883 F1).
|
|
62
|
+
*
|
|
63
|
+
* - `'screen'` (default) — per-project `screens/<slug>.jsx`. Reorder is a real
|
|
64
|
+
* operation there, so `movable` keeps its structural meaning.
|
|
65
|
+
* - `'kit'` — the shared kit component library (the `vendor.editor.js` babel
|
|
66
|
+
* pre-pass over the kit's `.gen/` sources, SDD §2.1). Every node emits
|
|
67
|
+
* `movable: false`: one template backs N usages across N projects, so
|
|
68
|
+
* reordering it is a permanent non-objective (PRD §1.3).
|
|
69
|
+
*
|
|
70
|
+
* Independently of this flag, a slot-carrying node is kit-origin by definition
|
|
71
|
+
* and also emits `movable: false`.
|
|
72
|
+
*/
|
|
73
|
+
origin?: 'screen' | 'kit';
|
|
25
74
|
}
|
|
26
75
|
declare function babelPluginEditorMeta({ types: t }: {
|
|
27
76
|
types: typeof types;
|
|
28
77
|
}, opts?: EditorMetaPluginOptions): PluginObj;
|
|
29
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A link between a rendered JSX expression and a prop of the component whose
|
|
81
|
+
* template contains it (ARCH-883 F1, SDD §2.3).
|
|
82
|
+
*
|
|
83
|
+
* `propBindings` decides what UI the editor may OFFER — never what code gets
|
|
84
|
+
* written. The server resolves the actual write through the existing
|
|
85
|
+
* `rebuild-screen` / `screen-slice` / `update-shell` paths.
|
|
86
|
+
*
|
|
87
|
+
* ROUTABILITY IS AN ALLOWLIST with THREE conditions, both required:
|
|
88
|
+
*
|
|
89
|
+
* const routable =
|
|
90
|
+
* (b.kind === 'direct' || b.kind === 'derived') &&
|
|
91
|
+
* usageValueIsStringLiteralOrAbsent(usage, b.prop) && // consumer-side
|
|
92
|
+
* propOwnerMatchesUsageComponent(meta, usage); // consumer-side
|
|
93
|
+
*
|
|
94
|
+
* Everything else is a gap: a `kind: 'formatted'` entry (prop-reachable but not
|
|
95
|
+
* safely routable) or no entry at all for that target. A resolver false positive
|
|
96
|
+
* must always degrade to a gap, never to a wrong binding.
|
|
97
|
+
*
|
|
98
|
+
* THE USAGE-VALUE LAW APPLIES TO BOTH ROUTABLE KINDS, not only to `derived`.
|
|
99
|
+
* The plugin sees the kit template, never the usage site, so it cannot enforce
|
|
100
|
+
* it — the consumer must, for `direct` too. Real-corpus evidence for why: on the
|
|
101
|
+
* kit, `direct` outnumbers `derived` ~26:1, and a `direct` binding whose usage
|
|
102
|
+
* value is a computed object (`config={{…}}`, `widgets={list}`) is exactly as
|
|
103
|
+
* unwritable as a `derived` one. Scoping the law to `derived` would leave the
|
|
104
|
+
* overwhelming majority of bindings unguarded.
|
|
105
|
+
*
|
|
106
|
+
* Attributes that are machine contracts rather than content (`on*` handlers,
|
|
107
|
+
* `data-*` — including the durable `data-agent-id` key — and structural ones like
|
|
108
|
+
* `id`/`htmlFor`/`name`/`type`/`role`/`slot`) are downgraded to `formatted` by the
|
|
109
|
+
* PRODUCER, because no usage value could make them a text edit. The consumer does
|
|
110
|
+
* not need to re-derive that list; it only needs the allowlist above.
|
|
111
|
+
*/
|
|
112
|
+
interface PropBinding {
|
|
113
|
+
/**
|
|
114
|
+
* Where the bound expression renders: the element's children, or an attribute.
|
|
115
|
+
*
|
|
116
|
+
* Parse `attr:` by splitting on the FIRST colon only — namespaced JSX attributes
|
|
117
|
+
* keep their own colon (`attr:xlink:href` is the attribute `xlink:href`). A
|
|
118
|
+
* naive `split(':')[1]` silently truncates those.
|
|
119
|
+
*/
|
|
120
|
+
target: 'children' | `attr:${string}`;
|
|
121
|
+
/**
|
|
122
|
+
* Prop name on the OWNING component — the one named by `EditorMeta.propOwner`,
|
|
123
|
+
* NOT necessarily the component at the usage site. The consumer MUST verify
|
|
124
|
+
* `propOwner` against the usage component it resolved from the fiber chain and
|
|
125
|
+
* treat a mismatch as a gap.
|
|
126
|
+
*/
|
|
127
|
+
prop: string;
|
|
128
|
+
/**
|
|
129
|
+
* Routing verdict.
|
|
130
|
+
*
|
|
131
|
+
* - `direct` — the expression is a bare identifier that is a prop of the
|
|
132
|
+
* owning component (`{headline}`). Routable.
|
|
133
|
+
* - `derived` — the expression's root is a SINGLE prop identifier carrying a
|
|
134
|
+
* string-literal fallback, reached directly or through one
|
|
135
|
+
* local-const hop (`{title}` where `const title = headline || '…'`).
|
|
136
|
+
* Subject to the same consumer-side usage-value law as `direct`
|
|
137
|
+
* (see above) — being `derived` is not a stricter licence, just a
|
|
138
|
+
* longer trace.
|
|
139
|
+
* - `formatted`— prop-reachable but NOT routable: member expressions, ternaries,
|
|
140
|
+
* template literals, calls (`{brand.slice(0, 1)}`), more than one
|
|
141
|
+
* prop identifier, or an expression among mixed children. This is
|
|
142
|
+
* the explicit gap marker; `prop` on it is telemetry only.
|
|
143
|
+
*/
|
|
144
|
+
kind: 'direct' | 'derived' | 'formatted';
|
|
145
|
+
}
|
|
146
|
+
/** Metadata injected into every JSX element by babel-plugin-editor-meta. */
|
|
147
|
+
interface EditorMeta {
|
|
148
|
+
/** Stable identifier: "file:line:col:componentName" */
|
|
149
|
+
nodeId: string;
|
|
150
|
+
/** Relative file path from project root. */
|
|
151
|
+
file: string;
|
|
152
|
+
line: number;
|
|
153
|
+
col: number;
|
|
154
|
+
/** PascalCase component name or lowercase HTML tag. */
|
|
155
|
+
componentName: string;
|
|
156
|
+
/** Safely serializable literal prop values. Dynamic expressions are "__dynamic__". */
|
|
157
|
+
staticProps: Record<string, unknown> | null;
|
|
158
|
+
/**
|
|
159
|
+
* Whether the JSX node is in a reorderable position (2+ siblings).
|
|
160
|
+
*
|
|
161
|
+
* ARCH-883 F1: forced to `false` for every kit-origin node (slot-carrying
|
|
162
|
+
* nodes, and everything emitted with `origin: 'kit'`). Reordering inside a kit
|
|
163
|
+
* template affects N usages across N projects — a permanent non-objective per
|
|
164
|
+
* the PRD (§1.3), not a missing feature.
|
|
165
|
+
*/
|
|
166
|
+
movable: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* nodeId of the enclosing JSX parent element, or null for top-level.
|
|
169
|
+
*
|
|
170
|
+
* INTRA-FILE BY CONSTRUCTION: the plugin only walks JSX ancestors inside the
|
|
171
|
+
* file it is transforming, so this never links a kit template node to a screen
|
|
172
|
+
* usage. Per SDD §2.3 it must NOT be used across the kit↔screen seam — the
|
|
173
|
+
* editor tree there is derived from the fiber chain, and projection from the
|
|
174
|
+
* DOM. The root node of a kit template emits `null` here.
|
|
175
|
+
*/
|
|
176
|
+
parentNodeId: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* Curated kit slot label, e.g. `"AuthFrame/aside/headline"` (SDD §2.6).
|
|
179
|
+
*
|
|
180
|
+
* Consumed from a `data-kit-slot` attribute at compile time; the attribute is
|
|
181
|
+
* STRIPPED from the output and never reaches the DOM — it travels on the fiber.
|
|
182
|
+
* A UI/telemetry label, NEVER a persisted key.
|
|
183
|
+
*
|
|
184
|
+
* The plugin treats any well-formed slot string as OPAQUE (non-empty, `/`
|
|
185
|
+
* separated, ≤4 segments). Validating it against the canonical vocabulary
|
|
186
|
+
* (`slot-vocabulary`, published by kit-runtime) is the consumer's job — devtools
|
|
187
|
+
* deliberately takes no dependency on kit-runtime.
|
|
188
|
+
*
|
|
189
|
+
* Absent (`undefined`) on nodes with no slot and on bundles built before F1.
|
|
190
|
+
*/
|
|
191
|
+
slot?: string | null;
|
|
192
|
+
/**
|
|
193
|
+
* Prop bindings for this node's rendered expressions (SDD §2.3).
|
|
194
|
+
*
|
|
195
|
+
* Emitted only when non-empty — read it as `meta.propBindings ?? []`. Absent on
|
|
196
|
+
* bundles built before ARCH-883 F1.
|
|
197
|
+
*/
|
|
198
|
+
propBindings?: PropBinding[];
|
|
199
|
+
/**
|
|
200
|
+
* Name of the component whose props `PropBinding.prop` refers to — the nearest
|
|
201
|
+
* enclosing, module-level component function that destructures its props.
|
|
202
|
+
*
|
|
203
|
+
* Emitted only alongside `propBindings`. The consumer MUST check it against the
|
|
204
|
+
* usage component before offering an edit: a kit-internal helper component
|
|
205
|
+
* (`function TableRow({ row })`) yields bindings in ITS prop namespace, which is
|
|
206
|
+
* not the usage's, and routing those would write the wrong prop. Mismatch = gap.
|
|
207
|
+
*/
|
|
208
|
+
propOwner?: string | null;
|
|
209
|
+
}
|
|
210
|
+
|
|
30
211
|
interface ArchieDevToolsOptions {
|
|
31
212
|
/**
|
|
32
213
|
* Enable debug logging.
|
|
@@ -61,4 +242,4 @@ interface ArchieDevToolsPlugin {
|
|
|
61
242
|
}
|
|
62
243
|
declare function archieDevTools(options?: ArchieDevToolsOptions): ArchieDevToolsPlugin;
|
|
63
244
|
|
|
64
|
-
export { type EditorMetaPluginOptions, archieDevTools, babelPluginEditorMeta };
|
|
245
|
+
export { type EditorMeta, type EditorMetaPluginOptions, type PropBinding, archieDevTools, babelPluginEditorMeta };
|