nudge-ui 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -46
- package/dist/client.mjs +252 -61
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +38 -32
- package/dist/hosts/astro/integration.js +5 -1
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +5 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +3 -0
- package/dist/hosts/vite/vite.d.ts +3 -0
- package/dist/hosts/vite/vite.js +24 -4
- package/dist/inspector/agent/McpConnectionDialog.js +13 -19
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +2 -0
- package/dist/inspector/agent/client.js +29 -1
- package/dist/inspector/agent/connectionStatus.js +1 -1
- package/dist/inspector/agent/httpTransport.js +9 -2
- package/dist/inspector/agent/protocol.d.ts +6 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +11 -1
- package/dist/inspector/agent/verification.js +53 -25
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +133 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +152 -16
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +225 -57
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
- package/dist/inspector/canvas/frameProtocol.js +3 -3
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +32 -4
- package/dist/inspector/canvas/rendererElementSelector.js +47 -6
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +6 -5
- package/dist/inspector/changes/changesLog.js +40 -32
- package/dist/inspector/changes/draftChanges.d.ts +56 -0
- package/dist/inspector/changes/draftChanges.js +300 -0
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/clientManifest.d.ts +1 -1
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +47 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +20 -27
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +173 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.js +21 -15
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
- package/dist/inspector/overlay/spacingGestures.js +82 -5
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +2 -1
- package/dist/inspector/projection/renderedInstance.js +9 -5
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +19 -15
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +8 -4
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +10 -18
- package/dist/inspector/prompt/clipboardHandoff.js +92 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +17 -16
- package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/settings/SettingsDialog.js +21 -8
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +46 -26
- package/dist/inspector/shell/CopyPromptButton.js +138 -89
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +13 -21
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +7 -5
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
- package/dist/inspector/styleEditors/nudgeValue.js +36 -8
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +29 -21
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +1 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +0 -2
- package/dist/inspector/ui/dragCursor.js +2 -10
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +29 -21
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +558 -311
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.js +3 -1
- package/dist/transport/routes.d.ts +2 -0
- package/dist/transport/routes.js +2 -0
- package/package.json +6 -2
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -78,6 +78,10 @@ export declare function interpretColorValue(value: string, ctx: ColorSemanticsCo
|
|
|
78
78
|
* unparseable opacities return an explicit `ok: false`.
|
|
79
79
|
*/
|
|
80
80
|
export declare function applyColorOpacity(value: string, opacity: string): ColorEditResult;
|
|
81
|
+
/** Applies opacity using RGB channels for hex colors so field edits retain percentage precision. */
|
|
82
|
+
export declare function withColorOpacity(color: string, opacity: string): ColorEditResult;
|
|
83
|
+
/** Preserves the previous opacity when the replacement color has no explicit alpha. */
|
|
84
|
+
export declare function replaceColorPreservingOpacity(previous: string, color: string, context: ColorSemanticsContext): ColorEditResult;
|
|
81
85
|
/**
|
|
82
86
|
* Replaces only the color token in a separable color expression. Keeping this
|
|
83
87
|
* separate from a plain token swap preserves authored alpha modifiers, for
|
|
@@ -398,6 +398,23 @@ export function applyColorOpacity(value, opacity) {
|
|
|
398
398
|
}
|
|
399
399
|
return { ok: false, reason: "unsupported" };
|
|
400
400
|
}
|
|
401
|
+
/** Applies opacity using RGB channels for hex colors so field edits retain percentage precision. */
|
|
402
|
+
export function withColorOpacity(color, opacity) {
|
|
403
|
+
const hex = /^#([\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.exec(color.trim());
|
|
404
|
+
if (hex) {
|
|
405
|
+
const raw = hex[1];
|
|
406
|
+
const channels = rgbTuple(`#${raw.slice(0, raw.length <= 4 ? 3 : 6)}`);
|
|
407
|
+
color = `rgb(${channels.join(" ")} / 100%)`;
|
|
408
|
+
}
|
|
409
|
+
return applyColorOpacity(color, opacity);
|
|
410
|
+
}
|
|
411
|
+
/** Preserves the previous opacity when the replacement color has no explicit alpha. */
|
|
412
|
+
export function replaceColorPreservingOpacity(previous, color, context) {
|
|
413
|
+
const opacity = interpretColorOpacity(previous, context);
|
|
414
|
+
return opacity && !colorValueHasEmbeddedAlpha(color)
|
|
415
|
+
? withColorOpacity(color, opacity.value)
|
|
416
|
+
: { ok: true, value: color };
|
|
417
|
+
}
|
|
401
418
|
function tokenReferenceName(entry) {
|
|
402
419
|
const name = entry.cssName ?? entry.name;
|
|
403
420
|
return name.startsWith("--") ? name : null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handleHtmlArtifactRequest } from "../../project/artifacts.js";
|
|
1
2
|
import {} from "../../inspector/clientManifest.js";
|
|
2
3
|
import { createAstroRuntimeConfig } from "./astroRuntimeConfig.js";
|
|
3
4
|
import { createAstroClientAssetHandler } from "./clientAsset.js";
|
|
@@ -17,38 +18,9 @@ export function createAstroClientTransportPlugin() {
|
|
|
17
18
|
apply: "serve",
|
|
18
19
|
configureServer(server) {
|
|
19
20
|
server.middlewares.use((request, response, next) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
response.statusCode = 405;
|
|
24
|
-
response.setHeader("Allow", "GET, HEAD");
|
|
25
|
-
response.end();
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
response.statusCode = 200;
|
|
29
|
-
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
30
|
-
response.setHeader("Cache-Control", "no-store");
|
|
31
|
-
response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (pathname === NUDGE_UI_CLIENT_PATH) {
|
|
35
|
-
void serveClient(request, response).catch(next);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (pathname === NUDGE_UI_MANIFEST_PATH) {
|
|
39
|
-
void createManifest(server, request, (bridge) => {
|
|
40
|
-
projectBridge = bridge;
|
|
41
|
-
})
|
|
42
|
-
.then((manifest) => {
|
|
43
|
-
response.statusCode = 200;
|
|
44
|
-
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
45
|
-
response.setHeader("Cache-Control", "no-store");
|
|
46
|
-
response.end(JSON.stringify(manifest));
|
|
47
|
-
})
|
|
48
|
-
.catch(next);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
next();
|
|
21
|
+
void handleHtmlArtifactRequest(request, response, server.config.root)
|
|
22
|
+
.then((handled) => { if (!handled)
|
|
23
|
+
handleClientRequest(server, request, response, next); }).catch(next);
|
|
52
24
|
});
|
|
53
25
|
},
|
|
54
26
|
async closeBundle() {
|
|
@@ -56,6 +28,40 @@ export function createAstroClientTransportPlugin() {
|
|
|
56
28
|
projectBridge = null;
|
|
57
29
|
},
|
|
58
30
|
};
|
|
31
|
+
function handleClientRequest(server, request, response, next) {
|
|
32
|
+
const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
|
|
33
|
+
if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
|
|
34
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
35
|
+
response.statusCode = 405;
|
|
36
|
+
response.setHeader("Allow", "GET, HEAD");
|
|
37
|
+
response.end();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
response.statusCode = 200;
|
|
41
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
42
|
+
response.setHeader("Cache-Control", "no-store");
|
|
43
|
+
response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (pathname === NUDGE_UI_CLIENT_PATH) {
|
|
47
|
+
void serveClient(request, response).catch(next);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (pathname === NUDGE_UI_MANIFEST_PATH) {
|
|
51
|
+
void createManifest(server, request, (bridge) => {
|
|
52
|
+
projectBridge = bridge;
|
|
53
|
+
})
|
|
54
|
+
.then((manifest) => {
|
|
55
|
+
response.statusCode = 200;
|
|
56
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
57
|
+
response.setHeader("Cache-Control", "no-store");
|
|
58
|
+
response.end(JSON.stringify(manifest));
|
|
59
|
+
})
|
|
60
|
+
.catch(next);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
next();
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
async function createManifest(server, request, setBridge) {
|
|
61
67
|
// SAFETY: these virtual modules are generated by the shared Nudge UI plugin
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import { nudgeUi } from "../vite/index.js";
|
|
2
3
|
import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH } from "../../transport/index.js";
|
|
3
4
|
import { createAstroClientTransportPlugin, } from "./clientTransport.js";
|
|
4
5
|
import { createProjectContextPlugin } from "./projectContext.js";
|
|
5
6
|
import { isNudgeUiEnabled } from "../environment.js";
|
|
7
|
+
import { installMarkdownIdentity } from "./markdownIdentity.js";
|
|
6
8
|
/**
|
|
7
9
|
* Adds the prebuilt client as an external module. The inspector dependency
|
|
8
10
|
* graph therefore never enters Astro's Vite optimizer.
|
|
@@ -43,14 +45,16 @@ export function nudgeUiAstro(options = {}) {
|
|
|
43
45
|
return {
|
|
44
46
|
name: "nudge-ui",
|
|
45
47
|
hooks: {
|
|
46
|
-
"astro:config:setup"({ command, updateConfig, injectScript, addMiddleware }) {
|
|
48
|
+
"astro:config:setup"({ command, config, updateConfig, injectScript, addMiddleware }) {
|
|
47
49
|
if (!enabled || command !== "dev")
|
|
48
50
|
return;
|
|
51
|
+
const markdown = installMarkdownIdentity(config.markdown, fileURLToPath(config.root));
|
|
49
52
|
// The shared plugin objects are plain Vite plugin records, but this
|
|
50
53
|
// monorepo typechecks them against a different Vite major than the
|
|
51
54
|
// installed Astro resolves internally; the runtime contract
|
|
52
55
|
// (config/resolveId/load/transform hooks) is identical.
|
|
53
56
|
updateConfig({
|
|
57
|
+
...(markdown ? { markdown } : {}),
|
|
54
58
|
vite: {
|
|
55
59
|
// SAFETY: Astro and this package resolve different Vite type
|
|
56
60
|
// versions, but both consume the same runtime Plugin contract.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Element, Root } from "hast";
|
|
2
|
+
/** Adds authored Markdown positions through the remark/rehype pipeline. */
|
|
3
|
+
export declare function createMarkdownIdentityPlugin(projectRoot: string): () => (tree: Root, file: {
|
|
4
|
+
path?: string;
|
|
5
|
+
}) => void;
|
|
6
|
+
/** Adds the same identity through Astro's Sätteri visitor API. */
|
|
7
|
+
export declare function createSatteriIdentityPlugin(projectRoot: string): {
|
|
8
|
+
name: string;
|
|
9
|
+
options: {
|
|
10
|
+
position: boolean;
|
|
11
|
+
};
|
|
12
|
+
element: {
|
|
13
|
+
filter: string[];
|
|
14
|
+
visit(node: Readonly<Element>, context: {
|
|
15
|
+
fileURL: URL | undefined;
|
|
16
|
+
setProperty(node: Readonly<Element>, key: string, value: string): void;
|
|
17
|
+
}): void;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** Extends supported processors without replacing the consumer's pipeline. */
|
|
21
|
+
export declare function installMarkdownIdentity(markdown: {
|
|
22
|
+
processor?: {
|
|
23
|
+
name: string;
|
|
24
|
+
options: object;
|
|
25
|
+
};
|
|
26
|
+
}, projectRoot: string): {
|
|
27
|
+
rehypePlugins: ReturnType<typeof createMarkdownIdentityPlugin>[];
|
|
28
|
+
} | undefined;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
const PROSE_TAGS = [
|
|
4
|
+
"p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li",
|
|
5
|
+
"blockquote", "a", "em", "strong", "del", "code", "pre", "hr", "br",
|
|
6
|
+
"table", "thead", "tbody", "tr", "th", "td",
|
|
7
|
+
];
|
|
8
|
+
const proseTags = new Set(PROSE_TAGS);
|
|
9
|
+
function attributesFor(node, file, projectRoot) {
|
|
10
|
+
const start = node.position?.start;
|
|
11
|
+
if (!start || start.line < 1 || start.column < 1 || !proseTags.has(node.tagName))
|
|
12
|
+
return {};
|
|
13
|
+
const relative = path.relative(projectRoot, file).replaceAll("\\", "/");
|
|
14
|
+
const attributes = {};
|
|
15
|
+
const properties = node.properties;
|
|
16
|
+
if (properties["data-cid"] === undefined && properties.dataCid === undefined) {
|
|
17
|
+
attributes["data-cid"] = `astro:${node.tagName.charAt(0).toUpperCase()}${node.tagName.slice(1)}`;
|
|
18
|
+
}
|
|
19
|
+
if (properties["data-src"] === undefined && properties.dataSrc === undefined) {
|
|
20
|
+
attributes["data-src"] = `${relative}:${start.line}:${start.column}`;
|
|
21
|
+
}
|
|
22
|
+
return attributes;
|
|
23
|
+
}
|
|
24
|
+
/** Adds authored Markdown positions through the remark/rehype pipeline. */
|
|
25
|
+
export function createMarkdownIdentityPlugin(projectRoot) {
|
|
26
|
+
return function nudgeMarkdownIdentity() {
|
|
27
|
+
return (tree, file) => {
|
|
28
|
+
if (!file.path || !/\.mdx?$/.test(file.path))
|
|
29
|
+
return;
|
|
30
|
+
const sourceFile = file.path;
|
|
31
|
+
const visit = (node) => {
|
|
32
|
+
if (node.type === "element") {
|
|
33
|
+
Object.assign(node.properties, attributesFor(node, sourceFile, projectRoot));
|
|
34
|
+
}
|
|
35
|
+
if ("children" in node) {
|
|
36
|
+
for (const child of node.children)
|
|
37
|
+
visit(child);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
visit(tree);
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Adds the same identity through Astro's Sätteri visitor API. */
|
|
45
|
+
export function createSatteriIdentityPlugin(projectRoot) {
|
|
46
|
+
return {
|
|
47
|
+
name: "nudge-ui-markdown-identity",
|
|
48
|
+
options: { position: true },
|
|
49
|
+
element: {
|
|
50
|
+
filter: PROSE_TAGS,
|
|
51
|
+
visit(node, context) {
|
|
52
|
+
if (!context.fileURL || context.fileURL.protocol !== "file:")
|
|
53
|
+
return;
|
|
54
|
+
const file = fileURLToPath(context.fileURL);
|
|
55
|
+
if (!/\.mdx?$/.test(file))
|
|
56
|
+
return;
|
|
57
|
+
for (const [key, value] of Object.entries(attributesFor(node, file, projectRoot))) {
|
|
58
|
+
context.setProperty(node, key, value);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Extends supported processors without replacing the consumer's pipeline. */
|
|
65
|
+
export function installMarkdownIdentity(markdown, projectRoot) {
|
|
66
|
+
const processor = markdown.processor;
|
|
67
|
+
if (!processor)
|
|
68
|
+
return { rehypePlugins: [createMarkdownIdentityPlugin(projectRoot)] };
|
|
69
|
+
if (processor.name !== "unified" && processor.name !== "satteri")
|
|
70
|
+
return undefined;
|
|
71
|
+
// SAFETY: Astro's supported processors expose mutable plugin arrays. This
|
|
72
|
+
// adapter only appends plugins and does not inspect existing entries.
|
|
73
|
+
const options = processor.options;
|
|
74
|
+
if (processor.name === "unified") {
|
|
75
|
+
(options.rehypePlugins ??= []).push(createMarkdownIdentityPlugin(projectRoot));
|
|
76
|
+
}
|
|
77
|
+
else if (processor.name === "satteri") {
|
|
78
|
+
(options.hastPlugins ??= []).push(createSatteriIdentityPlugin(projectRoot));
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
@@ -15,7 +15,7 @@ export function buildManifest(input) {
|
|
|
15
15
|
stylingSystem: "CSS custom properties",
|
|
16
16
|
// Canvas shares the Vite host's controller/renderer runtime (ADR-0006);
|
|
17
17
|
// the mount in every document bootstraps as a renderer inside cards.
|
|
18
|
-
capabilities: { canvas: true, componentSemantics: true },
|
|
18
|
+
capabilities: { canvas: true, componentSemantics: true, domNavigation: true },
|
|
19
19
|
tokenCatalog: [],
|
|
20
20
|
tokens: [],
|
|
21
21
|
tokenDiagnostics: [],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handleHtmlArtifactRequest } from "../../project/artifacts.js";
|
|
1
2
|
import { createServer } from "node:http";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -299,7 +300,10 @@ export async function ensureSidecar(root, options = {}) {
|
|
|
299
300
|
contractsTimer = setTimeout(flushContracts, 120);
|
|
300
301
|
};
|
|
301
302
|
const server = createServer((req, res) => {
|
|
302
|
-
|
|
303
|
+
void handleHtmlArtifactRequest(req, res, fsRoot).then((handled) => {
|
|
304
|
+
if (!handled)
|
|
305
|
+
respond(req, res, () => manifest, () => generation, streams, receiveContracts);
|
|
306
|
+
});
|
|
303
307
|
});
|
|
304
308
|
const port = await new Promise((resolvePort, rejectPort) => {
|
|
305
309
|
server.once("error", rejectPort);
|
|
@@ -24,7 +24,7 @@ export function createStandaloneRuntimeManifest(projectId, tokenSnapshot = empty
|
|
|
24
24
|
// Canvas shares the Vite host's controller/renderer runtime (ADR-0012):
|
|
25
25
|
// every served document carries the bootstrap, so card iframes boot as
|
|
26
26
|
// renderers without host-specific code.
|
|
27
|
-
capabilities: { canvas: true, componentSemantics: false },
|
|
27
|
+
capabilities: { canvas: true, componentSemantics: false, domNavigation: true },
|
|
28
28
|
tokenCatalog: tokenSnapshot.tokenCatalog,
|
|
29
29
|
tokens: tokenSnapshot.tokens,
|
|
30
30
|
tokenDiagnostics: tokenSnapshot.tokenDiagnostics,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handleHtmlArtifactRequest } from "../../project/artifacts.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { createServer, } from "node:http";
|
|
3
4
|
import { constants, existsSync, realpathSync, statSync, } from "node:fs";
|
|
@@ -383,6 +384,8 @@ function isWithin(root, candidate) {
|
|
|
383
384
|
}
|
|
384
385
|
async function handleRequest(input) {
|
|
385
386
|
const { request, response } = input;
|
|
387
|
+
if (await handleHtmlArtifactRequest(request, response, input.rootDirectory))
|
|
388
|
+
return;
|
|
386
389
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
387
390
|
sendText(response, 405, "Method Not Allowed", { Allow: "GET, HEAD" });
|
|
388
391
|
return;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import type { TailwindV3Config, ThemeContract } from "../../css/dialects/index.js";
|
|
3
|
+
import { type NudgeUiRuntimeConfig } from "../../inspector/clientManifest.js";
|
|
3
4
|
import type { FrameworkHost, FrameworkSupport, ReactOptions } from "./react.js";
|
|
4
5
|
/**
|
|
5
6
|
* How to find the project's vanilla-extract theme contract. Loading a module
|
|
@@ -28,6 +29,8 @@ export interface NudgeUiOptions extends ReactOptions {
|
|
|
28
29
|
demoPages?: readonly string[];
|
|
29
30
|
/** Labels for the seeded demo cards in display order, including the primary card. */
|
|
30
31
|
demoCardLabels?: readonly string[];
|
|
32
|
+
/** Product features exposed by the inspector runtime. Omitted flags remain enabled. */
|
|
33
|
+
features?: NudgeUiRuntimeConfig["features"];
|
|
31
34
|
/** Explicit project ID for browser-storage keys (defaults to root directory basename). */
|
|
32
35
|
projectId?: string;
|
|
33
36
|
/** Optional static v3 config for fixture/app integrations; dynamic configs are not executed. */
|
package/dist/hosts/vite/vite.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handleHtmlArtifactRequest } from "../../project/artifacts.js";
|
|
1
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { basename, join, relative, resolve, sep, } from "node:path";
|
|
@@ -81,6 +82,8 @@ function scanCssFiles(rootDir, files = [], dir = rootDir, ignoredDirectory) {
|
|
|
81
82
|
export function transformIndexHtmlHtml(html, command, { debug = false, demoBuild = false, demo = false } = {}) {
|
|
82
83
|
if (command === "build" && !demoBuild)
|
|
83
84
|
return null;
|
|
85
|
+
if (html.includes("data-nudge-ui-client"))
|
|
86
|
+
return null;
|
|
84
87
|
const script = demo || demoBuild ? LEGACY_INSPECTOR_SCRIPT : INSPECTOR_SCRIPT;
|
|
85
88
|
const inject = `\n${debug ? DEBUG_MOUNT_DIV : MOUNT_DIV}\n${script}\n`;
|
|
86
89
|
if (html.includes("</body>")) {
|
|
@@ -533,7 +536,8 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
533
536
|
...(options.demo === true ? { demo: true } : {}),
|
|
534
537
|
...(options.demoPages === undefined ? {} : { demoPages: options.demoPages }),
|
|
535
538
|
...(options.demoCardLabels === undefined ? {} : { demoCardLabels: options.demoCardLabels }),
|
|
536
|
-
|
|
539
|
+
...(options.features === undefined ? {} : { features: options.features }),
|
|
540
|
+
capabilities: { canvas: true, componentSemantics: framework !== null, domNavigation: true },
|
|
537
541
|
tokenCatalog: snapshot.definitions.map((definition) => ({
|
|
538
542
|
...definition,
|
|
539
543
|
declarations: definition.declarations.map((declaration) => ({ ...declaration })),
|
|
@@ -554,11 +558,24 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
554
558
|
if (!enabled || env.command !== "serve")
|
|
555
559
|
return;
|
|
556
560
|
// Module resolution is a framework concern; without one this host asks nothing of the resolver.
|
|
557
|
-
|
|
561
|
+
const frameworkConfig = framework?.viteConfig({
|
|
558
562
|
projectRoot: userConfig.root ?? process.cwd(),
|
|
559
563
|
demo: options.demo === true,
|
|
560
564
|
existingDedupe: userConfig.resolve?.dedupe ?? [],
|
|
561
565
|
});
|
|
566
|
+
const ignored = userConfig.server?.watch?.ignored;
|
|
567
|
+
return {
|
|
568
|
+
...frameworkConfig,
|
|
569
|
+
server: {
|
|
570
|
+
watch: {
|
|
571
|
+
ignored: [
|
|
572
|
+
...(ignored === undefined ? [] : Array.isArray(ignored) ? ignored : [ignored]),
|
|
573
|
+
"**/.nudge",
|
|
574
|
+
"**/.nudge/**",
|
|
575
|
+
],
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
};
|
|
562
579
|
},
|
|
563
580
|
configResolved(config) {
|
|
564
581
|
root = config.root;
|
|
@@ -586,6 +603,8 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
586
603
|
if (runtimeWarning)
|
|
587
604
|
server.config.logger.warn(runtimeWarning);
|
|
588
605
|
server.middlewares?.use(async (request, response, next) => {
|
|
606
|
+
if (await handleHtmlArtifactRequest(request, response, root ?? process.cwd(), (html) => server.transformIndexHtml(request.url ?? "/", html)))
|
|
607
|
+
return;
|
|
589
608
|
const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
|
|
590
609
|
const invalidReservedMethod = (pathname === CLIENT_PATH || pathname === MANIFEST_PATH)
|
|
591
610
|
? request.method !== "GET"
|
|
@@ -601,7 +620,7 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
601
620
|
response.statusCode = 200;
|
|
602
621
|
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
603
622
|
response.setHeader("Cache-Control", "no-store");
|
|
604
|
-
response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
|
|
623
|
+
response.end(request.method === "HEAD" ? undefined : await server.transformIndexHtml(request.url ?? NUDGE_UI_EDITOR_PATH, createNudgeUiEditorDocument()));
|
|
605
624
|
return;
|
|
606
625
|
}
|
|
607
626
|
if (pathname === CLIENT_PATH) {
|
|
@@ -718,7 +737,8 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
718
737
|
' demo: true,',
|
|
719
738
|
` demoPages: ${JSON.stringify(options.demoPages ?? [])},`,
|
|
720
739
|
` demoCardLabels: ${JSON.stringify(options.demoCardLabels ?? [])},`,
|
|
721
|
-
`
|
|
740
|
+
` features: ${JSON.stringify(options.features ?? {})},`,
|
|
741
|
+
` capabilities: { canvas: true, componentSemantics: ${String(framework !== null)}, domNavigation: true },`,
|
|
722
742
|
' tokenCatalog,',
|
|
723
743
|
' tokens,',
|
|
724
744
|
' tokenDiagnostics,',
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Dialog } from "@base-ui/react/dialog";
|
|
4
|
-
import { IconCheck,
|
|
4
|
+
import { IconCheck, IconCopy, IconPlugConnected, IconRefresh, IconUnlink, IconX, } from "@tabler/icons-react";
|
|
5
5
|
import { getAgentConnectionStatus } from "./connectionStatus.js";
|
|
6
6
|
import { copyToClipboard } from "../prompt/copyToClipboard.js";
|
|
7
7
|
import { Button } from "../ui/Button.js";
|
|
8
8
|
import { Disclosure } from "../ui/Disclosure.js";
|
|
9
|
-
import {
|
|
9
|
+
import { IconButton } from "../ui/IconButton.js";
|
|
10
10
|
import { StatusCallout } from "../ui/StatusCallout.js";
|
|
11
|
+
import { portalContainer } from "../ui/portalContainer.js";
|
|
11
12
|
export const MCP_DOCS_URL = "https://github.com/charlessmart/nudge-ui#connect-a-coding-agent";
|
|
12
13
|
/** Uses the guided installer so browser diagnostics never become shell arguments. */
|
|
13
14
|
export function createMcpSetupCommand() {
|
|
14
15
|
return "npx nudge-ui agent setup";
|
|
15
16
|
}
|
|
16
|
-
function portalContainer() {
|
|
17
|
-
return typeof document !== "undefined"
|
|
18
|
-
? document.getElementById("nudge-ui-root")?.shadowRoot ?? document.body
|
|
19
|
-
: null;
|
|
20
|
-
}
|
|
21
17
|
function TimelineStep({ number, complete, title, children, }) {
|
|
22
18
|
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] })] }));
|
|
23
19
|
}
|
|
24
20
|
/** Renders the MCP setup and pairing flow without owning a dialog surface. */
|
|
25
21
|
export function McpConnectionContent({ projectId, origin, snapshot, onConnect, onTakeOver, onDisconnect, onCheckAgain, }) {
|
|
26
22
|
const [checking, setChecking] = useState(false);
|
|
27
|
-
const [setupMethod, setSetupMethod] = useState("ai");
|
|
28
23
|
const [copiedCommand, setCopiedCommand] = useState(false);
|
|
29
24
|
const [copiedPrompt, setCopiedPrompt] = useState(false);
|
|
30
|
-
const [
|
|
25
|
+
const [copiedDoctor, setCopiedDoctor] = useState(false);
|
|
31
26
|
const [copyError, setCopyError] = useState();
|
|
32
27
|
const [checkError, setCheckError] = useState();
|
|
33
28
|
const setupCommand = createMcpSetupCommand();
|
|
@@ -39,8 +34,10 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
|
|
|
39
34
|
"",
|
|
40
35
|
"Start the application normally. Reload the agent if needed, then listen to Nudge.",
|
|
41
36
|
].join("\n"), [setupCommand]);
|
|
42
|
-
const
|
|
37
|
+
const doctorCommand = "nudge-ui agent doctor";
|
|
43
38
|
const status = getAgentConnectionStatus(snapshot);
|
|
39
|
+
const showConnectionStatus = status.kind !== "not-found"
|
|
40
|
+
|| Boolean(snapshot.error || checkError || snapshot.request?.summary || snapshot.request?.error);
|
|
44
41
|
const canConnect = snapshot.companionReachable
|
|
45
42
|
&& !snapshot.paired
|
|
46
43
|
&& !snapshot.pairedElsewhere
|
|
@@ -90,21 +87,18 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
|
|
|
90
87
|
setCopyError("The AI setup prompt could not be copied. Select it and copy it manually.");
|
|
91
88
|
}
|
|
92
89
|
}
|
|
93
|
-
async function
|
|
90
|
+
async function handleCopyDoctor() {
|
|
94
91
|
setCopyError(undefined);
|
|
95
92
|
try {
|
|
96
|
-
await copyToClipboard(
|
|
97
|
-
|
|
98
|
-
window.setTimeout(() =>
|
|
93
|
+
await copyToClipboard(doctorCommand);
|
|
94
|
+
setCopiedDoctor(true);
|
|
95
|
+
window.setTimeout(() => setCopiedDoctor(false), 1500);
|
|
99
96
|
}
|
|
100
97
|
catch {
|
|
101
|
-
setCopyError("The
|
|
98
|
+
setCopyError("The diagnostic command could not be copied. Select it and copy it manually.");
|
|
102
99
|
}
|
|
103
100
|
}
|
|
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
|
|
105
|
-
{ value: "ai", label: "AI instructions", testId: "mcp-setup-tab-ai" },
|
|
106
|
-
{ value: "terminal", label: "Terminal command", testId: "mcp-setup-tab-terminal" },
|
|
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"] })] }));
|
|
101
|
+
return (_jsxs(_Fragment, { children: [showConnectionStatus ? (_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] })) : null, _jsxs("ol", { className: "mcp-connection__timeline", "data-test": "mcp-connection-timeline", children: [_jsxs(TimelineStep, { number: 1, complete: snapshot.companionReachable || snapshot.listenerActive || snapshot.request !== null, title: "Set up agent", children: [_jsx("p", { className: "mcp-connection__copy", children: "Agent prompt" }), _jsxs("div", { className: "mcp-connection__command-block", children: [_jsx(IconButton, { className: "mcp-connection__copy-button", variant: "quiet", size: "compact", label: copiedPrompt ? "Copied" : "Copy setup instructions", "data-test": "mcp-copy-setup-prompt", onClick: () => void handleCopyPrompt(), children: copiedPrompt ? _jsx(IconCheck, { size: 16, "aria-hidden": "true" }) : _jsx(IconCopy, { size: 16, "aria-hidden": "true" }) }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-prompt", children: setupPrompt }) })] }), _jsx("p", { className: "mcp-connection__copy", children: "Or, terminal command" }), _jsxs("div", { className: "mcp-connection__command-block", children: [_jsx(IconButton, { className: "mcp-connection__copy-button", variant: "quiet", size: "compact", label: copiedCommand ? "Copied" : "Copy command", "data-test": "mcp-copy-command", onClick: () => void handleCopyCommand(), children: copiedCommand ? _jsx(IconCheck, { size: 16, "aria-hidden": "true" }) : _jsx(IconCopy, { size: 16, "aria-hidden": "true" }) }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-command", children: setupCommand }) })] })] }), _jsx(TimelineStep, { number: 2, complete: snapshot.listenerActive, title: "Tell agent to listen for instructions", children: _jsx("p", { className: "mcp-connection__copy", children: "Tell your coding agent: \u201CListen to Nudge.\u201D" }) })] }), _jsxs(Disclosure, { className: "mcp-connection__details", title: "Troubleshooting", defaultOpen: true, children: [_jsx("p", { className: "mcp-connection__copy", children: "To diagnose setup" }), _jsxs("div", { className: "mcp-connection__command-block", children: [_jsx(IconButton, { className: "mcp-connection__copy-button", variant: "quiet", size: "compact", label: copiedDoctor ? "Copied" : "Copy diagnostic command", "data-test": "mcp-copy-doctor", onClick: () => void handleCopyDoctor(), children: copiedDoctor ? _jsx(IconCheck, { size: 16, "aria-hidden": "true" }) : _jsx(IconCopy, { size: 16, "aria-hidden": "true" }) }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { children: doctorCommand }) })] }), _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: projectId })] }), _jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Origin" }), _jsx("dd", { "data-test": "mcp-origin", 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…" : "Refresh connection"] }), 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] }));
|
|
108
102
|
}
|
|
109
103
|
/**
|
|
110
104
|
* Explains and controls the project-scoped MCP companion connection.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DraftTarget } from "../drafts/model.js";
|
|
2
|
+
import type { AgentActivity } from "./protocol.js";
|
|
3
|
+
export interface ActivityFile extends AgentActivity {
|
|
4
|
+
readonly expiresAt: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AgentActivitySnapshot {
|
|
7
|
+
readonly requestId: string | null;
|
|
8
|
+
readonly targets: readonly string[];
|
|
9
|
+
readonly target?: DraftTarget;
|
|
10
|
+
readonly files: readonly ActivityFile[];
|
|
11
|
+
}
|
|
12
|
+
/** Pins submitted frames before dispatch. Activity never selects or edits a draft. */
|
|
13
|
+
export declare function beginActivityDispatch(projectId: string, dispatchId: string, targets: readonly string[], target?: DraftTarget): void;
|
|
14
|
+
/** Dispatch and status are the only authority for the layer's lifetime. */
|
|
15
|
+
export declare function synchronizeAgentActivity(projectId: string, requestId: string | undefined, working: boolean, dispatchId?: string): void;
|
|
16
|
+
export declare function reportAgentActivity(projectId: string, activity: AgentActivity): void;
|
|
17
|
+
export declare function getAgentActivity(projectId: string): AgentActivitySnapshot;
|
|
18
|
+
export declare function useAgentActivity(projectId: string): AgentActivitySnapshot;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
const EMPTY = Object.freeze({ requestId: null, targets: [], files: [] });
|
|
3
|
+
const projects = new Map();
|
|
4
|
+
function project(id) {
|
|
5
|
+
let state = projects.get(id);
|
|
6
|
+
if (!state) {
|
|
7
|
+
state = { snapshot: EMPTY, listeners: new Set() };
|
|
8
|
+
projects.set(id, state);
|
|
9
|
+
}
|
|
10
|
+
return state;
|
|
11
|
+
}
|
|
12
|
+
function publish(state, snapshot) {
|
|
13
|
+
state.snapshot = snapshot;
|
|
14
|
+
for (const listener of state.listeners)
|
|
15
|
+
listener();
|
|
16
|
+
}
|
|
17
|
+
/** Pins submitted frames before dispatch. Activity never selects or edits a draft. */
|
|
18
|
+
export function beginActivityDispatch(projectId, dispatchId, targets, target) {
|
|
19
|
+
project(projectId).dispatch = { id: dispatchId, targets: [...targets], target };
|
|
20
|
+
}
|
|
21
|
+
/** Dispatch and status are the only authority for the layer's lifetime. */
|
|
22
|
+
export function synchronizeAgentActivity(projectId, requestId, working, dispatchId) {
|
|
23
|
+
const state = project(projectId);
|
|
24
|
+
if (!working || !requestId) {
|
|
25
|
+
if (state.timer)
|
|
26
|
+
clearTimeout(state.timer);
|
|
27
|
+
state.timer = undefined;
|
|
28
|
+
state.dispatch = undefined;
|
|
29
|
+
if (state.snapshot !== EMPTY)
|
|
30
|
+
publish(state, EMPTY);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (state.snapshot.requestId === requestId)
|
|
34
|
+
return;
|
|
35
|
+
if (state.timer)
|
|
36
|
+
clearTimeout(state.timer);
|
|
37
|
+
const dispatch = state.dispatch?.id === dispatchId ? state.dispatch : undefined;
|
|
38
|
+
publish(state, { requestId, targets: dispatch?.targets ?? [], ...(dispatch?.target ? { target: dispatch.target } : {}), files: [] });
|
|
39
|
+
}
|
|
40
|
+
export function reportAgentActivity(projectId, activity) {
|
|
41
|
+
const state = project(projectId);
|
|
42
|
+
if (state.snapshot.requestId !== activity.requestId)
|
|
43
|
+
return;
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const files = state.snapshot.files.filter((file) => file.expiresAt > now && file.file !== activity.file);
|
|
46
|
+
files.push({ ...activity, expiresAt: now + (activity.operation === "read" ? 8_000 : 12_000) });
|
|
47
|
+
publish(state, { ...state.snapshot, files: files.slice(-64) });
|
|
48
|
+
scheduleExpiry(state);
|
|
49
|
+
}
|
|
50
|
+
function scheduleExpiry(state) {
|
|
51
|
+
if (state.timer)
|
|
52
|
+
clearTimeout(state.timer);
|
|
53
|
+
const deadline = Math.min(...state.snapshot.files.map((file) => file.expiresAt));
|
|
54
|
+
if (!Number.isFinite(deadline)) {
|
|
55
|
+
state.timer = undefined;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
state.timer = setTimeout(() => {
|
|
59
|
+
publish(state, { ...state.snapshot, files: state.snapshot.files.filter((file) => file.expiresAt > Date.now()) });
|
|
60
|
+
scheduleExpiry(state);
|
|
61
|
+
}, Math.max(0, deadline - Date.now()));
|
|
62
|
+
}
|
|
63
|
+
export function getAgentActivity(projectId) { return project(projectId).snapshot; }
|
|
64
|
+
export function useAgentActivity(projectId) {
|
|
65
|
+
return useSyncExternalStore((listener) => {
|
|
66
|
+
const state = project(projectId);
|
|
67
|
+
state.listeners.add(listener);
|
|
68
|
+
return () => { state.listeners.delete(listener); };
|
|
69
|
+
}, () => getAgentActivity(projectId), () => EMPTY);
|
|
70
|
+
}
|
|
@@ -15,6 +15,7 @@ export interface AgentClientSnapshot {
|
|
|
15
15
|
/** Companion state, using the shared protocol vocabulary. */
|
|
16
16
|
readonly connection: AgentStatusSnapshot["connection"];
|
|
17
17
|
readonly listenerActive: boolean;
|
|
18
|
+
readonly agentClientName?: string;
|
|
18
19
|
/** Whether the last valid discovery response came from the local companion. */
|
|
19
20
|
readonly companionReachable: boolean;
|
|
20
21
|
readonly paired: boolean;
|
|
@@ -51,6 +52,7 @@ export declare class AgentClient {
|
|
|
51
52
|
private started;
|
|
52
53
|
private connection;
|
|
53
54
|
private listenerActive;
|
|
55
|
+
private agentClientName;
|
|
54
56
|
private companionReachable;
|
|
55
57
|
private paired;
|
|
56
58
|
private pairedElsewhere;
|