nudge-ui 0.2.0 → 0.2.2

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 (231) 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 +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -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,23 @@ 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
+ info: (message) => console.info(message),
161
+ warn: (message) => console.warn(message),
162
+ }).then((bridge) => {
163
+ projectBridge = bridge;
164
+ if (bridge)
165
+ manifest = { ...manifest, agentBridge: bridge.browser };
166
+ if (closeRequested) {
167
+ rejectStart(new Error("Standalone server closed during startup."));
168
+ return;
169
+ }
170
+ resolveStart(address);
171
+ });
150
172
  });
151
173
  };
152
174
  httpServer.once("error", onError);
@@ -165,6 +187,10 @@ export function createStandaloneServer(options) {
165
187
  closeReloadClients();
166
188
  const closing = closeWatcher
167
189
  .then(() => pendingStart?.catch(() => undefined))
190
+ .then(async () => {
191
+ await projectBridge?.close();
192
+ projectBridge = null;
193
+ })
168
194
  .then(() => changeQueue)
169
195
  .then(() => {
170
196
  if (!httpServer.listening)
@@ -370,6 +396,13 @@ async function handleRequest(input) {
370
396
  await handleNudgeUiRoute(input, decodedPath);
371
397
  return;
372
398
  }
399
+ if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
400
+ const body = request.method === "HEAD"
401
+ ? Buffer.alloc(0)
402
+ : Buffer.from(createNudgeUiEditorDocument(), "utf8");
403
+ sendBody(response, 200, body, "text/html; charset=utf-8", { "Cache-Control": "no-store" });
404
+ return;
405
+ }
373
406
  const resolution = resolveDecodedStaticFile(input.rootDirectory, decodedPath);
374
407
  if (!resolution) {
375
408
  sendText(response, 404, "Not Found");
@@ -434,6 +467,15 @@ async function handleNudgeUiRoute(input, pathname) {
434
467
  });
435
468
  return;
436
469
  }
470
+ if (pathname === NUDGE_UI_EDITOR_PATH) {
471
+ const body = input.request.method === "HEAD"
472
+ ? Buffer.alloc(0)
473
+ : Buffer.from(createNudgeUiEditorDocument(), "utf8");
474
+ sendBody(input.response, 200, body, "text/html; charset=utf-8", {
475
+ "Cache-Control": "no-store",
476
+ });
477
+ return;
478
+ }
437
479
  if (pathname === NUDGE_UI_CLIENT_PATH) {
438
480
  try {
439
481
  const body = await readFileFromDescriptor(input.clientPath ?? resolveDefaultClientPath());
@@ -18,11 +18,16 @@ export interface VanillaExtractOptions {
18
18
  source?: string;
19
19
  }
20
20
  export interface NudgeUiOptions extends ReactOptions {
21
+ /** `false` registers nothing. `NUDGE_UI=0` has the same effect for one run. */
21
22
  enabled?: boolean;
22
23
  /** Enables experimental DOM parent/child navigation in the Inspector. */
23
24
  debug?: boolean;
24
- /** The landing app's demo runtime: mounted at root in dev and `nudge-demo` builds, or via `?nudgeDemo=1`. */
25
+ /** Enables the landing app's restricted, iframe-backed demo runtime. */
25
26
  demo?: boolean;
27
+ /** Additional same-origin routes shown when the public demo opens. */
28
+ demoPages?: readonly string[];
29
+ /** Labels for the seeded demo cards in display order, including the primary card. */
30
+ demoCardLabels?: readonly string[];
26
31
  /** Explicit project ID for browser-storage keys (defaults to root directory basename). */
27
32
  projectId?: string;
28
33
  /** Optional static v3 config for fixture/app integrations; dynamic configs are not executed. */
@@ -2,11 +2,13 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { basename, join, relative, resolve, sep, } from "node:path";
4
4
  import { createTokenInventory, } from "../../css/token-inventory/index.js";
5
- import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH } from "../../transport/index.js";
5
+ import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, createNudgeUiEditorDocument, isNudgeUiEditorDocumentRequest, } from "../../transport/index.js";
6
6
  import { discoverCssImportGraph, stripCssQuery } from "./tokens/activeStylesheets.js";
7
7
  import { catalogSourcePath, createViteStylesheetArtifact, isHostApplicationSource, isPackageStylesheet, orderViteStylesheetGraph, } from "./tokens/viteStylesheetArtifacts.js";
8
8
  import { interpretDialects } from "../../css/dialects/index.js";
9
9
  import { detectStylingSystem, } from "../../inspector/clientManifest.js";
10
+ import { loopbackOrigin, startOptionalProjectBridge, } from "../projectBridge.js";
11
+ import { automationManifestFields, isNudgeUiEnabled } from "../environment.js";
10
12
  function isRecord(value) {
11
13
  return typeof value === "object" && value !== null && !Array.isArray(value);
12
14
  }
@@ -100,12 +102,14 @@ function invalidateHmrModules(server, modules, timestamp) {
100
102
  * here has grown a React assumption.
101
103
  */
102
104
  export function createVitePlugins(options = {}, createFramework = null) {
103
- const enabled = options.enabled ?? true;
105
+ const enabled = isNudgeUiEnabled(options.enabled);
104
106
  let root;
105
107
  let buildOutputDirectory;
106
108
  let command = "serve";
107
109
  let demoBuild = false;
108
110
  let devServer;
111
+ let projectBridge = null;
112
+ let projectBridgeStart = null;
109
113
  let postTransformPromise = null;
110
114
  const inventory = createTokenInventory();
111
115
  const activeHostCssFiles = new Set();
@@ -473,14 +477,43 @@ export function createVitePlugins(options = {}, createFramework = null) {
473
477
  return postTransformPromise;
474
478
  }
475
479
  async function buildClientManifest() {
480
+ await projectBridgeStart;
476
481
  await ensurePostTransformCss();
477
482
  await ensurePublishedThemeContract();
478
483
  return {
479
484
  version: 1,
480
485
  revision: 0,
481
486
  runtime: buildRuntimeSnapshot(),
487
+ ...(projectBridge ? { agentBridge: projectBridge.browser } : {}),
488
+ ...automationManifestFields(),
482
489
  };
483
490
  }
491
+ function startProjectBridge(server) {
492
+ // The public demo uses the shared editor runtime without project-owned
493
+ // sessions, an agent bridge, or filesystem persistence.
494
+ if (options.demo === true || !root || projectBridgeStart)
495
+ return;
496
+ const address = server.httpServer?.address();
497
+ if (!address || typeof address === "string")
498
+ return;
499
+ const origin = server.resolvedUrls?.local[0]
500
+ ? new URL(server.resolvedUrls.local[0]).origin
501
+ : loopbackOrigin(address, server.config.server.https ? "https" : "http");
502
+ const allowedOrigins = [
503
+ ...(server.resolvedUrls?.local ?? []),
504
+ ...(server.resolvedUrls?.network ?? []),
505
+ ].map((url) => new URL(url).origin);
506
+ projectBridgeStart = startOptionalProjectBridge({
507
+ appRoot: root,
508
+ projectId: options.projectId ?? basename(root),
509
+ origin,
510
+ ...(allowedOrigins.length > 0 ? { allowedOrigins } : {}),
511
+ info: (message) => server.config.logger.info(message),
512
+ warn: (message) => server.config.logger.warn(message),
513
+ }).then((bridge) => {
514
+ projectBridge = bridge;
515
+ });
516
+ }
484
517
  function buildRuntimeSnapshot() {
485
518
  syncInventoryContributions();
486
519
  const snapshot = inventory.snapshot();
@@ -497,6 +530,9 @@ export function createVitePlugins(options = {}, createFramework = null) {
497
530
  host: framework?.hostLabel ?? "static-html",
498
531
  framework: framework?.framework ?? "HTML",
499
532
  stylingSystem: detectStylingSystem(snapshot.tokens),
533
+ ...(options.demo === true ? { demo: true } : {}),
534
+ ...(options.demoPages === undefined ? {} : { demoPages: options.demoPages }),
535
+ ...(options.demoCardLabels === undefined ? {} : { demoCardLabels: options.demoCardLabels }),
500
536
  capabilities: { canvas: true, componentSemantics: framework !== null },
501
537
  tokenCatalog: snapshot.definitions.map((definition) => ({
502
538
  ...definition,
@@ -536,16 +572,38 @@ export function createVitePlugins(options = {}, createFramework = null) {
536
572
  if (!enabled || command !== "serve")
537
573
  return;
538
574
  devServer = server;
575
+ if (server.httpServer?.listening)
576
+ startProjectBridge(server);
577
+ else
578
+ server.httpServer?.once("listening", () => startProjectBridge(server));
579
+ server.httpServer?.once("close", () => {
580
+ void projectBridgeStart?.then(() => projectBridge?.close()).finally(() => {
581
+ projectBridge = null;
582
+ projectBridgeStart = null;
583
+ });
584
+ });
539
585
  const runtimeWarning = framework?.unresolvedRuntimeWarning();
540
586
  if (runtimeWarning)
541
587
  server.config.logger.warn(runtimeWarning);
542
588
  server.middlewares?.use(async (request, response, next) => {
543
589
  const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
544
- if ((pathname === CLIENT_PATH || pathname === MANIFEST_PATH) && request.method !== "GET") {
590
+ const invalidReservedMethod = (pathname === CLIENT_PATH || pathname === MANIFEST_PATH)
591
+ ? request.method !== "GET"
592
+ : pathname === NUDGE_UI_EDITOR_PATH
593
+ && request.method !== "GET"
594
+ && request.method !== "HEAD";
595
+ if (invalidReservedMethod) {
545
596
  response.statusCode = 405;
546
597
  response.end();
547
598
  return;
548
599
  }
600
+ if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
601
+ response.statusCode = 200;
602
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
603
+ response.setHeader("Cache-Control", "no-store");
604
+ response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
605
+ return;
606
+ }
549
607
  if (pathname === CLIENT_PATH) {
550
608
  try {
551
609
  response.statusCode = 200;
@@ -628,29 +686,39 @@ export function createVitePlugins(options = {}, createFramework = null) {
628
686
  return `export {};\n`;
629
687
  }
630
688
  if (options.demo === true) {
631
- const landingDemoExpression = demoBuild
689
+ const demoControllerExpression = demoBuild
632
690
  ? "true"
633
691
  : '(import.meta.env.DEV && window.location.pathname === "/")';
634
692
  // Taken from the composed framework, so the demo bootstrap cannot drift
635
693
  // from the manifest the transport serves.
636
694
  const identity = buildRuntimeSnapshot();
637
695
  return [
638
- 'import { bootstrapNudgeUi, configureNudgeUiRuntime, detectFramework, setInspectorOpen } from "nudge-ui/internal/inspector";',
696
+ 'import { bootstrapNudgeUi, configureNudgeUiRuntime, createNudgeUiEditorUrl, detectFramework, hasNudgeUiDirectTabIntent, isCanvasRenderer, isNudgeUiDirectUrl, readNudgeUiEditorTarget, rememberNudgeUiDirectTabIntent } from "nudge-ui/internal/inspector";',
639
697
  'import { tokenCatalog, tokens, tokenDiagnostics, tokenGeneration, nudgeUiProjectId } from "virtual:design-tokens";',
640
698
  ...(framework
641
699
  ? [`import { componentContracts } from ${JSON.stringify(framework.virtualModuleId)};`]
642
700
  : []),
643
- 'const __nudge_ui_demo_frame = new URLSearchParams(window.location.search).get("nudgeDemo") === "1";',
644
- `const __nudge_ui_landing_demo = ${landingDemoExpression};`,
645
- 'const __nudge_ui_demo_runtime = __nudge_ui_demo_frame || __nudge_ui_landing_demo;',
646
- 'if (__nudge_ui_demo_runtime || (import.meta.env.DEV && window.location.pathname !== "/demo")) {',
701
+ 'const __nudge_ui_renderer = isCanvasRenderer();',
702
+ 'const __nudge_ui_explicit_direct = isNudgeUiDirectUrl(window.location.href);',
703
+ 'if (__nudge_ui_explicit_direct) rememberNudgeUiDirectTabIntent();',
704
+ 'const __nudge_ui_direct_tab = hasNudgeUiDirectTabIntent();',
705
+ `const __nudge_ui_demo_controller = !__nudge_ui_renderer && !__nudge_ui_explicit_direct && !__nudge_ui_direct_tab && ${demoControllerExpression};`,
706
+ 'const __nudge_ui_editor_target = readNudgeUiEditorTarget(window.location.href) ?? (__nudge_ui_demo_controller ? window.location.href : null);',
707
+ 'if (__nudge_ui_demo_controller && readNudgeUiEditorTarget(window.location.href) === null) {',
708
+ ' window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(window.location.href));',
709
+ '}',
710
+ 'if (__nudge_ui_editor_target) document.documentElement.setAttribute("data-nudge-ui-editor", "");',
711
+ 'if (__nudge_ui_renderer) document.documentElement.setAttribute("data-nudge-ui-renderer", "");',
712
+ 'if (__nudge_ui_editor_target || __nudge_ui_renderer) {',
647
713
  ' configureNudgeUiRuntime({',
648
714
  ' projectId: nudgeUiProjectId,',
649
715
  ` host: ${JSON.stringify(identity.host)},`,
650
716
  ` framework: ${JSON.stringify(identity.framework)},`,
651
717
  ' stylingSystem: detectFramework(tokens).stylingSystem,',
652
- ' ...(__nudge_ui_demo_runtime ? { demo: true } : {}),',
653
- ` capabilities: { canvas: __nudge_ui_demo_runtime ? false : true, componentSemantics: ${String(framework !== null)} },`,
718
+ ' demo: true,',
719
+ ` demoPages: ${JSON.stringify(options.demoPages ?? [])},`,
720
+ ` demoCardLabels: ${JSON.stringify(options.demoCardLabels ?? [])},`,
721
+ ` capabilities: { canvas: true, componentSemantics: ${String(framework !== null)} },`,
654
722
  ' tokenCatalog,',
655
723
  ' tokens,',
656
724
  ' tokenDiagnostics,',
@@ -660,12 +728,11 @@ export function createVitePlugins(options = {}, createFramework = null) {
660
728
  ' const __dt_root = document.getElementById("nudge-ui-root");',
661
729
  ' if (__dt_root) {',
662
730
  ' bootstrapNudgeUi(__dt_root);',
663
- ' if (__nudge_ui_demo_runtime) {',
664
- ' setInspectorOpen(false);',
665
- ' window.addEventListener("nudge-ui:open", () => setInspectorOpen(true));',
666
- ' }',
667
731
  ' }',
668
732
  '}',
733
+ 'window.addEventListener("nudge-ui:open", () => {',
734
+ ' if (!__nudge_ui_renderer && (__nudge_ui_explicit_direct || __nudge_ui_direct_tab)) window.location.assign(createNudgeUiEditorUrl(window.location.href));',
735
+ '});',
669
736
  ].join("\n");
670
737
  }
671
738
  return null;
@@ -1,13 +1,14 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { AgentClientSnapshot } from "./client.js";
3
3
  export declare const MCP_DOCS_URL = "https://github.com/charlessmart/nudge-ui#connect-a-coding-agent";
4
- /** Builds the project-local add-mcp command shown by the connection panel. */
5
- export declare function createMcpSetupCommand(projectId: string, origin: string): string;
4
+ /** Uses the guided installer so browser diagnostics never become shell arguments. */
5
+ export declare function createMcpSetupCommand(): string;
6
6
  export interface McpConnectionContentProps {
7
7
  readonly projectId: string;
8
8
  readonly origin: string;
9
9
  readonly snapshot: AgentClientSnapshot;
10
10
  readonly onConnect: () => Promise<boolean> | boolean | void;
11
+ readonly onTakeOver: () => Promise<boolean> | boolean | void;
11
12
  readonly onDisconnect: () => void;
12
13
  readonly onCheckAgain: () => Promise<void>;
13
14
  }
@@ -16,11 +17,11 @@ export interface McpConnectionDialogProps extends McpConnectionContentProps {
16
17
  readonly onOpenChange: (open: boolean) => void;
17
18
  }
18
19
  /** Renders the MCP setup and pairing flow without owning a dialog surface. */
19
- export declare function McpConnectionContent({ projectId, origin, snapshot, onConnect, onDisconnect, onCheckAgain, }: McpConnectionContentProps): ReactElement;
20
+ export declare function McpConnectionContent({ projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }: McpConnectionContentProps): ReactElement;
20
21
  /**
21
22
  * Explains and controls the project-scoped MCP companion connection.
22
23
  *
23
24
  * This compatibility wrapper keeps the existing standalone MCP surface while
24
25
  * the shared content is also embedded in the general settings modal.
25
26
  */
26
- export declare function McpConnectionDialog({ open, projectId, origin, snapshot, onOpenChange, onConnect, onDisconnect, onCheckAgain, }: McpConnectionDialogProps): ReactElement;
27
+ export declare function McpConnectionDialog({ open, projectId, origin, snapshot, onOpenChange, onConnect, onTakeOver, onDisconnect, onCheckAgain, }: McpConnectionDialogProps): ReactElement;
@@ -5,35 +5,13 @@ import { IconCheck, IconClipboard, IconExternalLink, IconPlugConnected, IconRefr
5
5
  import { getAgentConnectionStatus } from "./connectionStatus.js";
6
6
  import { copyToClipboard } from "../prompt/copyToClipboard.js";
7
7
  import { Button } from "../ui/Button.js";
8
+ import { Disclosure } from "../ui/Disclosure.js";
8
9
  import { SegmentedControl } from "../ui/SegmentedControl.js";
9
10
  import { StatusCallout } from "../ui/StatusCallout.js";
10
11
  export const MCP_DOCS_URL = "https://github.com/charlessmart/nudge-ui#connect-a-coding-agent";
11
- /** Quotes one value for a POSIX shell command. */
12
- function shellQuote(value) {
13
- return `'${value.replaceAll("'", "'\\''")}'`;
14
- }
15
- function shellArgument(value) {
16
- // Keep the usual command readable while quoting values that could be
17
- // interpreted as shell syntax when add-mcp runs the nested command.
18
- return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : shellQuote(value);
19
- }
20
- /** Builds the project-local add-mcp command shown by the connection panel. */
21
- export function createMcpSetupCommand(projectId, origin) {
22
- const serverCommand = [
23
- "npx -y @nudge-ui/mcp@latest",
24
- "--project-id",
25
- shellArgument(projectId),
26
- "--origin",
27
- shellArgument(origin),
28
- "--workspace-root .",
29
- ].join(" ");
30
- return `npx add-mcp ${shellQuote(serverCommand)} --name nudge_ui`;
31
- }
32
- function formatMcpSetupCommand(command) {
33
- return command
34
- .replace(" --project-id ", "\n --project-id ")
35
- .replace(" --origin ", "\n --origin ")
36
- .replace(" --workspace-root ", "\n --workspace-root ");
12
+ /** Uses the guided installer so browser diagnostics never become shell arguments. */
13
+ export function createMcpSetupCommand() {
14
+ return "npx nudge-ui agent setup";
37
15
  }
38
16
  function portalContainer() {
39
17
  return typeof document !== "undefined"
@@ -44,7 +22,7 @@ function TimelineStep({ number, complete, title, children, }) {
44
22
  return (_jsxs("li", { className: `mcp-connection__timeline-step${complete ? " mcp-connection__timeline-step--complete" : ""}`, "data-test": `mcp-step-${number}`, "data-complete": complete ? "true" : "false", children: [_jsx("div", { className: "mcp-connection__timeline-marker", "aria-hidden": "true", children: complete ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 2 }) : number }), _jsxs("section", { className: "mcp-connection__timeline-content", children: [_jsx("h3", { className: "mcp-connection__section-title", children: title }), children] })] }));
45
23
  }
46
24
  /** Renders the MCP setup and pairing flow without owning a dialog surface. */
47
- export function McpConnectionContent({ projectId, origin, snapshot, onConnect, onDisconnect, onCheckAgain, }) {
25
+ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }) {
48
26
  const [checking, setChecking] = useState(false);
49
27
  const [setupMethod, setSetupMethod] = useState("ai");
50
28
  const [copiedCommand, setCopiedCommand] = useState(false);
@@ -52,24 +30,29 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
52
30
  const [copiedListener, setCopiedListener] = useState(false);
53
31
  const [copyError, setCopyError] = useState();
54
32
  const [checkError, setCheckError] = useState();
55
- const setupCommand = useMemo(() => createMcpSetupCommand(projectId, origin), [origin, projectId]);
56
- const displaySetupCommand = useMemo(() => formatMcpSetupCommand(setupCommand), [setupCommand]);
33
+ const setupCommand = createMcpSetupCommand();
57
34
  const setupPrompt = useMemo(() => [
58
- "Please configure the Nudge MCP companion for this project.",
35
+ "Please set up the Nudge coding-agent integration for this application.",
59
36
  "",
60
- "Run this command from the project root:",
37
+ "Run this command from the application directory and select this coding agent:",
61
38
  setupCommand,
62
39
  "",
63
- "Then restart or refresh the MCP server so Nudge can connect.",
40
+ "Start the application normally. Reload the agent if needed, then listen to Nudge.",
64
41
  ].join("\n"), [setupCommand]);
65
- const listenerInstruction = "Call nudge_listen now and wait for my Nudge UI prompt. After applying it, call nudge_report_status and listen again.";
42
+ const listenerInstruction = "Listen to Nudge for this application in the current worktree. Use nudge_list_sessions if selection is ambiguous, then call nudge_listen. After applying each prompt, call nudge_report_status and listen again until I ask you to stop.";
66
43
  const status = getAgentConnectionStatus(snapshot);
67
44
  const canConnect = snapshot.companionReachable
68
45
  && !snapshot.paired
46
+ && !snapshot.pairedElsewhere
69
47
  && snapshot.state !== "pairing"
70
48
  && snapshot.state !== "working"
71
49
  && snapshot.request?.status !== "working";
72
50
  const canDisconnect = snapshot.paired || snapshot.request?.status === "working";
51
+ const canTakeOver = snapshot.companionReachable
52
+ && snapshot.pairedElsewhere
53
+ && snapshot.state !== "pairing"
54
+ && snapshot.state !== "working"
55
+ && snapshot.request?.status !== "working";
73
56
  async function handleCheckAgain() {
74
57
  if (checking)
75
58
  return;
@@ -118,10 +101,10 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
118
101
  setCopyError("The listener instruction could not be copied. Select it and copy it manually.");
119
102
  }
120
103
  }
121
- return (_jsxs(_Fragment, { children: [_jsxs(StatusCallout, { className: "mcp-connection__status", tone: status.tone, "data-test": "mcp-connection-status", children: [_jsx("span", { className: "mcp-connection__status-label", role: "status", children: status.label }), snapshot.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.error })) : null, checkError ? _jsx("span", { role: "alert", children: checkError }) : null, snapshot.request?.summary ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.summary })) : null, snapshot.request?.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.error })) : null] }), _jsxs("ol", { className: "mcp-connection__timeline", "data-test": "mcp-connection-timeline", children: [_jsxs(TimelineStep, { number: 1, complete: snapshot.companionReachable, title: "Configure the MCP host", children: [_jsx(SegmentedControl, { "aria-label": "MCP setup method", className: "mcp-connection__setup-tabs", "data-test": "mcp-setup-tabs", value: setupMethod, options: [
104
+ return (_jsxs(_Fragment, { children: [_jsxs(StatusCallout, { className: "mcp-connection__status", tone: status.tone, "data-test": "mcp-connection-status", children: [_jsx("span", { className: "mcp-connection__status-label", role: "status", children: status.label }), snapshot.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.error })) : null, checkError ? _jsx("span", { role: "alert", children: checkError }) : null, snapshot.request?.summary ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.summary })) : null, snapshot.request?.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.error })) : null] }), _jsxs("ol", { className: "mcp-connection__timeline", "data-test": "mcp-connection-timeline", children: [_jsxs(TimelineStep, { number: 1, complete: snapshot.listenerActive || snapshot.request !== null, title: "Set up your agent once", children: [_jsx(SegmentedControl, { "aria-label": "MCP setup method", className: "mcp-connection__setup-tabs", "data-test": "mcp-setup-tabs", value: setupMethod, options: [
122
105
  { value: "ai", label: "AI instructions", testId: "mcp-setup-tab-ai" },
123
106
  { value: "terminal", label: "Terminal command", testId: "mcp-setup-tab-terminal" },
124
- ], onChange: setSetupMethod }), setupMethod === "terminal" ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Run this command from your project root." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-command", children: displaySetupCommand }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-command", type: "button", onClick: () => void handleCopyCommand(), children: [copiedCommand ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedCommand ? "Copied" : "Copy command"] }) })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Copy this prompt into your coding agent to configure Nudge MCP." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-prompt", children: setupPrompt }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-setup-prompt", type: "button", onClick: () => void handleCopyPrompt(), children: [copiedPrompt ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedPrompt ? "Copied" : "Copy prompt"] }) })] }))] }), _jsxs(TimelineStep, { number: 2, complete: snapshot.paired, title: "Connect this page", children: [_jsxs("dl", { className: "mcp-connection__diagnostics", "data-test": "mcp-connection-diagnostics", children: [_jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Project ID" }), _jsx("dd", { "data-test": "mcp-project-id", children: _jsx("code", { children: projectId }) })] }), _jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Origin" }), _jsx("dd", { "data-test": "mcp-origin", children: _jsx("code", { children: origin }) })] })] }), _jsxs("div", { className: "mcp-connection__actions", children: [canConnect ? (_jsxs(Button, { variant: "primary", "data-test": "mcp-connect", type: "button", onClick: () => void onConnect(), children: [_jsx(IconPlugConnected, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Connect"] })) : null, _jsxs(Button, { variant: "quiet", "data-test": "mcp-check-again", type: "button", disabled: checking || snapshot.state === "disabled" || snapshot.state === "pairing", onClick: () => void handleCheckAgain(), children: [_jsx(IconRefresh, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), checking ? "Checking…" : "Check again"] }), canDisconnect ? (_jsxs(Button, { variant: "danger", "data-test": "mcp-disconnect", type: "button", onClick: onDisconnect, children: [_jsx(IconUnlink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), snapshot.paired ? "Disconnect" : "Forget connection"] })) : null] })] }), _jsxs(TimelineStep, { number: 3, complete: snapshot.listenerActive, title: "Start the agent listener", children: [_jsxs("p", { className: "mcp-connection__copy", children: ["Ask your coding agent to call ", _jsx("code", { children: "nudge_listen" }), " and keep it active."] }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-listener", type: "button", onClick: () => void handleCopyListener(), children: [copiedListener ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedListener ? "Copied" : "Copy listener instruction"] }) })] })] }), copyError ? _jsx("p", { className: "mcp-connection__error", role: "alert", children: copyError }) : null, _jsxs("a", { className: "mcp-connection__docs", "data-test": "mcp-docs-link", href: MCP_DOCS_URL, target: "_blank", rel: "noreferrer", children: [_jsx(IconExternalLink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Read the MCP connection guide"] })] }));
107
+ ], onChange: setSetupMethod }), setupMethod === "terminal" ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Run this command from the application directory, then start your app normally." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-command", children: setupCommand }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-command", type: "button", onClick: () => void handleCopyCommand(), children: [copiedCommand ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedCommand ? "Copied" : "Copy command"] }) })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Copy these setup instructions into your coding agent." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-prompt", children: setupPrompt }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-setup-prompt", type: "button", onClick: () => void handleCopyPrompt(), children: [copiedPrompt ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedPrompt ? "Copied" : "Copy prompt"] }) })] }))] }), _jsxs(TimelineStep, { number: 2, complete: snapshot.listenerActive, title: "Listen in this agent session", children: [_jsx("p", { className: "mcp-connection__copy", children: "Tell your coding agent: \u201CListen to Nudge.\u201D Reload the agent if Nudge tools are unavailable." }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-listener", type: "button", onClick: () => void handleCopyListener(), children: [copiedListener ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedListener ? "Copied" : "Copy listening instruction"] }) })] })] }), _jsxs(Disclosure, { className: "mcp-connection__details", title: "Connection details and recovery", children: [_jsxs("p", { className: "mcp-connection__copy", children: ["The page connects automatically when the development integration is available. To diagnose setup, run ", _jsx("code", { children: "nudge-ui agent doctor" }), " from the application directory."] }), _jsxs("dl", { className: "mcp-connection__diagnostics", "data-test": "mcp-connection-diagnostics", children: [_jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Project ID" }), _jsx("dd", { "data-test": "mcp-project-id", children: _jsx("code", { children: projectId }) })] }), _jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Origin" }), _jsx("dd", { "data-test": "mcp-origin", children: _jsx("code", { children: origin }) })] })] }), _jsxs("div", { className: "mcp-connection__actions", children: [canTakeOver ? (_jsxs(Button, { variant: "primary", "data-test": "mcp-takeover", type: "button", onClick: () => void onTakeOver(), children: [_jsx(IconPlugConnected, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Take over"] })) : null, canConnect ? (_jsxs(Button, { variant: "primary", "data-test": "mcp-connect", type: "button", onClick: () => void onConnect(), children: [_jsx(IconPlugConnected, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Connect"] })) : null, _jsxs(Button, { variant: "quiet", "data-test": "mcp-check-again", type: "button", disabled: checking || snapshot.state === "disabled" || snapshot.state === "pairing", onClick: () => void handleCheckAgain(), children: [_jsx(IconRefresh, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), checking ? "Checking…" : "Check again"] }), canDisconnect ? (_jsxs(Button, { variant: "danger", "data-test": "mcp-disconnect", type: "button", onClick: onDisconnect, children: [_jsx(IconUnlink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), snapshot.paired ? "Disconnect" : "Forget connection"] })) : null] })] }), copyError ? _jsx("p", { className: "mcp-connection__error", role: "alert", children: copyError }) : null, _jsxs("a", { className: "mcp-connection__docs", "data-test": "mcp-docs-link", href: MCP_DOCS_URL, target: "_blank", rel: "noreferrer", children: [_jsx(IconExternalLink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Read the MCP connection guide"] })] }));
125
108
  }
