@archie/devtools 0.0.22 → 0.0.23

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 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
@@ -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 ?? null);
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 ?? null;
5098
+ return readEditorMetaValue(owner.memoizedProps?.__editorMeta);
5093
5099
  }
5094
5100
  function isRuntimeVisualInternal(el) {
5095
5101
  if (shouldIgnoreAsVirtualRuntime(el)) return true;
@@ -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 ?? null);
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 ?? null;
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 };