@archie/devtools 0.0.21 → 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/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 };