126
109
  /**
127
110
  * Explains and controls the project-scoped MCP companion connection.
@@ -129,7 +112,7 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
129
112
  * This compatibility wrapper keeps the existing standalone MCP surface while
130
113
  * the shared content is also embedded in the general settings modal.
131
114
  */
132
- export function McpConnectionDialog({ open, projectId, origin, snapshot, onOpenChange, onConnect, onDisconnect, onCheckAgain, }) {
115
+ export function McpConnectionDialog({ open, projectId, origin, snapshot, onOpenChange, onConnect, onTakeOver, onDisconnect, onCheckAgain, }) {
133
116
  const closeRef = useRef(null);
134
- return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "mcp-connection__backdrop", "data-test": "mcp-connection-backdrop" }), _jsxs(Dialog.Popup, { className: "mcp-connection__popup", "data-test": "mcp-connection-dialog", initialFocus: closeRef, children: [_jsxs("div", { className: "mcp-connection__header", children: [_jsxs("div", { children: [_jsx(Dialog.Title, { className: "mcp-connection__title", children: "Connect MCP" }), _jsx(Dialog.Description, { className: "mcp-connection__description", children: "Pair this page with your coding agent. You can connect before the agent starts listening." })] }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet mcp-connection__close", "aria-label": "Close MCP connection", "data-test": "mcp-connection-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })] }), _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain })] })] }) }));
117
+ return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "mcp-connection__backdrop", "data-test": "mcp-connection-backdrop" }), _jsxs(Dialog.Popup, { className: "mcp-connection__popup", "data-test": "mcp-connection-dialog", initialFocus: closeRef, children: [_jsxs("div", { className: "mcp-connection__header", children: [_jsxs("div", { children: [_jsx(Dialog.Title, { className: "mcp-connection__title", children: "Connect MCP" }), _jsx(Dialog.Description, { className: "mcp-connection__description", children: "Set up your coding agent once, then ask it to listen to Nudge in this worktree." })] }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet mcp-connection__close", "aria-label": "Close MCP connection", "data-test": "mcp-connection-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })] }), _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onTakeOver: onTakeOver, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain })] })] }) }));
135
118
  }
