nudge-ui 0.2.0 → 0.2.1

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 (201) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +872 -12
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +60 -11
  7. package/dist/hosts/next/sidecar.d.ts +4 -0
  8. package/dist/hosts/next/sidecar.js +49 -6
  9. package/dist/hosts/next/wrapper.js +30 -2
  10. package/dist/hosts/projectBridge.d.ts +20 -0
  11. package/dist/hosts/projectBridge.js +97 -0
  12. package/dist/hosts/static/cli.d.ts +1 -1
  13. package/dist/hosts/static/cli.js +6 -1
  14. package/dist/hosts/static/manifest.d.ts +1 -1
  15. package/dist/hosts/static/manifest.js +1 -1
  16. package/dist/hosts/static/server.js +44 -3
  17. package/dist/hosts/vite/vite.d.ts +5 -1
  18. package/dist/hosts/vite/vite.js +78 -14
  19. package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
  20. package/dist/inspector/agent/McpConnectionDialog.js +11 -35
  21. package/dist/inspector/agent/client.d.ts +10 -3
  22. package/dist/inspector/agent/client.js +80 -9
  23. package/dist/inspector/agent/connectionStatus.js +3 -3
  24. package/dist/inspector/agent/httpTransport.d.ts +2 -0
  25. package/dist/inspector/agent/httpTransport.js +37 -17
  26. package/dist/inspector/agent/protocol.d.ts +8 -3
  27. package/dist/inspector/agent/protocol.js +1 -1
  28. package/dist/inspector/agent/verification.d.ts +1 -1
  29. package/dist/inspector/agent/verification.js +69 -12
  30. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  31. package/dist/inspector/canvas/CanvasCard.js +181 -91
  32. package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
  33. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  34. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  35. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  36. package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
  37. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  38. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  39. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  40. package/dist/inspector/canvas/canvasResize.js +52 -0
  41. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  42. package/dist/inspector/canvas/canvasStore.js +128 -61
  43. package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
  44. package/dist/inspector/canvas/frameProtocol.js +56 -2
  45. package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
  46. package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
  47. package/dist/inspector/canvas/projection.d.ts +7 -0
  48. package/dist/inspector/canvas/projection.js +118 -25
  49. package/dist/inspector/canvas/rendererBootstrap.js +29 -29
  50. package/dist/inspector/canvas/rendererElementSelector.js +341 -30
  51. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  52. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  53. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  54. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  55. package/dist/inspector/canvas/sessionStore.js +12 -4
  56. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  57. package/dist/inspector/changes/changesLog.js +31 -1
  58. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  59. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  60. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  61. package/dist/inspector/clientManifest.d.ts +5 -0
  62. package/dist/inspector/clientManifest.js +11 -0
  63. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  64. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  65. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  66. package/dist/inspector/index.d.ts +2 -0
  67. package/dist/inspector/index.js +74 -24
  68. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  69. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  70. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  71. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  72. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  73. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  74. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  75. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  76. package/dist/inspector/inline-text/textEditChange.js +165 -0
  77. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  78. package/dist/inspector/inspection/selectionProperty.js +1 -0
  79. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  80. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  81. package/dist/inspector/overlay/spacingGestures.js +353 -0
  82. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  83. package/dist/inspector/overlay/structuralGestures.js +12 -10
  84. package/dist/inspector/projection/managedStylesheet.js +4 -1
  85. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  86. package/dist/inspector/projection/renderedInstance.js +5 -1
  87. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  88. package/dist/inspector/projection/structuralProjection.js +85 -1
  89. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  90. package/dist/inspector/projection/textProjection.d.ts +2 -0
  91. package/dist/inspector/projection/textProjection.js +10 -4
  92. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  93. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  94. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  95. package/dist/inspector/prompt/generatePrompt.js +19 -2
  96. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  97. package/dist/inspector/runtime/editorShell.js +4 -0
  98. package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
  99. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  100. package/dist/inspector/selection/selectionStore.js +2 -0
  101. package/dist/inspector/settings/SettingsDialog.js +10 -0
  102. package/dist/inspector/shell/AppShell.js +5 -1
  103. package/dist/inspector/shell/ChangesLog.js +8 -4
  104. package/dist/inspector/shell/CopyPromptButton.js +95 -19
  105. package/dist/inspector/shell/EmptyState.js +1 -1
  106. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  107. package/dist/inspector/shell/InspectorShell.js +37 -38
  108. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  109. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  110. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  111. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  112. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  113. package/dist/inspector/shell/originalPreview.js +61 -0
  114. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  115. package/dist/inspector/shell/shortcuts.js +15 -0
  116. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  117. package/dist/inspector/sketch/SketchChanges.js +72 -0
  118. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  119. package/dist/inspector/sketch/SketchEditor.js +402 -0
  120. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  121. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  122. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  123. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  124. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  125. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  126. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  127. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  128. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  129. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  130. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  131. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  132. package/dist/inspector/sketch/capture.d.ts +55 -0
  133. package/dist/inspector/sketch/capture.js +350 -0
  134. package/dist/inspector/sketch/freehand.d.ts +13 -0
  135. package/dist/inspector/sketch/freehand.js +64 -0
  136. package/dist/inspector/sketch/geometry.d.ts +17 -0
  137. package/dist/inspector/sketch/geometry.js +98 -0
  138. package/dist/inspector/sketch/handoff.d.ts +21 -0
  139. package/dist/inspector/sketch/handoff.js +72 -0
  140. package/dist/inspector/sketch/interaction.d.ts +21 -0
  141. package/dist/inspector/sketch/interaction.js +98 -0
  142. package/dist/inspector/sketch/model.d.ts +104 -0
  143. package/dist/inspector/sketch/model.js +282 -0
  144. package/dist/inspector/sketch/persistence.d.ts +21 -0
  145. package/dist/inspector/sketch/persistence.js +205 -0
  146. package/dist/inspector/sketch/raster.d.ts +22 -0
  147. package/dist/inspector/sketch/raster.js +203 -0
  148. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  149. package/dist/inspector/sketch/sketchNote.js +30 -0
  150. package/dist/inspector/sketch/store.d.ts +47 -0
  151. package/dist/inspector/sketch/store.js +409 -0
  152. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  153. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  154. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  155. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  156. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  157. package/dist/inspector/styleEditors/GapField.js +2 -2
  158. package/dist/inspector/styleEditors/GridSection.js +1 -1
  159. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  160. package/dist/inspector/styleEditors/SpacingBox.js +23 -30
  161. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  162. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  163. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  164. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  165. package/dist/inspector/styleEditors/nudgeValue.js +91 -5
  166. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  167. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  168. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  169. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  170. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  171. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  172. package/dist/inspector/tokens/TokenField.js +111 -7
  173. package/dist/inspector/tokens/editActions.d.ts +2 -0
  174. package/dist/inspector/tokens/editActions.js +2 -2
  175. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  176. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  177. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  178. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  179. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  180. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  181. package/dist/inspector/tokens/resolution.d.ts +3 -26
  182. package/dist/inspector/tokens/resolution.js +44 -735
  183. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  184. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  185. package/dist/inspector/ui/Button.d.ts +1 -1
  186. package/dist/inspector/ui/SideValuesField.js +2 -2
  187. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  188. package/dist/inspector/ui/StatusCallout.js +2 -2
  189. package/dist/inspector/ui/styles.js +13 -11
  190. package/dist/internal/agent-protocol.d.ts +65 -1
  191. package/dist/internal/agent-protocol.js +183 -3
  192. package/dist/nudge-ui.mjs +261 -57
  193. package/dist/transport/editor.d.ts +28 -0
  194. package/dist/transport/editor.js +132 -0
  195. package/dist/transport/index.d.ts +1 -0
  196. package/dist/transport/index.js +1 -0
  197. package/dist/transport/routes.d.ts +7 -0
  198. package/dist/transport/routes.js +7 -0
  199. package/package.json +3 -1
  200. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  201. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -12,7 +12,7 @@ export function createAstroRuntimeConfig(knowledge) {
12
12
  framework: "Astro",
13
13
  stylingSystem: detectStylingSystem(knowledge.tokens),
14
14
  capabilities: {
15
- canvas: false,
15
+ canvas: true,
16
16
  componentSemantics: true,
17
17
  sourceCoordinates: ASTRO_SOURCE_COORDINATES,
18
18
  scopingSelectorPattern: ASTRO_SCOPING_SELECTOR_PATTERN,
@@ -1,10 +1,12 @@
1
1
  import {} from "../../inspector/clientManifest.js";
2
2
  import { createAstroRuntimeConfig } from "./astroRuntimeConfig.js";
3
3
  import { createAstroClientAssetHandler } from "./clientAsset.js";
4
- import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH, } from "../../transport/index.js";
4
+ import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, createNudgeUiEditorDocument, isNudgeUiEditorDocumentRequest, } from "../../transport/index.js";
5
+ import { startOptionalProjectBridge } from "../projectBridge.js";
5
6
  const serveClient = createAstroClientAssetHandler();
