@forgecart/designer-runtime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +166 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +51 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/loader-cjs/package.json +3 -0
  6. package/dist/loader-cjs/turbo-loader.d.ts +13 -0
  7. package/dist/loader-cjs/turbo-loader.js +190 -0
  8. package/dist/loader-cjs/turbo-loader.js.map +1 -0
  9. package/dist/protocol/element-reference-formatter.d.ts +28 -0
  10. package/dist/protocol/element-reference-formatter.js +60 -0
  11. package/dist/protocol/element-reference-formatter.js.map +1 -0
  12. package/dist/protocol/element.types.d.ts +75 -0
  13. package/dist/protocol/element.types.js +7 -0
  14. package/dist/protocol/element.types.js.map +1 -0
  15. package/dist/protocol/index.d.ts +5 -0
  16. package/dist/protocol/index.js +6 -0
  17. package/dist/protocol/index.js.map +1 -0
  18. package/dist/protocol/message-codec.d.ts +50 -0
  19. package/dist/protocol/message-codec.js +209 -0
  20. package/dist/protocol/message-codec.js.map +1 -0
  21. package/dist/protocol/message.types.d.ts +122 -0
  22. package/dist/protocol/message.types.js +2 -0
  23. package/dist/protocol/message.types.js.map +1 -0
  24. package/dist/protocol/protocol.constant.d.ts +30 -0
  25. package/dist/protocol/protocol.constant.js +31 -0
  26. package/dist/protocol/protocol.constant.js.map +1 -0
  27. package/dist/runtime/descriptor/element-descriptor-extractor.d.ts +38 -0
  28. package/dist/runtime/descriptor/element-descriptor-extractor.js +190 -0
  29. package/dist/runtime/descriptor/element-descriptor-extractor.js.map +1 -0
  30. package/dist/runtime/designer-runtime.d.ts +85 -0
  31. package/dist/runtime/designer-runtime.js +321 -0
  32. package/dist/runtime/designer-runtime.js.map +1 -0
  33. package/dist/runtime/guest-messenger.d.ts +37 -0
  34. package/dist/runtime/guest-messenger.js +41 -0
  35. package/dist/runtime/guest-messenger.js.map +1 -0
  36. package/dist/runtime/handshake-gate.d.ts +35 -0
  37. package/dist/runtime/handshake-gate.js +81 -0
  38. package/dist/runtime/handshake-gate.js.map +1 -0
  39. package/dist/runtime/highlight/highlight-overlay.d.ts +25 -0
  40. package/dist/runtime/highlight/highlight-overlay.js +80 -0
  41. package/dist/runtime/highlight/highlight-overlay.js.map +1 -0
  42. package/dist/runtime/region/dom-hit-tester.d.ts +28 -0
  43. package/dist/runtime/region/dom-hit-tester.js +45 -0
  44. package/dist/runtime/region/dom-hit-tester.js.map +1 -0
  45. package/dist/runtime/region/element-filter.d.ts +60 -0
  46. package/dist/runtime/region/element-filter.js +146 -0
  47. package/dist/runtime/region/element-filter.js.map +1 -0
  48. package/dist/runtime/region/region-sampler.d.ts +41 -0
  49. package/dist/runtime/region/region-sampler.js +100 -0
  50. package/dist/runtime/region/region-sampler.js.map +1 -0
  51. package/dist/runtime/region/spray-region-resolver.d.ts +53 -0
  52. package/dist/runtime/region/spray-region-resolver.js +113 -0
  53. package/dist/runtime/region/spray-region-resolver.js.map +1 -0
  54. package/dist/util/geometry.util.d.ts +29 -0
  55. package/dist/util/geometry.util.js +68 -0
  56. package/dist/util/geometry.util.js.map +1 -0
  57. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # @forgecart/designer-runtime