@@ -1,8 +1,10 @@
1
- import { type AgentBridgeTransport, type AgentClientOptions, type AgentRequestStatus, type AgentStatusSnapshot, type PromptDispatchResponse } from "./protocol.js";
1
+ import { type AgentBridgeTransport, type AgentClientOptions, type AgentSketchAttachment, type AgentSketchMetadata, type AgentRequestStatus, type AgentStatusSnapshot, type PromptDispatchResponse } from "./protocol.js";
2
2
  export type AgentClientState = "disabled" | "disconnected" | "available" | "pairing" | "connected" | "working" | "completed" | "failed" | "interrupted";
3
3
  export interface AgentRequestSnapshot {
4
4
  readonly requestId: string;
5
5
  readonly changeRevision?: number;
6
+ readonly clientDispatchId?: string;
7
+ readonly sketches?: readonly AgentSketchMetadata[];
6
8
  readonly status: AgentRequestStatus;
7
9
  readonly summary?: string;
8
10
  readonly error?: string;
@@ -16,11 +18,13 @@ export interface AgentClientSnapshot {
16
18
  /** Whether the last valid discovery response came from the local companion. */
17
19
  readonly companionReachable: boolean;
18
20
  readonly paired: boolean;
21
+ /** Whether another browser currently owns this project's pairing. */
22
+ readonly pairedElsewhere: boolean;
19
23
  readonly request: AgentRequestSnapshot | null;
20
24
  readonly error?: string;
21
25
  }
22
26
  /** Creates a stable numeric revision for one prompt snapshot. */
23
- export declare function createPromptRevision(changes: readonly unknown[], structuralChanges?: readonly unknown[]): number;
27
+ export declare function createPromptRevision(changes: readonly unknown[], structuralChanges?: readonly unknown[], sketches?: readonly unknown[]): number;
24
28
  /**
25
29
  * Browser-side companion client.
26
30
  *
@@ -49,7 +53,10 @@ export declare class AgentClient {
49
53
  private listenerActive;
50
54
  private companionReachable;
51
55
  private paired;
56
+ private pairedElsewhere;
52
57
  private lastError;
58
+ private readonly autoConnect;
59
+ private autoConnectSuppressed;
53
60
  constructor(options: AgentClientOptions);
54
61
  /** Installs the controller callback used to acknowledge Canvas commands. */
55
62
  setCanvasCommandHandler(handler: AgentClientOptions["canvasCommandHandler"]): void;
@@ -66,13 +73,19 @@ export declare class AgentClient {
66
73
  reconnect(): void;
67
74
  /** Pairs the browser with a reachable companion, including an idle agent. */
68
75
  connect(): Promise<boolean>;
76
+ /** Replaces an existing browser pairing after an explicit user action. */
77
+ takeOver(): Promise<boolean>;
78
+ private pairWith;
69
79
  private openEvents;
70
80
  /**
71
81
  * Dispatches one immutable prompt revision. The request enters `working`
72
82
  * synchronously, before the transport promise resolves, so the UI cannot
73
83
  * issue a second prompt while the first one is in flight.
74
84
  */
75
- dispatchPrompt(prompt: string, changeRevision?: number): Promise<PromptDispatchResponse | null>;
85
+ dispatchPrompt(prompt: string, changeRevision?: number, options?: {
86
+ readonly clientDispatchId?: string;
87
+ readonly attachments?: readonly AgentSketchAttachment[];
88
+ }): Promise<PromptDispatchResponse | null>;
76
89
  /** Interrupts local request state and asks the companion to revoke pairing. */
77
90
  disconnect(reason?: string): void;
78
91
  private discover;