6
7
  /** Serves the prebuilt client and its host-neutral manifest in Astro dev. */
7
8
  export function createAstroClientTransportPlugin() {
9
+ let projectBridge = null;
8
10
  return {
9
11
  name: "nudge-ui-astro-client-transport",
10
12
  // The shared Vite plugin exposes the same route for React hosts. Astro's
@@ -15,12 +17,27 @@ export function createAstroClientTransportPlugin() {
15
17
  configureServer(server) {
16
18
  server.middlewares.use((request, response, next) => {
17
19
  const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
20
+ if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
21
+ if (request.method !== "GET" && request.method !== "HEAD") {
22
+ response.statusCode = 405;
23
+ response.setHeader("Allow", "GET, HEAD");
24
+ response.end();
25
+ return;
26
+ }
27
+ response.statusCode = 200;
28
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
29
+ response.setHeader("Cache-Control", "no-store");
30
+ response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
31
+ return;
32
+ }
18
33
  if (pathname === NUDGE_UI_CLIENT_PATH) {
19
34
  void serveClient(request, response).catch(next);
20
35
  return;
21
36
  }
22
37
  if (pathname === NUDGE_UI_MANIFEST_PATH) {
23
- void createManifest(server)
38
+ void createManifest(server, request, (bridge) => {
39
+ projectBridge = bridge;
40
+ })
24
41
  .then((manifest) => {
25
42
  response.statusCode = 200;
26
43
  response.setHeader("Content-Type", "application/json; charset=utf-8");
@@ -33,25 +50,57 @@ export function createAstroClientTransportPlugin() {
33
50
  next();
34
51
  });
35
52
  },
53
+ async closeBundle() {
54
+ await projectBridge?.close();
55
+ projectBridge = null;
56
+ },
36
57
  };
37
58
  }
38
- async function createManifest(server) {
59
+ async function createManifest(server, request, setBridge) {
39
60
  // SAFETY: these virtual modules are generated by the shared Nudge UI plugin
40
61
  // with the exact exports described by TokenModule and ComponentModule.
41
62
  const [tokenModule, componentModule] = await Promise.all([
42
63
  server.ssrLoadModule("virtual:design-tokens"),
43
64
  server.ssrLoadModule("virtual:nudge-ui-components"),
44
65
  ]);
66
+ const runtime = createAstroRuntimeConfig({
67
+ projectId: tokenModule.nudgeUiProjectId,
68
+ tokenCatalog: tokenModule.tokenCatalog,
69
+ tokens: tokenModule.tokens,
70
+ tokenDiagnostics: tokenModule.tokenDiagnostics,
71
+ tokenGeneration: tokenModule.tokenGeneration,
72
+ componentContracts: componentModule.componentContracts,
73
+ });
74
+ const origin = resolvedRequestOrigin(server, request);
75
+ const bridge = origin
76
+ ? await startOptionalProjectBridge({
77
+ appRoot: server.config.root,
78
+ projectId: runtime.projectId,
79
+ origin,
80
+ warn: (message) => server.config.logger.warn(message),
81
+ })
82
+ : null;
83
+ setBridge(bridge);
45
84
  return {
46
85
  version: 1,
47
86
  revision: 0,
48
- runtime: createAstroRuntimeConfig({
49
- projectId: tokenModule.nudgeUiProjectId,
50
- tokenCatalog: tokenModule.tokenCatalog,
51
- tokens: tokenModule.tokens,
52
- tokenDiagnostics: tokenModule.tokenDiagnostics,
53
- tokenGeneration: tokenModule.tokenGeneration,
54
- componentContracts: componentModule.componentContracts,
55
- }),
87
+ runtime,
88
+ ...(bridge ? { agentBridge: bridge.browser } : {}),
56
89
  };
57
90
  }
91
+ function resolvedRequestOrigin(server, request) {
92
+ const host = request.headers.host;
93
+ if (!host)
94
+ return null;
95
+ const candidates = [
96
+ ...(server.resolvedUrls?.local ?? []),
97
+ ...(server.resolvedUrls?.network ?? []),
98
+ ].map((url) => new URL(url).origin);
99
+ const protocols = new Set(candidates.map((candidate) => new URL(candidate).protocol));
100
+ for (const protocol of protocols) {
101
+ const requested = new URL(`${protocol}//${host}`).origin;
102
+ if (candidates.includes(requested))
103
+ return requested;
104
+ }
105
+ return null;
106
+ }
@@ -17,6 +17,10 @@ export interface SidecarOptions {
17
17
  * add/change/remove transitions. Diagnostics never disable inspection.
18
18
  */
19
19
  tokens?: boolean;
20
+ /** Trusted browser origin established by the Next development process. */
21
+ projectBridgeOrigin?: string;
22
+ /** Canonical loopback aliases for the known development port. */
23
+ projectBridgeAllowedOrigins?: readonly string[];
20
24
  }
21
25
  /**
22
26
  * Starts (or returns) the sidecar for this dev-server process.
@@ -7,7 +7,8 @@ import { buildManifest, } from "./manifest.js";
7
7
  import { extractComponentContracts } from "../../compiler/componentContracts.js";
8
8
  import { createProjectFileWatcher, } from "../../project/files.js";
9
9
  import { createProjectTokenSnapshot } from "../../project/tokens.js";
10
- import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
10
+ import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, createNudgeUiEditorDocument, isNudgeUiEditorDocumentRequest, } from "../../transport/index.js";
11
+ import { startOptionalProjectBridge } from "../projectBridge.js";
11
12
  /**
12
13
  * Loopback-only manifest/reload sidecar (ADR-0010).
13
14
  *
@@ -221,7 +222,7 @@ export async function ensureSidecar(root, options = {}) {
221
222
  // derive every filesystem-relative computation from the canonical root.
222
223
  const fsRoot = await realpath(root).catch(() => root);
223
224
  const scanRoots = await tokenScanRoots(fsRoot, options.sourceRoots);
224
- const state = globalState(`${options.tokens ? "tokens" : "manifest"}:${fsRoot}:${scanRoots.join("|")}`);
225
+ const state = globalState(`${options.tokens ? "tokens" : "manifest"}:${options.projectBridgeOrigin ?? "plain"}:${fsRoot}:${scanRoots.join("|")}`);
225
226
  // The settled promise doubles as the handle cache: awaiting it replays the
226
227
  // same handle for every caller until close/failure clears the slot.
227
228
  if (state.starting)
@@ -249,7 +250,35 @@ export async function ensureSidecar(root, options = {}) {
249
250
  stream.write(`data: ${JSON.stringify({ revision: generation })}\n\n`);
250
251
  }
251
252
  };
252
- const manifest = options.manifest ?? buildManifest({ root });
253
+ let manifest = options.manifest ?? buildManifest({ root });
254
+ let projectBridge = null;
255
+ let projectBridgeStart = null;
256
+ const ensureProjectBridge = () => {
257
+ if (!options.projectBridgeOrigin || projectBridge || projectBridgeStart) {
258
+ return projectBridgeStart ?? Promise.resolve();
259
+ }
260
+ projectBridgeStart = startOptionalProjectBridge({
261
+ appRoot: fsRoot,
262
+ projectId: manifest.runtime.projectId,
263
+ origin: options.projectBridgeOrigin,
264
+ ...(options.projectBridgeAllowedOrigins
265
+ ? { allowedOrigins: options.projectBridgeAllowedOrigins }
266
+ : {}),
267
+ warn: (message) => console.warn(message),
268
+ }).then((bridge) => {
269
+ projectBridge = bridge;
270
+ if (bridge)
271
+ manifest = { ...manifest, agentBridge: bridge.browser };
272
+ });
273
+ return projectBridgeStart;
274
+ };
275
+ const closeProjectBridge = async () => {
276
+ await projectBridgeStart;
277
+ const bridge = projectBridge;
278
+ projectBridge = null;
279
+ projectBridgeStart = null;
280
+ await bridge?.close();
281
+ };
253
282
  const additionalTokenRoots = scanRoots.filter((scanRoot) => scanRoot !== fsRoot);
254
283
  const tokenSnapshotOptions = {
255
284
  rootDirectory: fsRoot,
@@ -290,6 +319,7 @@ export async function ensureSidecar(root, options = {}) {
290
319
  // The port file is diagnostics-only; the rewrite carries the real
291
320
  // transport, so an unwritable .next must not break instrumentation.
292
321
  }
322
+ await ensureProjectBridge();
293
323
  const watchers = [];
294
324
  // The startup source scan runs regardless of the token lifecycle so a
295
325
  // restarted sidecar republishes contracts even when Turbopack's
@@ -425,7 +455,7 @@ export async function ensureSidecar(root, options = {}) {
425
455
  // close. Closing twice must not throw.
426
456
  if (!server.listening) {
427
457
  state.starting = undefined;
428
- return Promise.resolve();
458
+ return closeProjectBridge();
429
459
  }
430
460
  for (const stream of streams)
431
461
  stream.end();
@@ -433,9 +463,13 @@ export async function ensureSidecar(root, options = {}) {
433
463
  void Promise.all(watchers.map((watcher) => watcher.close().catch(() => { })));
434
464
  rmSync(portFilePath(root), { force: true });
435
465
  state.starting = undefined;
436
- return new Promise((resolveClose, rejectClose) => {
466
+ const closeServer = new Promise((resolveClose, rejectClose) => {
437
467
  server.close((error) => (error ? rejectClose(error) : resolveClose()));
438
468
  });
469
+ return Promise.all([
470
+ closeServer,
471
+ closeProjectBridge(),
472
+ ]).then(() => undefined);
439
473
  },
440
474
  };
441
475
  return handle;
@@ -452,7 +486,8 @@ export async function ensureSidecar(root, options = {}) {
452
486
  return starting;
453
487
  }
454
488
  function respond(req, res, currentManifest, currentGeneration, streams, receiveContracts) {
455
- const url = (req.url ?? "").split("?")[0];
489
+ const requestUrl = req.url ?? "";
490
+ const url = requestUrl.split("?")[0];
456
491
  // Loader postings aggregate component contracts (Stage 5). Loopback-only by
457
492
  // virtue of the bind address; payload size is capped defensively.
458
493
  if (url === `${NUDGE_UI_ROUTE_PREFIX}contracts` && req.method === "POST") {
@@ -479,6 +514,14 @@ function respond(req, res, currentManifest, currentGeneration, streams, receiveC
479
514
  });
480
515
  return;
481
516
  }
517
+ if (isNudgeUiEditorDocumentRequest(requestUrl, req.method, req.headers)) {
518
+ res.writeHead(200, {
519
+ "content-type": "text/html; charset=utf-8",
520
+ "cache-control": "no-store",
521
+ });
522
+ res.end(req.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
523
+ return;
524
+ }
482
525
  if (req.method !== "GET") {
483
526
  res.writeHead(405).end();
484
527
  return;
@@ -4,7 +4,7 @@ import { dirname, join, relative, resolve } from "node:path";
4
4
  import { ensureSidecar } from "./sidecar.js";
5
5
  import { buildManifest } from "./manifest.js";
6
6
  import { nudgeUiRepositoryPackagePath } from "./repositoryScope.js";
7
- import { NUDGE_UI_ROUTE_PREFIX } from "../../transport/index.js";
7
+ import { NUDGE_UI_EDITOR_QUERY_PARAM, NUDGE_UI_EDITOR_QUERY_VALUE, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
8
8
  const NAMESPACE_PREFIX = NUDGE_UI_ROUTE_PREFIX.slice(0, -1);
9
9
  const SUPPORTED_NEXT_RANGE = ">=15.3 <17";
10
10
  /**
@@ -119,12 +119,15 @@ function instrumentConfig(config, options) {
119
119
  return config;
120
120
  }
121
121
  const sourceRoots = options.sourceRoots?.map((sourceRoot) => resolve(root, sourceRoot));
122
+ const bridgeOrigin = nextDevelopmentOrigin();
122
123
  // ensureSidecar is a per-process singleton; awaiting it wherever the port
123
124
  // is needed removes any config-evaluation race. The same authored roots
124
125
  // reach token discovery so workspace CSS is visible to both integrations.
125
126
  const sidecarPort = () => ensureSidecar(root, {
126
127
  manifest: buildManifest({ root }),
127
128
  tokens: true,
129
+ projectBridgeOrigin: bridgeOrigin.origin,
130
+ projectBridgeAllowedOrigins: bridgeOrigin.allowedOrigins,
128
131
  ...(sourceRoots ? { sourceRoots } : {}),
129
132
  })
130
133
  .then((handle) => handle.port)
@@ -280,6 +283,14 @@ function instrumentConfig(config, options) {
280
283
  source: `${NUDGE_UI_ROUTE_PREFIX}:path*`,
281
284
  destination: `http://127.0.0.1:${port}${NUDGE_UI_ROUTE_PREFIX}:path*`,
282
285
  });
286
+ const editorRewrite = (port, header) => ({
287
+ source: "/:path*",
288
+ has: [
289
+ { type: "query", key: NUDGE_UI_EDITOR_QUERY_PARAM, value: NUDGE_UI_EDITOR_QUERY_VALUE },
290
+ { type: "header", ...header },
291
+ ],
292
+ destination: `http://127.0.0.1:${port}/:path*`,
293
+ });
283
294
  const resolveRewrites = async () => {
284
295
  const port = await sidecarPort();
285
296
  const userRewrites = typeof source.rewrites === "function"
@@ -297,7 +308,12 @@ function instrumentConfig(config, options) {
297
308
  // Port 0 (sidecar failed to bind) yields a dead destination; the mount
298
309
  // surfaces an honest fetch failure instead of silently pretending to
299
310
  // work.
300
- beforeFiles: [proxyRewrite(port), ...(shape.beforeFiles ?? [])],
311
+ beforeFiles: [
312
+ proxyRewrite(port),
313
+ editorRewrite(port, { key: "accept", value: ".*text/html.*" }),
314
+ editorRewrite(port, { key: "sec-fetch-dest", value: "document" }),
315
+ ...(shape.beforeFiles ?? []),
316
+ ],
301
317
  afterFiles: shape.afterFiles ?? [],
302
318
  fallback: shape.fallback ?? [],
303
319
  };
@@ -308,3 +324,15 @@ function instrumentConfig(config, options) {
308
324
  // consumers keep their NextConfig typing via the generic passthrough.
309
325
  return nextConfig;
310
326
  }
327
+ function nextDevelopmentOrigin() {
328
+ const selected = process.env.__NEXT_PRIVATE_ORIGIN
329
+ ?? `http://localhost:${process.env.PORT ?? "3000"}`;
330
+ const url = new URL(selected);
331
+ const port = url.port || (url.protocol === "https:" ? "443" : "80");
332
+ const aliases = ["localhost", "127.0.0.1", "[::1]"]
333
+ .map((host) => `${url.protocol}//${host}:${port}`);
334
+ return {
335
+ origin: url.origin,
336
+ allowedOrigins: [...new Set([url.origin, ...aliases])],
337
+ };
338
+ }
@@ -0,0 +1,20 @@
1
+ export interface ProjectBridgeBrowserConfig {
2
+ readonly baseUrl: string;
3
+ readonly autoConnect: true;
4
+ }
5
+ /** Starts the optional project bridge installed beside the host application. */
6
+ export declare function startOptionalProjectBridge(input: {
7
+ readonly appRoot: string;
8
+ readonly projectId: string;
9
+ readonly origin: string;
10
+ readonly allowedOrigins?: readonly string[];
11
+ readonly warn?: (message: string) => void;
12
+ }): Promise<{
13
+ browser: ProjectBridgeBrowserConfig;
14
+ close(): Promise<void>;
15
+ } | null>;
16
+ /** Returns the browser origin for a loopback HTTP server address. */
17
+ export declare function loopbackOrigin(address: {
18
+ address: string;
19
+ port: number;
20
+ }, protocol?: "http" | "https"): string;
@@ -0,0 +1,97 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync } from "node:fs";
10
+ import { createRequire } from "node:module";
11
+ import { dirname, join, parse } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ const runningBridges = new Map();
14
+ /** Starts the optional project bridge installed beside the host application. */
15
+ export async function startOptionalProjectBridge(input) {
16
+ const key = `${input.appRoot}\u0000${input.origin}`;
17
+ const running = runningBridges.get(key);
18
+ if (running)
19
+ return running;
20
+ const starting = launchOptionalProjectBridge(input, key);
21
+ runningBridges.set(key, starting);
22
+ void starting.then((bridge) => {
23
+ if (bridge === null && runningBridges.get(key) === starting)
24
+ runningBridges.delete(key);
25
+ });
26
+ return starting;
27
+ }
28
+ async function launchOptionalProjectBridge(input, key) {
29
+ let modulePath;
30
+ try {
31
+ if (!isPnpRuntime() && !hasProjectInstalledMcp(input.appRoot)) {
32
+ runningBridges.delete(key);
33
+ return null;
34
+ }
35
+ modulePath = createRequire(join(input.appRoot, "package.json")).resolve("@nudge-ui/mcp/project");
36
+ }
37
+ catch {
38
+ runningBridges.delete(key);
39
+ return null;
40
+ }
41
+ try {
42
+ const loaded = await import(__rewriteRelativeImportExtension(pathToFileURL(modulePath).href));
43
+ if (typeof loaded.startProjectBridge !== "function") {
44
+ throw new Error("the project bridge export is unavailable");
45
+ }
46
+ const runtime = await loaded.startProjectBridge({
47
+ appRoot: input.appRoot,
48
+ projectId: input.projectId,
49
+ origin: input.origin,
50
+ ...(input.allowedOrigins ? { allowedOrigins: input.allowedOrigins } : {}),
51
+ });
52
+ if (runtime.browser.projectId !== input.projectId || runtime.browser.origin !== input.origin) {
53
+ await runtime.close().catch(() => undefined);
54
+ throw new Error("the project bridge returned a different project or origin");
55
+ }
56
+ let closed = false;
57
+ return {
58
+ browser: { baseUrl: runtime.browser.bridgeUrl, autoConnect: true },
59
+ close: async () => {
60
+ if (closed)
61
+ return;
62
+ closed = true;
63
+ runningBridges.delete(key);
64
+ await runtime.close();
65
+ },
66
+ };
67
+ }
68
+ catch (error) {
69
+ const detail = error instanceof Error ? error.message : String(error);
70
+ input.warn?.(`[nudge-ui] MCP project bridge failed to start: ${detail}`);
71
+ runningBridges.delete(key);
72
+ return null;
73
+ }
74
+ }
75
+ function isPnpRuntime() {
76
+ return typeof process.versions.pnp === "string";
77
+ }
78
+ function hasProjectInstalledMcp(appRoot) {
79
+ let directory = appRoot;
80
+ const root = parse(directory).root;
81
+ for (;;) {
82
+ if (existsSync(join(directory, "node_modules", "@nudge-ui", "mcp", "package.json")))
83
+ return true;
84
+ if (directory === root)
85
+ return false;
86
+ directory = dirname(directory);
87
+ }
88
+ }
89
+ /** Returns the browser origin for a loopback HTTP server address. */
90
+ export function loopbackOrigin(address, protocol = "http") {
91
+ const host = address.address === "::" || address.address === "::1"
92
+ ? "[::1]"
93
+ : address.address === "0.0.0.0"
94
+ ? "127.0.0.1"
95
+ : address.address;
96
+ return `${protocol}://${host}:${address.port}`;
97
+ }
@@ -9,4 +9,4 @@ export declare function parseServeArguments(args: readonly string[], currentDire
9
9
  /** Starts the CLI server and waits until the process receives a termination signal. */
10
10
  export declare function runServeCommand(options: ServeCommandOptions): Promise<void>;
11
11
  /** Runs the standalone command. */
12
- export declare function main(args?: string[]): Promise<void>;
12
+ export declare function main(args?: string[], packageVersion?: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { runAgentCommand } from "../../cli/agentCommand.js";
2
3
  import { createStandaloneServer } from "./server.js";
3
4
  /** Parses the standalone CLI's intentionally small command surface. */
4
5
  export function parseServeArguments(args, currentDirectory = process.cwd()) {
@@ -44,7 +45,11 @@ export async function runServeCommand(options) {
44
45
  await waitForTermination(server);
45
46
  }
46
47
  /** Runs the standalone command. */
47
- export async function main(args = process.argv.slice(2)) {
48
+ export async function main(args = process.argv.slice(2), packageVersion) {
49
+ if (args[0] === "agent") {
50
+ runAgentCommand(args.slice(1), packageVersion);
51
+ return;
52
+ }
48
53
  const options = parseServeArguments(args);
49
54
  await runServeCommand(options);
50
55
  }
@@ -1,6 +1,6 @@
1
1
  import type { NudgeUiClientManifest } from "../../inspector/clientManifest.js";
2
2
  import type { ProjectTokenSnapshot } from "../../project/tokens.js";
3
- export { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
3
+ export { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
4
4
  /** The serializable runtime document sent to a standalone client. */
5
5
  export interface StandaloneRuntimeManifest extends NudgeUiClientManifest {
6
6
  }
@@ -1,5 +1,5 @@
1
1
  import { NUDGE_UI_MANIFEST_VERSION, NUDGE_UI_RELOAD_PATH } from "../../transport/index.js";
2
- export { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
2
+ export { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_MOUNT_ID, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "../../transport/index.js";
3
3
  /**
4
4
  * Creates the first valid static-HTML runtime snapshot.
5
5
  *
@@ -6,7 +6,9 @@ import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path
6
6
  import { createRequire } from "node:module";
7
7
  import { instrumentSourceHtml } from "../../html/identity.js";
8
8
  import { injectStandaloneBootstrap } from "./html/bootstrap.js";
9
- import { createStandaloneRuntimeManifest, NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "./manifest.js";
9
+ import { createNudgeUiEditorDocument, isNudgeUiEditorDocumentRequest } from "../../transport/editor.js";
10
+ import { createStandaloneRuntimeManifest, NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, NUDGE_UI_RELOAD_PATH, NUDGE_UI_ROUTE_PREFIX, } from "./manifest.js";
11
+ import { startOptionalProjectBridge } from "../projectBridge.js";
10
12
  import { createProjectTokenSnapshot, EMPTY_PROJECT_TOKEN_SNAPSHOT } from "../../project/tokens.js";
11
13
  import { createProjectFileWatcher, isSensitiveProjectPath, } from "../../project/files.js";
12
14
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "localhost"]);
@@ -36,6 +38,7 @@ export function createStandaloneServer(options) {
36
38
  const projectId = options.projectId ?? createStandaloneProjectId(rootDirectory);
37
39
  let revision = 0;
38
40
  let manifest = createStandaloneRuntimeManifest(projectId, EMPTY_PROJECT_TOKEN_SNAPSHOT, revision);
41
+ let projectBridge = null;
39
42
  // The initial scan runs asynchronously; start() awaits it so the first
40
43
  // request and manifest always observe the complete project token knowledge.
41
44
  const initialTokens = createProjectTokenSnapshot({ rootDirectory, generationLabel: "static-html" }).then((tokens) => {
@@ -78,7 +81,10 @@ export function createStandaloneServer(options) {
78
81
  // The scan is asynchronous and bounded to the project root; rebuilding
79
82
  // once per settled batch keeps the manifest coherent before reload.
80
83
  const tokens = await createProjectTokenSnapshot({ rootDirectory, generationLabel: "static-html" });
81
- manifest = createStandaloneRuntimeManifest(projectId, tokens, revision + 1);
84
+ manifest = {
85
+ ...createStandaloneRuntimeManifest(projectId, tokens, revision + 1),
86
+ ...(projectBridge ? { agentBridge: projectBridge.browser } : {}),
87
+ };
82
88
  revision += 1;
83
89
  notifyReload();
84
90
  }).catch((error) => {
@@ -146,7 +152,22 @@ export function createStandaloneServer(options) {
146
152
  rejectStart(new Error("Standalone server closed during startup."));
147
153
  return;
148
154
  }
149
- resolveStart(readAddress(httpServer, host));
155
+ const address = readAddress(httpServer, host);
156
+ void startOptionalProjectBridge({
157
+ appRoot: rootDirectory,
158
+ projectId,
159
+ origin: address.url.replace(/\/$/, ""),
160
+ warn: (message) => console.warn(message),
161
+ }).then((bridge) => {
162
+ projectBridge = bridge;
163
+ if (bridge)
164
+ manifest = { ...manifest, agentBridge: bridge.browser };
165
+ if (closeRequested) {
166
+ rejectStart(new Error("Standalone server closed during startup."));
167
+ return;
168
+ }
169
+ resolveStart(address);
170
+ });
150
171
  });
151
172
  };
152
173
  httpServer.once("error", onError);
@@ -165,6 +186,10 @@ export function createStandaloneServer(options) {
165
186
  closeReloadClients();
166
187
  const closing = closeWatcher
167
188
  .then(() => pendingStart?.catch(() => undefined))
189
+ .then(async () => {
190
+ await projectBridge?.close();
191
+ projectBridge = null;
192
+ })
168
193
  .then(() => changeQueue)
169
194
  .then(() => {
170
195
  if (!httpServer.listening)
@@ -370,6 +395,13 @@ async function handleRequest(input) {
370
395
  await handleNudgeUiRoute(input, decodedPath);
371
396
  return;
372
397
  }
398
+ if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
399
+ const body = request.method === "HEAD"
400
+ ? Buffer.alloc(0)
401
+ : Buffer.from(createNudgeUiEditorDocument(), "utf8");
402
+ sendBody(response, 200, body, "text/html; charset=utf-8", { "Cache-Control": "no-store" });
403
+ return;
404
+ }
373
405
  const resolution = resolveDecodedStaticFile(input.rootDirectory, decodedPath);
374
406
  if (!resolution) {
375
407
  sendText(response, 404, "Not Found");
@@ -434,6 +466,15 @@ async function handleNudgeUiRoute(input, pathname) {
434
466
  });
435
467
  return;
436
468
  }
469
+ if (pathname === NUDGE_UI_EDITOR_PATH) {
470
+ const body = input.request.method === "HEAD"
471
+ ? Buffer.alloc(0)
472
+ : Buffer.from(createNudgeUiEditorDocument(), "utf8");
473
+ sendBody(input.response, 200, body, "text/html; charset=utf-8", {
474
+ "Cache-Control": "no-store",
475
+ });
476
+ return;
477
+ }
437
478
  if (pathname === NUDGE_UI_CLIENT_PATH) {
438
479
  try {
439
480
  const body = await readFileFromDescriptor(input.clientPath ?? resolveDefaultClientPath());
@@ -21,8 +21,12 @@ export interface NudgeUiOptions extends ReactOptions {
21
21
  enabled?: boolean;
22
22
  /** Enables experimental DOM parent/child navigation in the Inspector. */
23
23
  debug?: boolean;
24
- /** The landing app's demo runtime: mounted at root in dev and `nudge-demo` builds, or via `?nudgeDemo=1`. */
24
+ /** Enables the landing app's restricted, iframe-backed demo runtime. */
25
25
  demo?: boolean;
26
+ /** Additional same-origin routes shown when the public demo opens. */
27
+ demoPages?: readonly string[];
28
+ /** Labels for the seeded demo cards in display order, including the primary card. */
29
+ demoCardLabels?: readonly string[];
26
30
  /** Explicit project ID for browser-storage keys (defaults to root directory basename). */
27
31
  projectId?: string;
28
32
  /** Optional static v3 config for fixture/app integrations; dynamic configs are not executed. */