2
+
3
+ The guest half of ForgeCart's visual editor. It ships inside tenant Next.js
4
+ storefronts, boots **dormant**, and is activated exclusively over the iframe
5
+ `postMessage` API by the dashboard host. Once active it resolves spray-stroke
6
+ regions into DOM element descriptors (with `file:line:col` source attribution
7
+ stamped at build time), renders highlight overlays, and reports scroll /
8
+ resize / navigation so the host can keep its canvas in sync.
9
+
10
+ Three entrypoints:
11
+
12
+ | Subpath | Environment | Contents |
13
+ |---------|-------------|----------|
14
+ | `.` | Browser (ESM) | `installDesignerRuntime(): () => void` — the storefront mounts this once |
15
+ | `./protocol` | Anywhere (ESM, DOM-free) | Wire types, `MessageCodec`, `ElementReferenceFormatter`, protocol constants — the dashboard host and the agent import **only** this |
16
+ | `./turbo-loader` | Node (CJS) | The Turbopack/webpack loader that stamps `data-fc-source` onto host JSX elements in development |
17
+
18
+ The browser entrypoints have **zero runtime dependencies**; the loader
19
+ subpath depends on `@babel/parser` + `magic-string` only.
20
+
21
+ ## Wire protocol (v1, namespace `fc-designer`)
22
+
23
+ Every message carries the envelope `{ ns: 'fc-designer', v: 1 }`; anything
24
+ else on the message bus (payment SDKs, analytics, HMR) is foreign traffic and
25
+ ignored silently. All coordinates are **iframe-viewport CSS pixels** — the
26
+ guest is zoom-agnostic by construction.
27
+
28
+ ```
29
+ GUEST boots dormant ── READY (targetOrigin '*', empty payload) ──▶ HOST
30
+ HOST ── HELLO { nonce, protocolVersion } ──▶ GUEST nonce-gated, pins event.origin
31
+ GUEST ── HELLO_ACK { capabilities, url, viewport } ──▶ HOST state: handshaken
32
+ HOST ── ACTIVATE ──▶ GUEST ── ACTIVATED ──▶ HOST state: active
33
+ HOST ── REGION_SELECT { requestId, stroke, radius, maxElements } ──▶ GUEST
34
+ GUEST ── REGION_RESOLVED { requestId, elements, regionBounds, truncated } ──▶ HOST
35
+ HOST ── HIGHLIGHT_SET { refIds } / HIGHLIGHT_CLEAR ──▶ GUEST
36
+ HOST ── ELEMENT_BOUNDS_QUERY { requestId, refIds } ──▶ GUEST ── ELEMENT_BOUNDS ──▶ HOST
37
+ HOST ── PING ──▶ GUEST ── PONG { url, scroll } ──▶ HOST liveness
38
+ GUEST ── SCROLL / RESIZE (rAF-throttled), NAVIGATED ──▶ HOST while active
39
+ HOST ── DEACTIVATE ──▶ GUEST ── DEACTIVATED ──▶ HOST state: handshaken
40
+ GUEST ── ERROR { code, requestId, detail } ──▶ HOST post-handshake only
41
+ ```
42
+
43
+ Error codes: `INVALID_MESSAGE`, `NOT_ACTIVE`, `REGION_TOO_LARGE`,
44
+ `UNKNOWN_REF`, `INTERNAL` — a wire-level string union; the host maps them to
45
+ its own UX.
46
+
47
+ ### Activation security
48
+
49
+ - **Inert outside an iframe** — `window.parent === window` makes
50
+ `installDesignerRuntime` a no-op. The dormant footprint is exactly one
51
+ `message` listener and zero DOM nodes.
52
+ - **Capability nonce** — the host appends `#fcd=<uuid>` to the preview URL.
53
+ The fragment never reaches the server; the guest parks the nonce in
54
+ `sessionStorage` (survives iframe reloads) and strips only the `fcd` key
55
+ from the hash. Without the nonce, every HELLO is ignored silently.
56
+ - **Origin pinning** — a HELLO is accepted only from `window.parent` with a
57
+ real (non-`'null'`) origin and the exact nonce; the first success pins
58
+ `event.origin` and every later post targets that origin. A re-HELLO must
59
+ come from the pinned origin.
60
+ - **Reload recovery** — an iframe reload boots a fresh runtime that posts a
61
+ fresh READY beacon; the host re-handshakes and the nonce is still in
62
+ `sessionStorage`.
63
+
64
+ ### Region resolution
65
+
66
+ `REGION_SELECT` rasterizes the stroke into grid cells (cell size
67
+ `max(8, radius/2)`, budget 600 cells with coarsening back-off; padded bbox
68
+ beyond 4× the viewport area → `REGION_TOO_LARGE`), stack hit-tests every
69
+ cell center, drops non-meaningful elements (document chrome, invisible,
70
+ zero-area, > 85%-viewport wrappers, Next dev chrome), collapses
71
+ ancestor/descendant stacks by coverage, ranks by coverage then area, caps at
72
+ `min(maxElements ?? 12, 25)`, and stamps survivors with `data-fc-ref` for
73
+ later `HIGHLIGHT_SET` / `ELEMENT_BOUNDS_QUERY`. Stamps accumulate per
74
+ activation and are stripped on `DEACTIVATE`.
75
+
76
+ ## Template integration (tenant storefront)
77
+
78
+ `next.config.js` — run the stamping loader before SWC (Next 15.1 key;
79
+ renamed `turbopack.rules` in 15.3). `next build` ignores it on 15.1 and the
80
+ loader additionally self-gates on `NODE_ENV === 'development'`, so production
81
+ output is untouched:
82
+
83
+ ```js
84
+ module.exports = {
85
+ experimental: {
86
+ turbo: {
87
+ rules: {
88
+ 'src/**/*.{tsx,jsx}': {
89
+ loaders: [{ loader: '@forgecart/designer-runtime/turbo-loader' }],
90
+ },
91
+ },
92
+ },
93
+ },
94
+ };
95
+ ```
96
+
97
+ Mount the runtime last in the root layout's `<body>` via a `'use client'`
98
+ component so it survives App Router client navigations:
99
+
100
+ ```tsx
101
+ 'use client';
102
+ import { useEffect } from 'react';
103
+
104
+ export function ForgecartDesigner(): null {
105
+ useEffect(() => {
106
+ if (process.env.NODE_ENV !== 'development') return; // DCE'd from prod bundles
107
+ if (window.parent === window) return;
108
+ let dispose: (() => void) | undefined;
109
+ let cancelled = false;
110
+ void import('@forgecart/designer-runtime').then((mod) => {
111
+ if (cancelled) return;
112
+ dispose = mod.installDesignerRuntime();
113
+ });
114
+ return () => {
115
+ cancelled = true;
116
+ dispose?.();
117
+ };
118
+ }, []);
119
+ return null;
120
+ }
121
+ ```
122
+
123
+ `installDesignerRuntime` is idempotent across React StrictMode's double
124
+ effect: a second install while a runtime is live returns the live dispose,
125
+ and disposing clears the singleton.
126
+
127
+ ## Host integration (dashboard)
128
+
129
+ Import **only** `@forgecart/designer-runtime/protocol`. The host:
130
+
131
+ 1. appends `#fcd=<crypto.randomUUID()>` to the preview URL before setting the
132
+ iframe `src`;
133
+ 2. listens for `READY`, then posts `HELLO { nonce, protocolVersion }` to the
134
+ iframe's content window — and re-handshakes whenever a fresh `READY`
135
+ arrives (iframe reload);
136
+ 3. validates every inbound message with `MessageCodec.parseGuestMessage` and
137
+ checks `event.source` is the iframe's content window;
138
+ 4. correlates request/response pairs by `requestId` with a timeout;
139
+ 5. serializes selections for the first message of an anchored AI thread via
140
+ `ElementReferenceFormatter` (`<fc-selection>{json}</fc-selection>`).
141
+
142
+ ## Contingency: jsx-dev-runtime fallback
143
+
144
+ If a future Next/Turbopack release breaks `experimental.turbo.rules` for JS
145
+ loaders, the documented fallback is intercepting `jsx-dev-runtime`'s `jsxDEV`
146
+ source argument to inject the same attribute at render time. It is kept as a
147
+ contingency only — it is fragile against React 19.2 dropping `jsxDEV` source
148
+ args, while the loader approach owns its parse and survives React upgrades.
149
+
150
+ ## Develop, build, publish
151
+
152
+ ```bash
153
+ nx run designer-runtime:test # jsdom unit + loader integration suite
154
+ nx run designer-runtime:typecheck # lib + loader + spec tsconfigs
155
+ nx run designer-runtime:build # tsc ESM lib → dist/, tsc CJS loader → dist/loader-cjs/
156
+ nx run designer-runtime:publish # npm publish --access public (runs build first)
157
+ ```
158
+
159
+ The build stamps `dist/loader-cjs/package.json` with `{ "type": "commonjs" }`
160
+ so the loader subpath `require()`s correctly from this otherwise
161
+ `"type": "module"` package.
162
+
163
+ Publish order for real workspace pods: publish this package first (the
164
+ pod-image template warmup `npm install`s it through the Nexus npm proxy),
165
+ then bump + commit the storefront template / CLI, then rebuild the
166
+ workspace-pod image.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Boots the designer runtime inside a tenant storefront. Inert unless the
3
+ * page is actually embedded (`window.parent !== window`) — on a top-level
4
+ * page this returns a no-op dispose and registers nothing. When embedded,
5
+ * the runtime boots dormant (one `message` listener, a READY beacon, zero
6
+ * DOM nodes) and waits for the dashboard's nonce-gated HELLO.
7
+ *
8
+ * @returns a dispose function that fully tears the runtime down.
9
+ */
10
+ export declare function installDesignerRuntime(): () => void;
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ import { MessageCodec } from './protocol/message-codec.js';
2
+ import { ElementDescriptorExtractor } from './runtime/descriptor/element-descriptor-extractor.js';
3
+ import { DesignerRuntime } from './runtime/designer-runtime.js';
4
+ import { GuestMessenger } from './runtime/guest-messenger.js';
5
+ import { HandshakeGate } from './runtime/handshake-gate.js';
6
+ import { HighlightOverlay } from './runtime/highlight/highlight-overlay.js';
7
+ import { DomHitTester } from './runtime/region/dom-hit-tester.js';
8
+ import { ElementFilter } from './runtime/region/element-filter.js';
9
+ import { RegionSampler } from './runtime/region/region-sampler.js';
10
+ import { SprayRegionResolver } from './runtime/region/spray-region-resolver.js';
11
+ /**
12
+ * Module-level singleton handle. React StrictMode runs every effect twice
13
+ * (mount → cleanup → mount), and an App Router layout can re-render the
14
+ * mounting component without a real page load — so idempotency cannot live
15
+ * in component state. The module instance is the one thing shared across
16
+ * those mounts: a second install while a runtime is live returns the live
17
+ * dispose, and disposing clears the slot so the next install boots fresh.
18
+ */
19
+ let liveDispose = null;
20
+ const NOOP_DISPOSE = () => undefined;
21
+ /**
22
+ * Boots the designer runtime inside a tenant storefront. Inert unless the
23
+ * page is actually embedded (`window.parent !== window`) — on a top-level
24
+ * page this returns a no-op dispose and registers nothing. When embedded,
25
+ * the runtime boots dormant (one `message` listener, a READY beacon, zero
26
+ * DOM nodes) and waits for the dashboard's nonce-gated HELLO.
27
+ *
28
+ * @returns a dispose function that fully tears the runtime down.
29
+ */
30
+ export function installDesignerRuntime() {
31
+ if (typeof window === 'undefined' || window.parent === window)
32
+ return NOOP_DISPOSE;
33
+ if (liveDispose !== null)
34
+ return liveDispose;
35
+ const runtime = buildRuntime(window);
36
+ runtime.install();
37
+ const dispose = () => {
38
+ if (liveDispose !== dispose)
39
+ return;
40
+ liveDispose = null;
41
+ runtime.dispose();
42
+ };
43
+ liveDispose = dispose;
44
+ return dispose;
45
+ }
46
+ /** Composition root: wires the runtime object graph for a live window. */
47
+ function buildRuntime(win) {
48
+ const doc = win.document;
49
+ return new DesignerRuntime(win, new MessageCodec(), new HandshakeGate(win), new GuestMessenger(win.parent), new SprayRegionResolver(new RegionSampler(), new DomHitTester(doc), new ElementFilter(win), new ElementDescriptorExtractor(win), doc), new HighlightOverlay(doc));
50
+ }
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sDAAsD,CAAC;AAClG,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAEhF;;;;;;;GAOG;AACH,IAAI,WAAW,GAAwB,IAAI,CAAC;AAE5C,MAAM,YAAY,GAAG,GAAS,EAAE,CAAC,SAAS,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,YAAY,CAAC;IACnF,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IAE7C,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,EAAE,CAAC;IAElB,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,WAAW,KAAK,OAAO;YAAE,OAAO;QACpC,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC;IACF,WAAW,GAAG,OAAO,CAAC;IACtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC;IACzB,OAAO,IAAI,eAAe,CACxB,GAAG,EACH,IAAI,YAAY,EAAE,EAClB,IAAI,aAAa,CAAC,GAAG,CAAC,EACtB,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAC9B,IAAI,mBAAmB,CACrB,IAAI,aAAa,EAAE,EACnB,IAAI,YAAY,CAAC,GAAG,CAAC,EACrB,IAAI,aAAa,CAAC,GAAG,CAAC,EACtB,IAAI,0BAA0B,CAAC,GAAG,CAAC,EACnC,GAAG,CACJ,EACD,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,13 @@
1
+ /** Minimal surface of the webpack/Turbopack loader context this loader uses. */
2
+ interface TurboLoaderContext {
3
+ /** Absolute path of the module being loaded. */
4
+ resourcePath: string;
5
+ /** Absolute path of the project root; empty/undefined falls back to ''. */
6
+ rootContext?: string;
7
+ /** Marks the loader async and returns the completion callback. */
8
+ async(): (error: Error | null, content?: string, sourceMap?: object) => void;
9
+ }
10
+ declare const _default: ((this: TurboLoaderContext, source: string, map?: object) => void) & {
11
+ DATA_ATTRIBUTE: string;
12
+ };
13
+ export = _default;
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const node_path_1 = require("node:path");
6
+ const parser_1 = require("@babel/parser");
7
+ const magic_string_1 = __importDefault(require("magic-string"));
8
+ /**
9
+ * The attribute stamped on host JSX elements, value
10
+ * `<project-relative-file>:<line>:<col>:<ComponentName>`.
11
+ *
12
+ * This is a deliberate local duplicate of the protocol's `SOURCE_ATTRIBUTE`
13
+ * constant: the loader compiles standalone to CommonJS (`dist/loader-cjs/`,
14
+ * rootDir `src/loader`) and must not import from `src/protocol`, which
15
+ * belongs to the ESM browser build. The turbo-loader spec asserts the two
16
+ * constants are equal, so they cannot drift.
17
+ */
18
+ const DATA_ATTRIBUTE = 'data-fc-source';
19
+ /**
20
+ * Stamps `data-fc-source="<file>:<line>:<col>:<Component>"` onto every host
21
+ * (lowercase) JSX element of one TSX/JSX module via magic-string, tracking
22
+ * the enclosing component name through a hand-rolled recursive AST walk —
23
+ * no `@babel/traverse`, keeping the loader's dependency surface at
24
+ * `@babel/parser` + `magic-string`.
25
+ */
26
+ class JsxSourceStamper {
27
+ source;
28
+ relativePath;
29
+ magic;
30
+ stampCount = 0;
31
+ constructor(source, relativePath) {
32
+ this.source = source;
33
+ this.relativePath = relativePath;
34
+ this.magic = new magic_string_1.default(source);
35
+ }
36
+ /**
37
+ * Runs the stamping pass. Returns null when the module is left unchanged —
38
+ * either nothing needed stamping, or the module did not parse. A parse
39
+ * failure must never break the dev server: the file may be mid-edit under
40
+ * hot reload, and an unstamped module only costs source attribution, so
41
+ * the deliberate strategy is to pass every unparseable module through
42
+ * untouched.
43
+ */
44
+ stamp() {
45
+ let parsed;
46
+ try {
47
+ parsed = (0, parser_1.parse)(this.source, {
48
+ sourceType: 'module',
49
+ plugins: ['typescript', 'jsx'],
50
+ });
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ if (!this.isAstNode(parsed))
56
+ return null;
57
+ this.visit(parsed, null);
58
+ if (this.stampCount === 0)
59
+ return null;
60
+ return { code: this.magic.toString(), map: this.magic.generateMap({ hires: true }) };
61
+ }
62
+ visit(node, componentName) {
63
+ const enclosing = this.enclosingComponentName(node) ?? componentName;
64
+ if (node.type === 'JSXOpeningElement')
65
+ this.stampIfHostElement(node, enclosing);
66
+ for (const key of Object.keys(node)) {
67
+ if (key === 'loc')
68
+ continue;
69
+ this.visitChild(node[key], enclosing);
70
+ }
71
+ }
72
+ visitChild(value, componentName) {
73
+ if (Array.isArray(value)) {
74
+ for (const item of value) {
75
+ if (this.isAstNode(item))
76
+ this.visit(item, componentName);
77
+ }
78
+ return;
79
+ }
80
+ if (this.isAstNode(value))
81
+ this.visit(value, componentName);
82
+ }
83
+ /**
84
+ * A node opens a component scope when it is a `function Component()`
85
+ * declaration or a `const Component = () => ...` / `function` initializer
86
+ * — in both cases the identifier must be capitalized, React's own
87
+ * component convention.
88
+ */
89
+ enclosingComponentName(node) {
90
+ if (node.type === 'FunctionDeclaration') {
91
+ return this.capitalizedIdentifierName(node.id);
92
+ }
93
+ if (node.type !== 'VariableDeclarator')
94
+ return null;
95
+ if (!this.isAstNode(node.init))
96
+ return null;
97
+ if (node.init.type !== 'ArrowFunctionExpression' && node.init.type !== 'FunctionExpression') {
98
+ return null;
99
+ }
100
+ return this.capitalizedIdentifierName(node.id);
101
+ }
102
+ capitalizedIdentifierName(value) {
103
+ if (!this.isAstNode(value) || value.type !== 'Identifier')
104
+ return null;
105
+ const name = value.name;
106
+ if (typeof name !== 'string' || !/^[A-Z]/.test(name))
107
+ return null;
108
+ return name;
109
+ }
110
+ /**
111
+ * Stamps a JSXOpeningElement when it is a host element — a lowercase
112
+ * `JSXIdentifier` tag (`<div>`, `<my-widget>`); components, fragments,
113
+ * member expressions (`<Foo.Bar>`) and namespaced names stay untouched —
114
+ * and does not already carry the attribute (idempotency under double
115
+ * application). Line is 1-based and column converted to 1-based, both
116
+ * addressing the opening element's `<`.
117
+ */
118
+ stampIfHostElement(node, componentName) {
119
+ const name = node.name;
120
+ if (!this.isAstNode(name) || name.type !== 'JSXIdentifier')
121
+ return;
122
+ if (typeof name.name !== 'string' || !/^[a-z]/.test(name.name))
123
+ return;
124
+ if (this.hasDataAttribute(node))
125
+ return;
126
+ const position = this.startPosition(node);
127
+ const insertAt = name.end;
128
+ if (position === null || typeof insertAt !== 'number')
129
+ return;
130
+ const value = `${this.relativePath}:${String(position.line)}:${String(position.column + 1)}:${componentName ?? ''}`;
131
+ this.magic.appendLeft(insertAt, ` ${DATA_ATTRIBUTE}="${value.replace(/"/g, '')}"`);
132
+ this.stampCount++;
133
+ }
134
+ hasDataAttribute(openingElement) {
135
+ const attributes = openingElement.attributes;
136
+ if (!Array.isArray(attributes))
137
+ return false;
138
+ return attributes.some((attribute) => this.isDataAttribute(attribute));
139
+ }
140
+ isDataAttribute(attribute) {
141
+ if (!this.isAstNode(attribute) || attribute.type !== 'JSXAttribute')
142
+ return false;
143
+ const name = attribute.name;
144
+ return this.isAstNode(name) && name.type === 'JSXIdentifier' && name.name === DATA_ATTRIBUTE;
145
+ }
146
+ startPosition(node) {
147
+ const loc = node.loc;
148
+ if (typeof loc !== 'object' || loc === null || !('start' in loc))
149
+ return null;
150
+ const start = loc.start;
151
+ if (typeof start !== 'object' || start === null)
152
+ return null;
153
+ if (!('line' in start) || !('column' in start))
154
+ return null;
155
+ const { line, column } = start;
156
+ if (typeof line !== 'number' || typeof column !== 'number')
157
+ return null;
158
+ return { line, column };
159
+ }
160
+ isAstNode(value) {
161
+ return (typeof value === 'object' &&
162
+ value !== null &&
163
+ 'type' in value &&
164
+ typeof value.type === 'string');
165
+ }
166
+ }
167
+ /**
168
+ * Turbopack/webpack loader entry. Registered in the storefront template via
169
+ * `experimental.turbo.rules` so it runs before SWC compiles each TSX/JSX
170
+ * module — identically across the RSC, SSR and client layers, so server and
171
+ * client markup carry the same attributes and hydration never mismatches.
172
+ * Self-gates on `NODE_ENV === 'development'`: production builds pass every
173
+ * module through untouched.
174
+ */
175
+ const turboLoader = function (source, map) {
176
+ const callback = this.async();
177
+ if (process.env.NODE_ENV !== 'development') {
178
+ callback(null, source, map);
179
+ return;
180
+ }
181
+ const relativePath = (0, node_path_1.relative)(this.rootContext ?? '', this.resourcePath).replace(/\\/g, '/');
182
+ const result = new JsxSourceStamper(source, relativePath).stamp();
183
+ if (result === null) {
184
+ callback(null, source, map);
185
+ return;
186
+ }
187
+ callback(null, result.code, result.map);
188
+ };
189
+ module.exports = Object.assign(turboLoader, { DATA_ATTRIBUTE });
190
+ //# sourceMappingURL=turbo-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turbo-loader.js","sourceRoot":"","sources":["../../src/loader/turbo-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { ElementDescriptor } from './element.types.js';
2
+ /** Opens the machine-readable selection block inside a chat message. */
3
+ export declare const SELECTION_BLOCK_OPEN = "<fc-selection>";
4
+ /** Closes the machine-readable selection block inside a chat message. */
5
+ export declare const SELECTION_BLOCK_CLOSE = "</fc-selection>";
6
+ /**
7
+ * Serializes resolved element descriptors into the `<fc-selection>` text
8
+ * block that anchors a visual-editor thread. The block rides verbatim in the
9
+ * first chat message of the thread (the agent pipeline persists and
10
+ * rehydrates message text untouched), so this formatter is the single
11
+ * definition of the format for both the dashboard host and the agent.
12
+ */
13
+ export declare class ElementReferenceFormatter {
14
+ /**
15
+ * Builds the selection block for one spray selection. Stamped elements
16
+ * carry a `file:line:col` source pointer plus the enclosing component;
17
+ * unstamped elements fall back to their body-rooted DOM path.
18
+ */
19
+ toReferenceBlock(descriptors: readonly ElementDescriptor[], pageUrl: string): string;
20
+ /**
21
+ * Prepends the selection block to the user's text — the exact message
22
+ * shape the dashboard sends as the first message of an anchored thread.
23
+ */
24
+ buildSelectionMessage(block: string, userText: string): string;
25
+ private toReferenceEntry;
26
+ private formatSource;
27
+ private formatDomPath;
28
+ }
@@ -0,0 +1,60 @@
1
+ import { PROTOCOL_VERSION } from './protocol.constant.js';
2
+ /** Opens the machine-readable selection block inside a chat message. */
3
+ export const SELECTION_BLOCK_OPEN = '<fc-selection>';
4
+ /** Closes the machine-readable selection block inside a chat message. */
5
+ export const SELECTION_BLOCK_CLOSE = '</fc-selection>';
6
+ /**
7
+ * Serializes resolved element descriptors into the `<fc-selection>` text
8
+ * block that anchors a visual-editor thread. The block rides verbatim in the
9
+ * first chat message of the thread (the agent pipeline persists and
10
+ * rehydrates message text untouched), so this formatter is the single
11
+ * definition of the format for both the dashboard host and the agent.
12
+ */
13
+ export class ElementReferenceFormatter {
14
+ /**
15
+ * Builds the selection block for one spray selection. Stamped elements
16
+ * carry a `file:line:col` source pointer plus the enclosing component;
17
+ * unstamped elements fall back to their body-rooted DOM path.
18
+ */
19
+ toReferenceBlock(descriptors, pageUrl) {
20
+ const payload = {
21
+ version: PROTOCOL_VERSION,
22
+ pageUrl,
23
+ elements: descriptors.map((descriptor) => this.toReferenceEntry(descriptor)),
24
+ };
25
+ return `${SELECTION_BLOCK_OPEN}${JSON.stringify(payload)}${SELECTION_BLOCK_CLOSE}`;
26
+ }
27
+ /**
28
+ * Prepends the selection block to the user's text — the exact message
29
+ * shape the dashboard sends as the first message of an anchored thread.
30
+ */
31
+ buildSelectionMessage(block, userText) {
32
+ return `${block}\n${userText}`;
33
+ }
34
+ toReferenceEntry(descriptor) {
35
+ return {
36
+ ref: descriptor.refId,
37
+ tag: descriptor.tagName,
38
+ source: this.formatSource(descriptor),
39
+ component: descriptor.source?.componentName ?? null,
40
+ text: descriptor.text,
41
+ role: descriptor.role,
42
+ id: descriptor.id,
43
+ classes: descriptor.classList,
44
+ attributes: descriptor.attributes,
45
+ domPath: this.formatDomPath(descriptor.domPath),
46
+ };
47
+ }
48
+ formatSource(descriptor) {
49
+ if (descriptor.source === null)
50
+ return null;
51
+ const { file, line, column } = descriptor.source;
52
+ return `${file}:${String(line)}:${String(column)}`;
53
+ }
54
+ formatDomPath(segments) {
55
+ return segments
56
+ .map((segment) => `${segment.tag}:nth-of-type(${String(segment.nthOfType)})`)
57
+ .join(' > ');
58
+ }
59
+ }
60
+ //# sourceMappingURL=element-reference-formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-reference-formatter.js","sourceRoot":"","sources":["../../src/protocol/element-reference-formatter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAErD,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAuBvD;;;;;;GAMG;AACH,MAAM,OAAO,yBAAyB;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,WAAyC,EAAE,OAAe;QACzE,MAAM,OAAO,GAA0B;YACrC,OAAO,EAAE,gBAAgB;YACzB,OAAO;YACP,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;SAC7E,CAAC;QACF,OAAO,GAAG,oBAAoB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,qBAAqB,EAAE,CAAC;IACrF,CAAC;IAED;;;OAGG;IACH,qBAAqB,CAAC,KAAa,EAAE,QAAgB;QACnD,OAAO,GAAG,KAAK,KAAK,QAAQ,EAAE,CAAC;IACjC,CAAC;IAEO,gBAAgB,CAAC,UAA6B;QACpD,OAAO;YACL,GAAG,EAAE,UAAU,CAAC,KAAK;YACrB,GAAG,EAAE,UAAU,CAAC,OAAO;YACvB,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YACrC,SAAS,EAAE,UAAU,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI;YACnD,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,EAAE,EAAE,UAAU,CAAC,EAAE;YACjB,OAAO,EAAE,UAAU,CAAC,SAAS;YAC7B,UAAU,EAAE,UAAU,CAAC,UAAU;YACjC,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC;SAChD,CAAC;IACJ,CAAC;IAEO,YAAY,CAAC,UAA6B;QAChD,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC;QACjD,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IACrD,CAAC;IAEO,aAAa,CAAC,QAAmC;QACvD,OAAO,QAAQ;aACZ,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,GAAG,gBAAgB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC;aAC5E,IAAI,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * DOM-free element/geometry shapes that cross the iframe wire. All
3
+ * coordinates are iframe-viewport CSS pixels — the guest is zoom-agnostic by
4
+ * construction; the host owns world/zoom transforms.
5
+ */
6
+ /** A point in iframe-viewport CSS pixels. */
7
+ export interface Point {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ /** An axis-aligned rectangle in iframe-viewport CSS pixels. */
12
+ export interface Rect {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+ /** The iframe's visual viewport size in CSS pixels. */
19
+ export interface Viewport {
20
+ width: number;
21
+ height: number;
22
+ }
23
+ /** The iframe document's scroll offset in CSS pixels. */
24
+ export interface ScrollPosition {
25
+ x: number;
26
+ y: number;
27
+ }
28
+ /** One `tag:nth-of-type(n)` step of an element's DOM path (body-rooted). */
29
+ export interface DomPathSegment {
30
+ tag: string;
31
+ nthOfType: number;
32
+ }
33
+ /**
34
+ * A parsed `data-fc-source` stamp pointing back at the JSX opening element
35
+ * in the storefront source tree. `componentName` is null when the stamping
36
+ * loader could not determine an enclosing component.
37
+ */
38
+ export interface SourceRef {
39
+ file: string;
40
+ line: number;
41
+ column: number;
42
+ componentName: string | null;
43
+ }
44
+ /**
45
+ * Everything the host (and ultimately the AI agent) needs to know about one
46
+ * element resolved from a spray region. `refId` addresses the element in
47
+ * later HIGHLIGHT_SET / ELEMENT_BOUNDS_QUERY calls via its `data-fc-ref`
48
+ * attribute; it is only stable until DEACTIVATE strips the stamps.
49
+ */
50
+ export interface ElementDescriptor {
51
+ refId: string;
52
+ tagName: string;
53
+ id: string | null;
54
+ classList: string[];
55
+ domPath: DomPathSegment[];
56
+ source: SourceRef | null;
57
+ text: string | null;
58
+ role: string | null;
59
+ attributes: Record<string, string>;
60
+ bounds: Rect;
61
+ coverage: number;
62
+ childCount: number;
63
+ }
64
+ /** What the guest runtime can do for this page, reported in HELLO_ACK. */
65
+ export interface GuestCapabilities {
66
+ /** True when at least one `data-fc-source` stamp exists in the document. */
67
+ stamping: boolean;
68
+ /** True when the highlight overlay is available. */
69
+ highlight: boolean;
70
+ }
71
+ /** Per-ref answer of an ELEMENT_BOUNDS_QUERY; `bounds` is null for stale refs. */
72
+ export interface ElementBoundsEntry {
73
+ refId: string;
74
+ bounds: Rect | null;
75
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * DOM-free element/geometry shapes that cross the iframe wire. All
3
+ * coordinates are iframe-viewport CSS pixels — the guest is zoom-agnostic by
4
+ * construction; the host owns world/zoom transforms.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=element.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.types.js","sourceRoot":"","sources":["../../src/protocol/element.types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,5 @@
1
+ export * from './protocol.constant.js';
2
+ export * from './element.types.js';
3
+ export * from './message.types.js';
4
+ export * from './message-codec.js';
5
+ export * from './element-reference-formatter.js';
@@ -0,0 +1,6 @@
1
+ export * from './protocol.constant.js';
2
+ export * from './element.types.js';
3
+ export * from './message.types.js';
4
+ export * from './message-codec.js';
5
+ export * from './element-reference-formatter.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/protocol/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kCAAkC,CAAC"}