nudge-ui 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/agentCommand.js +4 -28
- package/dist/client.mjs +870 -212
- package/dist/hosts/next/wrapper.js +1 -7
- package/dist/hosts/projectBridge.js +3 -17
- package/dist/inspector/agent/McpConnectionDialog.js +7 -3
- package/dist/inspector/agent/activity.d.ts +1 -1
- package/dist/inspector/agent/activity.js +3 -2
- package/dist/inspector/agent/client.d.ts +2 -2
- package/dist/inspector/agent/client.js +28 -29
- package/dist/inspector/agent/connectionStatus.d.ts +3 -1
- package/dist/inspector/agent/connectionStatus.js +25 -0
- package/dist/inspector/agent/httpTransport.js +31 -147
- package/dist/inspector/agent/protocol.d.ts +1 -1
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +5 -7
- package/dist/inspector/agent/verification.js +67 -20
- package/dist/inspector/boot.d.ts +3 -0
- package/dist/inspector/boot.js +297 -0
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +2 -1
- package/dist/inspector/canvas/AgentActivityOverlay.js +6 -3
- package/dist/inspector/canvas/CanvasCard.js +23 -12
- package/dist/inspector/canvas/CanvasElementOverlay.js +97 -128
- package/dist/inspector/canvas/CanvasToolbar.js +2 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +119 -162
- package/dist/inspector/canvas/LockedWorkspaceNotice.d.ts +3 -2
- package/dist/inspector/canvas/LockedWorkspaceNotice.js +10 -6
- package/dist/inspector/canvas/PageCard.d.ts +10 -0
- package/dist/inspector/canvas/PageCard.js +51 -0
- package/dist/inspector/canvas/SketchToolOptions.d.ts +5 -0
- package/dist/inspector/canvas/SketchToolOptions.js +86 -0
- package/dist/inspector/canvas/StaleChangeIndicator.d.ts +1 -1
- package/dist/inspector/canvas/StaleChangeIndicator.js +1 -1
- package/dist/inspector/canvas/ToolOptionsDrawer.d.ts +21 -0
- package/dist/inspector/canvas/ToolOptionsDrawer.js +77 -0
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +3 -2
- package/dist/inspector/canvas/assets/eraser.svg +28 -0
- package/dist/inspector/canvas/assets/pen.svg +25 -0
- package/dist/inspector/canvas/canvasFrame.d.ts +16 -0
- package/dist/inspector/canvas/canvasFrame.js +10 -0
- package/dist/inspector/canvas/canvasGestures.d.ts +0 -1
- package/dist/inspector/canvas/canvasGestures.js +0 -6
- package/dist/inspector/canvas/canvasStore.d.ts +6 -0
- package/dist/inspector/canvas/canvasStore.js +36 -1
- package/dist/inspector/canvas/frameConnection.d.ts +48 -0
- package/dist/inspector/canvas/frameConnection.js +73 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +18 -1
- package/dist/inspector/canvas/frameProtocol.js +36 -0
- package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
- package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
- package/dist/inspector/canvas/pageSurface.d.ts +11 -0
- package/dist/inspector/canvas/pageSurface.js +34 -0
- package/dist/inspector/canvas/projection.d.ts +19 -9
- package/dist/inspector/canvas/projection.js +58 -31
- package/dist/inspector/canvas/rendererBootstrap.js +4 -1
- package/dist/inspector/canvas/rendererElementSelector.js +9 -1
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +4 -5
- package/dist/inspector/canvas/rendererMessageRouter.js +12 -16
- package/dist/inspector/canvas/rendererSelectionProxy.d.ts +7 -1
- package/dist/inspector/canvas/rendererSelectionProxy.js +8 -36
- package/dist/inspector/canvas/rendererStylesheet.js +15 -0
- package/dist/inspector/canvas/sessionStore.d.ts +2 -2
- package/dist/inspector/canvas/sessionStore.js +12 -9
- package/dist/inspector/canvas/staleChangeDetector.d.ts +1 -1
- package/dist/inspector/canvas/staleChangeDetector.js +10 -22
- package/dist/inspector/changes/codecs.d.ts +2 -0
- package/dist/inspector/changes/codecs.js +27 -3
- package/dist/inspector/changes/draftPreview.d.ts +12 -0
- package/dist/inspector/changes/draftPreview.js +133 -0
- package/dist/inspector/changes/editModel.d.ts +18 -0
- package/dist/inspector/changes/editModel.js +16 -3
- package/dist/inspector/changes/managedStyleProjection.js +2 -1
- package/dist/inspector/changes/previewDiagnostics.js +1 -1
- package/dist/inspector/changes/types.d.ts +18 -1
- package/dist/inspector/comments/CommentsOverlay.js +18 -16
- package/dist/inspector/comments/element.js +10 -3
- package/dist/inspector/comments/store.js +3 -2
- package/dist/inspector/comments/verification.js +1 -1
- package/dist/inspector/componentSemantics/ComponentPropsSection.js +2 -1
- package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
- package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
- package/dist/inspector/componentSemantics/changeAction.js +1 -1
- package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
- package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
- package/dist/inspector/componentSemantics/textBinding.d.ts +1 -2
- package/dist/inspector/componentSemantics/textBinding.js +4 -45
- package/dist/inspector/componentSemantics/types.d.ts +11 -0
- package/dist/inspector/drafts/draftStore.d.ts +109 -0
- package/dist/inspector/drafts/draftStore.js +451 -0
- package/dist/inspector/drafts/model.d.ts +9 -1
- package/dist/inspector/drafts/model.js +5 -1
- package/dist/inspector/drafts/store.d.ts +53 -15
- package/dist/inspector/drafts/store.js +143 -169
- package/dist/inspector/index.d.ts +2 -4
- package/dist/inspector/index.js +2 -264
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +1 -1
- package/dist/inspector/inline-text/inlineTextEditor.js +1 -2
- package/dist/inspector/inline-text/textEditChange.d.ts +1 -1
- package/dist/inspector/inspection/bridge.d.ts +1 -1
- package/dist/inspector/inspection/bridge.js +2 -1
- package/dist/inspector/inspection/browserCssInspection.d.ts +8 -0
- package/dist/inspector/inspection/browserCssInspection.js +28 -6
- package/dist/inspector/inspection/inspectionSnapshot.d.ts +6 -0
- package/dist/inspector/inspection/inspectionSnapshot.js +10 -0
- package/dist/inspector/inspection/performanceDiagnostics.d.ts +18 -0
- package/dist/inspector/inspection/performanceDiagnostics.js +31 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +17 -38
- package/dist/inspector/iterations/capture.d.ts +2 -1
- package/dist/inspector/iterations/capture.js +2 -1
- package/dist/inspector/iterations/client.d.ts +2 -1
- package/dist/inspector/iterations/coordinator.d.ts +4 -3
- package/dist/inspector/iterations/coordinator.js +5 -3
- package/dist/inspector/iterations/prompt.js +9 -8
- package/dist/inspector/overlay/interactionStyles.js +17 -0
- package/dist/inspector/pageInspector.d.ts +42 -0
- package/dist/inspector/pageInspector.js +342 -0
- package/dist/inspector/projection/draftProjection.d.ts +10 -2
- package/dist/inspector/projection/draftProjection.js +9 -1
- package/dist/inspector/projection/managedStylesheet.js +24 -4
- package/dist/inspector/projection/renderedInstance.d.ts +15 -2
- package/dist/inspector/projection/renderedInstance.js +60 -7
- package/dist/inspector/projection/structuralProjection.d.ts +0 -9
- package/dist/inspector/projection/structuralProjection.js +16 -46
- package/dist/inspector/projection/textProjection.d.ts +8 -2
- package/dist/inspector/projection/textProjection.js +77 -24
- package/dist/inspector/prompt/clipboardHandoff.d.ts +14 -0
- package/dist/inspector/prompt/clipboardHandoff.js +47 -16
- package/dist/inspector/prompt/generatePrompt.d.ts +1 -1
- package/dist/inspector/prompt/generatePrompt.js +248 -148
- package/dist/inspector/prompt/promptDocument.d.ts +3 -0
- package/dist/inspector/prompt/promptDocument.js +8 -0
- package/dist/inspector/prompt/promptSettings.js +7 -5
- package/dist/inspector/runtime/inspectorStorage.d.ts +17 -0
- package/dist/inspector/runtime/inspectorStorage.js +42 -0
- package/dist/inspector/runtime/ownedStylesheets.d.ts +20 -0
- package/dist/inspector/runtime/ownedStylesheets.js +72 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +10 -1
- package/dist/inspector/runtime/runtimeIdentityClaims.d.ts +29 -0
- package/dist/inspector/runtime/runtimeIdentityClaims.js +188 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.d.ts +15 -0
- package/dist/inspector/runtime/staticHtmlRuntimeIdentity.js +113 -29
- package/dist/inspector/selection/editScope.d.ts +1 -0
- package/dist/inspector/selection/editScope.js +13 -2
- package/dist/inspector/selection/sourceSite.d.ts +12 -0
- package/dist/inspector/selection/sourceSite.js +28 -0
- package/dist/inspector/shell/AgentStatusButton.js +2 -2
- package/dist/inspector/shell/AppShell.js +6 -2
- package/dist/inspector/shell/ChangesLog.js +27 -9
- package/dist/inspector/shell/CopyPromptButton.js +74 -26
- package/dist/inspector/shell/EmptyState.js +3 -3
- package/dist/inspector/shell/InspectorShell.d.ts +0 -3
- package/dist/inspector/shell/InspectorShell.js +21 -13
- package/dist/inspector/shell/PeekOriginalButton.js +3 -3
- package/dist/inspector/shell/interactionStatePreview.d.ts +9 -0
- package/dist/inspector/shell/interactionStatePreview.js +112 -0
- package/dist/inspector/shell/originalPreview.d.ts +3 -2
- package/dist/inspector/shell/originalPreview.js +10 -37
- package/dist/inspector/shell/stateValue.js +2 -3
- package/dist/inspector/sketch/SketchEditor.js +5 -4
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +2 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +5 -4
- package/dist/inspector/sketch/SketchOverlay.d.ts +3 -5
- package/dist/inspector/sketch/SketchOverlay.js +139 -263
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +1 -1
- package/dist/inspector/sketch/SketchPromptPanel.js +8 -1
- package/dist/inspector/sketch/SketchThumbnail.d.ts +5 -0
- package/dist/inspector/sketch/SketchThumbnail.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +1 -2
- package/dist/inspector/sketch/SketchWorkspace.js +69 -34
- package/dist/inspector/sketch/capture.js +19 -3
- package/dist/inspector/sketch/copySketch.d.ts +10 -0
- package/dist/inspector/sketch/copySketch.js +52 -0
- package/dist/inspector/sketch/drawingTools.d.ts +17 -0
- package/dist/inspector/sketch/drawingTools.js +47 -0
- package/dist/inspector/sketch/frameHeading.d.ts +9 -0
- package/dist/inspector/sketch/frameHeading.js +30 -0
- package/dist/inspector/sketch/freehand.js +3 -3
- package/dist/inspector/sketch/geometry.d.ts +6 -0
- package/dist/inspector/sketch/geometry.js +19 -0
- package/dist/inspector/sketch/history.d.ts +30 -0
- package/dist/inspector/sketch/history.js +58 -0
- package/dist/inspector/sketch/interaction.d.ts +1 -3
- package/dist/inspector/sketch/interaction.js +6 -9
- package/dist/inspector/sketch/model.d.ts +35 -1
- package/dist/inspector/sketch/model.js +116 -61
- package/dist/inspector/sketch/persistence.d.ts +14 -0
- package/dist/inspector/sketch/persistence.js +63 -7
- package/dist/inspector/sketch/raster.d.ts +6 -2
- package/dist/inspector/sketch/raster.js +43 -17
- package/dist/inspector/sketch/store.d.ts +1 -1
- package/dist/inspector/sketch/store.js +1 -1
- package/dist/inspector/sketch/videoFrames.d.ts +6 -0
- package/dist/inspector/sketch/videoFrames.js +81 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +3 -2
- package/dist/inspector/styleEditors/BorderEditor.js +30 -130
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +8 -3
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +2 -2
- package/dist/inspector/styleEditors/borderModel.d.ts +42 -0
- package/dist/inspector/styleEditors/borderModel.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.js +2 -3
- package/dist/inspector/tokens/TokenField.js +40 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/editActions.d.ts +3 -3
- package/dist/inspector/tokens/editActions.js +15 -3
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.d.ts +15 -0
- package/dist/inspector/tokens/resolution/containerQueryEvaluator.js +86 -0
- package/dist/inspector/tokens/resolution/cssomCollector.js +44 -9
- package/dist/inspector/tokens/resolution/nestedSelectors.d.ts +6 -0
- package/dist/inspector/tokens/resolution/nestedSelectors.js +67 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +23 -4
- package/dist/inspector/tokens/resolution.d.ts +6 -3
- package/dist/inspector/tokens/resolution.js +91 -112
- package/dist/inspector/tokens/tokenEdits.d.ts +1 -1
- package/dist/inspector/tokens/tokenEdits.js +1 -1
- package/dist/inspector/ui/InlineStyleWarning.js +2 -2
- package/dist/inspector/ui/InspectorStyle.d.ts +8 -0
- package/dist/inspector/ui/InspectorStyle.js +26 -0
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +3 -3
- package/dist/inspector/ui/Toast.d.ts +14 -0
- package/dist/inspector/ui/Toast.js +30 -0
- package/dist/inspector/ui/assets/canvasIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/canvasIllustration.js +21 -0
- package/dist/inspector/ui/assets/gridIllustration.d.ts +2 -0
- package/dist/inspector/ui/assets/{grid.svg → gridIllustration.js} +3 -2
- package/dist/inspector/ui/customCursors.d.ts +5 -0
- package/dist/inspector/ui/customCursors.js +32 -6
- package/dist/inspector/ui/styles.js +12 -10
- package/dist/inspector/workspace/commands.js +1 -2
- package/dist/inspector/workspace/controller.js +1 -2
- package/dist/inspector/workspace/handoff.js +3 -2
- package/dist/inspector/workspace/sentItems.d.ts +16 -0
- package/dist/inspector/workspace/sentItems.js +33 -0
- package/dist/inspector/workspace/timeline.d.ts +24 -10
- package/dist/inspector/workspace/timeline.js +118 -96
- package/dist/internal/agent-protocol.d.ts +50 -13
- package/dist/internal/agent-protocol.js +145 -19
- package/dist/nudge-ui.mjs +81 -65
- package/dist/project/mcpPackage.d.ts +8 -0
- package/dist/project/mcpPackage.js +60 -0
- package/package.json +13 -3
- package/dist/inspector/canvas/measurementProjection.d.ts +0 -8
- package/dist/inspector/canvas/measurementProjection.js +0 -16
- package/dist/inspector/changes/changesLog.d.ts +0 -47
- package/dist/inspector/changes/changesLog.js +0 -247
- package/dist/inspector/changes/draftChanges.d.ts +0 -56
- package/dist/inspector/changes/draftChanges.js +0 -300
- package/dist/inspector/conformance/projection.d.ts +0 -2
- package/dist/inspector/conformance/projection.js +0 -3
- package/dist/inspector/inline-text/textChangeBoundary.d.ts +0 -6
- package/dist/inspector/inline-text/textChangeBoundary.js +0 -1
- package/dist/inspector/overlay/InspectorOverlay.d.ts +0 -5
- package/dist/inspector/overlay/InspectorOverlay.js +0 -332
- package/dist/inspector/overlay/elementSelector.d.ts +0 -1
- package/dist/inspector/overlay/elementSelector.js +0 -111
- package/dist/inspector/sketch/SketchChanges.d.ts +0 -2
- package/dist/inspector/sketch/SketchChanges.js +0 -72
- package/dist/inspector/sketch/SketchEntryButton.d.ts +0 -4
- package/dist/inspector/sketch/SketchEntryButton.js +0 -11
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +0 -2
- package/dist/inspector/sketch/SketchLayersPanel.js +0 -71
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type StyleRoot = Document | ShadowRoot;
|
|
2
|
+
/** Transient visual state projection, excluded from authored CSS inspection. */
|
|
3
|
+
export declare const STATE_PREVIEW_SHEET_ID = "nudge-ui-state-preview";
|
|
4
|
+
/**
|
|
5
|
+
* Uses constructable stylesheets everywhere without probing the page policy.
|
|
6
|
+
* A blocked probe logs a violation and can send one to the site's CSP report endpoint.
|
|
7
|
+
*/
|
|
8
|
+
export declare function preferAdoptedStylesheets(): void;
|
|
9
|
+
/**
|
|
10
|
+
* A strict `style-src` policy blocks `<style>` elements, leaving them without
|
|
11
|
+
* a sheet, but not constructable stylesheets. Checked once per document.
|
|
12
|
+
*/
|
|
13
|
+
export declare function allowsStyleElements(doc: Document): boolean;
|
|
14
|
+
/** Creates or replaces the adopted sheet stored under `key`, after the root's own stylesheets. */
|
|
15
|
+
export declare function adoptStylesheet(root: StyleRoot, key: string, cssText: string): CSSStyleSheet | null;
|
|
16
|
+
/** The key a sheet was adopted under, or null for sheets the page adopted itself. */
|
|
17
|
+
export declare function adoptedStylesheetKey(root: StyleRoot, sheet: CSSStyleSheet): string | null;
|
|
18
|
+
export declare function getAdoptedStylesheet(root: StyleRoot, key: string): CSSStyleSheet | null;
|
|
19
|
+
export declare function releaseAdoptedStylesheet(root: StyleRoot, key: string): void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Transient visual state projection, excluded from authored CSS inspection. */
|
|
2
|
+
export const STATE_PREVIEW_SHEET_ID = "nudge-ui-state-preview";
|
|
3
|
+
const styleElementSupport = new WeakMap();
|
|
4
|
+
const adoptedSheets = new WeakMap();
|
|
5
|
+
let adoptedOnly = false;
|
|
6
|
+
/**
|
|
7
|
+
* Uses constructable stylesheets everywhere without probing the page policy.
|
|
8
|
+
* A blocked probe logs a violation and can send one to the site's CSP report endpoint.
|
|
9
|
+
*/
|
|
10
|
+
export function preferAdoptedStylesheets() {
|
|
11
|
+
adoptedOnly = true;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A strict `style-src` policy blocks `<style>` elements, leaving them without
|
|
15
|
+
* a sheet, but not constructable stylesheets. Checked once per document.
|
|
16
|
+
*/
|
|
17
|
+
export function allowsStyleElements(doc) {
|
|
18
|
+
if (adoptedOnly)
|
|
19
|
+
return false;
|
|
20
|
+
const known = styleElementSupport.get(doc);
|
|
21
|
+
if (known !== undefined)
|
|
22
|
+
return known;
|
|
23
|
+
const probe = doc.createElement("style");
|
|
24
|
+
probe.setAttribute("data-nudge-ui", "style-policy-probe");
|
|
25
|
+
probe.textContent = ":root {}";
|
|
26
|
+
(doc.head ?? doc.documentElement).appendChild(probe);
|
|
27
|
+
const allowed = probe.sheet !== null;
|
|
28
|
+
probe.remove();
|
|
29
|
+
styleElementSupport.set(doc, allowed);
|
|
30
|
+
return allowed;
|
|
31
|
+
}
|
|
32
|
+
function ownerDocumentOf(root) {
|
|
33
|
+
return root.nodeType === Node.DOCUMENT_NODE ? root : root.ownerDocument;
|
|
34
|
+
}
|
|
35
|
+
/** Creates or replaces the adopted sheet stored under `key`, after the root's own stylesheets. */
|
|
36
|
+
export function adoptStylesheet(root, key, cssText) {
|
|
37
|
+
const view = ownerDocumentOf(root).defaultView;
|
|
38
|
+
if (!view)
|
|
39
|
+
return null;
|
|
40
|
+
let sheets = adoptedSheets.get(root);
|
|
41
|
+
if (!sheets) {
|
|
42
|
+
sheets = new Map();
|
|
43
|
+
adoptedSheets.set(root, sheets);
|
|
44
|
+
}
|
|
45
|
+
let sheet = sheets.get(key);
|
|
46
|
+
if (!sheet) {
|
|
47
|
+
sheet = new view.CSSStyleSheet();
|
|
48
|
+
sheets.set(key, sheet);
|
|
49
|
+
}
|
|
50
|
+
sheet.replaceSync(cssText);
|
|
51
|
+
if (!root.adoptedStyleSheets.includes(sheet))
|
|
52
|
+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
53
|
+
return sheet;
|
|
54
|
+
}
|
|
55
|
+
/** The key a sheet was adopted under, or null for sheets the page adopted itself. */
|
|
56
|
+
export function adoptedStylesheetKey(root, sheet) {
|
|
57
|
+
for (const [key, candidate] of adoptedSheets.get(root) ?? []) {
|
|
58
|
+
if (candidate === sheet)
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
export function getAdoptedStylesheet(root, key) {
|
|
64
|
+
return adoptedSheets.get(root)?.get(key) ?? null;
|
|
65
|
+
}
|
|
66
|
+
export function releaseAdoptedStylesheet(root, key) {
|
|
67
|
+
const sheet = adoptedSheets.get(root)?.get(key);
|
|
68
|
+
if (!sheet)
|
|
69
|
+
return;
|
|
70
|
+
adoptedSheets.get(root)?.delete(key);
|
|
71
|
+
root.adoptedStyleSheets = root.adoptedStyleSheets.filter((candidate) => candidate !== sheet);
|
|
72
|
+
}
|
|
@@ -41,6 +41,8 @@ export interface NudgeUiRuntimeCapabilities {
|
|
|
41
41
|
}
|
|
42
42
|
/** Product features that a host can selectively expose in its inspector UI. */
|
|
43
43
|
export interface NudgeUiRuntimeFeatures {
|
|
44
|
+
/** Whether the annotation sketch tool is available in the Canvas UI. */
|
|
45
|
+
readonly sketch?: boolean;
|
|
44
46
|
/** Whether Canvas can create filesystem-backed HTML iterations. */
|
|
45
47
|
readonly canvasIterations?: boolean;
|
|
46
48
|
}
|
|
@@ -65,6 +67,8 @@ export interface NudgeUiRuntimeConfig {
|
|
|
65
67
|
readonly tokenDiagnostics: readonly TokenCatalogDiagnostic[];
|
|
66
68
|
readonly tokenGeneration: string;
|
|
67
69
|
readonly componentContracts: readonly ComponentContract[];
|
|
70
|
+
/** Why this page cannot be inspected; replaces the selection empty state. */
|
|
71
|
+
readonly inspectionUnavailableReason?: string;
|
|
68
72
|
/** Whether this runtime is the explicit public landing-page demo. */
|
|
69
73
|
readonly demo?: boolean;
|
|
70
74
|
/** Additional same-origin pages seeded into the public demo canvas. */
|
|
@@ -157,8 +157,14 @@ function normalizeFeatures(input) {
|
|
|
157
157
|
if (!isPlainRecord(input)) {
|
|
158
158
|
throw new TypeError('Nudge UI runtime configuration field "features" must be an object.');
|
|
159
159
|
}
|
|
160
|
+
const sketch = optionalFeatureBoolean(input, "sketch");
|
|
160
161
|
const canvasIterations = optionalFeatureBoolean(input, "canvasIterations");
|
|
161
|
-
|
|
162
|
+
if (sketch === undefined && canvasIterations === undefined)
|
|
163
|
+
return {};
|
|
164
|
+
return {
|
|
165
|
+
...(sketch === undefined ? {} : { sketch }),
|
|
166
|
+
...(canvasIterations === undefined ? {} : { canvasIterations }),
|
|
167
|
+
};
|
|
162
168
|
}
|
|
163
169
|
function optionalDemoFlag(input) {
|
|
164
170
|
const value = input.demo;
|
|
@@ -203,6 +209,9 @@ export function normalizeNudgeUiRuntimeConfig(input) {
|
|
|
203
209
|
tokenDiagnostics: optionalArray(input, "tokenDiagnostics"),
|
|
204
210
|
tokenGeneration: optionalString(input, "tokenGeneration"),
|
|
205
211
|
componentContracts: optionalArray(input, "componentContracts"),
|
|
212
|
+
...(input.inspectionUnavailableReason === undefined ? {} : {
|
|
213
|
+
inspectionUnavailableReason: optionalNonEmptyString(input, "inspectionUnavailableReason"),
|
|
214
|
+
}),
|
|
206
215
|
...(demo === true ? { demo: true } : {}),
|
|
207
216
|
...(demoPages === undefined ? {} : { demoPages }),
|
|
208
217
|
...(demoCardLabels === undefined ? {} : { demoCardLabels }),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DraftContents } from "../drafts/model.js";
|
|
2
|
+
/**
|
|
3
|
+
* A generated identity that a saved edit refers to, plus the rendered facts
|
|
4
|
+
* captured with it. Generated identities are document-local: after a reload
|
|
5
|
+
* no element carries them until the claim is re-attached to the one element
|
|
6
|
+
* whose rendered facts still match.
|
|
7
|
+
*/
|
|
8
|
+
export interface RuntimeIdentityClaim {
|
|
9
|
+
readonly cid: string;
|
|
10
|
+
readonly src: string | null;
|
|
11
|
+
readonly tagName: string;
|
|
12
|
+
readonly text: string | null;
|
|
13
|
+
readonly props: string | null;
|
|
14
|
+
readonly ariaLabel: string | null;
|
|
15
|
+
readonly domPath: string | null;
|
|
16
|
+
readonly elementId: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Collects one claim per generated identity referenced by saved edits. */
|
|
19
|
+
export declare function collectRuntimeIdentityClaims({ changes, structuralChanges }: DraftContents): RuntimeIdentityClaim[];
|
|
20
|
+
/**
|
|
21
|
+
* Replaces the generated identities saved edits refer to in one document and
|
|
22
|
+
* attaches each to its element, now and whenever the document's elements are
|
|
23
|
+
* replaced. Unmatched claims stay pending; their edits simply do not apply.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyRuntimeIdentityClaims(doc: Document, claims: readonly RuntimeIdentityClaim[]): void;
|
|
26
|
+
/** Stops reattachment for a disposed document session, including queued work. */
|
|
27
|
+
export declare function disposeRuntimeIdentityClaims(doc: Document): void;
|
|
28
|
+
/** Runtime validation for claims received from another window. */
|
|
29
|
+
export declare function isRuntimeIdentityClaim(value: unknown): value is RuntimeIdentityClaim;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { boundRuntimeEvidence, isRuntimeGeneratedSource, normalizeRuntimeTag, normalizeRuntimeText, RUNTIME_ELEMENT_CID_PREFIX, runtimeDomPath, } from "./staticHtmlRuntimeIdentity.js";
|
|
2
|
+
const states = new WeakMap();
|
|
3
|
+
function isGeneratedCid(cid) {
|
|
4
|
+
return typeof cid === "string" && cid.startsWith(RUNTIME_ELEMENT_CID_PREFIX);
|
|
5
|
+
}
|
|
6
|
+
/** Generated values contain no characters that selector escaping rewrites. */
|
|
7
|
+
function generatedSourceFromSelector(selector) {
|
|
8
|
+
const match = /\[data-src="([^"\\]*)"\]/.exec(selector);
|
|
9
|
+
return match && isRuntimeGeneratedSource(match[1]) ? match[1] : null;
|
|
10
|
+
}
|
|
11
|
+
function claimFromEvidence(cid, src, evidence) {
|
|
12
|
+
return {
|
|
13
|
+
cid,
|
|
14
|
+
src,
|
|
15
|
+
tagName: evidence.tagName,
|
|
16
|
+
text: evidence.text,
|
|
17
|
+
props: evidence.props,
|
|
18
|
+
ariaLabel: evidence.ariaLabel,
|
|
19
|
+
domPath: evidence.domPath ?? null,
|
|
20
|
+
elementId: evidence.elementId ?? null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Collects one claim per generated identity referenced by saved edits. */
|
|
24
|
+
export function collectRuntimeIdentityClaims({ changes, structuralChanges }) {
|
|
25
|
+
const claims = new Map();
|
|
26
|
+
// The page still renders the text from before an inline text edit, so a
|
|
27
|
+
// text edit's original text wins over evidence captured after the preview.
|
|
28
|
+
const originalText = new Map();
|
|
29
|
+
for (const change of changes) {
|
|
30
|
+
if (change.kind === "text-content") {
|
|
31
|
+
const { sourceSite, beforeText } = change.target;
|
|
32
|
+
if (isGeneratedCid(sourceSite.cid))
|
|
33
|
+
originalText.set(sourceSite.cid, normalizeRuntimeText(beforeText));
|
|
34
|
+
if (!isGeneratedCid(sourceSite.cid) || claims.has(sourceSite.cid))
|
|
35
|
+
continue;
|
|
36
|
+
claims.set(sourceSite.cid, {
|
|
37
|
+
cid: sourceSite.cid,
|
|
38
|
+
src: sourceSite.src || null,
|
|
39
|
+
tagName: change.target.tagName ?? "",
|
|
40
|
+
text: null,
|
|
41
|
+
props: change.target.props,
|
|
42
|
+
ariaLabel: change.target.ariaLabel,
|
|
43
|
+
domPath: change.target.domPath ?? null,
|
|
44
|
+
elementId: change.target.elementId ?? null,
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (change.kind !== undefined && change.kind !== "element")
|
|
49
|
+
continue;
|
|
50
|
+
if (!isGeneratedCid(change.cid) || !change.runtimeEvidence || claims.has(change.cid))
|
|
51
|
+
continue;
|
|
52
|
+
claims.set(change.cid, claimFromEvidence(change.cid, generatedSourceFromSelector(change.selector), change.runtimeEvidence));
|
|
53
|
+
}
|
|
54
|
+
for (const change of structuralChanges) {
|
|
55
|
+
const targets = change.kind === "move"
|
|
56
|
+
? [change.target, change.source.parent, change.destination.parent, ...(change.destination.before ? [change.destination.before] : [])]
|
|
57
|
+
: [change.target];
|
|
58
|
+
for (const { sourceSite, locator } of targets) {
|
|
59
|
+
if (!isGeneratedCid(sourceSite.cid) || claims.has(sourceSite.cid))
|
|
60
|
+
continue;
|
|
61
|
+
claims.set(sourceSite.cid, {
|
|
62
|
+
cid: sourceSite.cid,
|
|
63
|
+
src: isRuntimeGeneratedSource(sourceSite.src) ? sourceSite.src : null,
|
|
64
|
+
tagName: locator.tagName ?? "",
|
|
65
|
+
text: locator.text,
|
|
66
|
+
props: boundRuntimeEvidence(locator.props),
|
|
67
|
+
ariaLabel: boundRuntimeEvidence(locator.ariaLabel ?? null),
|
|
68
|
+
domPath: locator.domPath ?? null,
|
|
69
|
+
elementId: locator.elementId ?? null,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...claims.values()].map((claim) => (originalText.has(claim.cid) ? { ...claim, text: originalText.get(claim.cid) } : claim));
|
|
74
|
+
}
|
|
75
|
+
function hasStructuralMatch(element, claim) {
|
|
76
|
+
return normalizeRuntimeTag(element.tagName) === claim.tagName
|
|
77
|
+
&& boundRuntimeEvidence(element.id || null) === claim.elementId
|
|
78
|
+
&& boundRuntimeEvidence(element.getAttribute("aria-label")) === claim.ariaLabel
|
|
79
|
+
&& boundRuntimeEvidence(element.getAttribute("data-cprops")) === claim.props
|
|
80
|
+
&& (claim.domPath === null || runtimeDomPath(element) === claim.domPath);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Finds the single element a claim describes, or null when none or several
|
|
84
|
+
* match. Elements holding authored identity, or another claim's identity,
|
|
85
|
+
* are never taken. Conflicting text evidence leaves the claim unattached.
|
|
86
|
+
*/
|
|
87
|
+
function findClaimedElement(doc, claim, claimed) {
|
|
88
|
+
if (!claim.tagName)
|
|
89
|
+
return null;
|
|
90
|
+
let elements;
|
|
91
|
+
try {
|
|
92
|
+
elements = Array.from(doc.getElementsByTagName(claim.tagName));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const candidates = elements.filter((element) => {
|
|
98
|
+
const cid = element.getAttribute("data-cid");
|
|
99
|
+
if (cid !== null && (!isGeneratedCid(cid) || claimed.has(cid)))
|
|
100
|
+
return false;
|
|
101
|
+
return hasStructuralMatch(element, claim);
|
|
102
|
+
});
|
|
103
|
+
const byText = candidates.filter((element) => normalizeRuntimeText(element.textContent) === claim.text);
|
|
104
|
+
if (byText.length === 1)
|
|
105
|
+
return byText[0];
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
function hasIdentity(doc, cid) {
|
|
109
|
+
for (const element of Array.from(doc.querySelectorAll("[data-cid]"))) {
|
|
110
|
+
if (element.getAttribute("data-cid") === cid)
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function attachClaims(doc, state) {
|
|
116
|
+
const claimed = new Set(state.claims.keys());
|
|
117
|
+
for (const claim of state.claims.values()) {
|
|
118
|
+
if (hasIdentity(doc, claim.cid))
|
|
119
|
+
continue;
|
|
120
|
+
const element = findClaimedElement(doc, claim, claimed);
|
|
121
|
+
if (!element)
|
|
122
|
+
continue;
|
|
123
|
+
element.setAttribute("data-cid", claim.cid);
|
|
124
|
+
if (claim.src !== null)
|
|
125
|
+
element.setAttribute("data-src", claim.src);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function observe(doc, state) {
|
|
129
|
+
if (state.observer || state.claims.size === 0)
|
|
130
|
+
return;
|
|
131
|
+
const Observer = doc.defaultView?.MutationObserver;
|
|
132
|
+
if (!Observer || !doc.documentElement)
|
|
133
|
+
return;
|
|
134
|
+
// Frameworks replace server-rendered elements during hydration and route
|
|
135
|
+
// changes. Re-attach once per mutation batch; attaching only writes to an
|
|
136
|
+
// element that lacks the identity, so it cannot feed itself.
|
|
137
|
+
state.observer = new Observer(() => {
|
|
138
|
+
if (state.queued)
|
|
139
|
+
return;
|
|
140
|
+
state.queued = true;
|
|
141
|
+
queueMicrotask(() => {
|
|
142
|
+
if (states.get(doc) !== state)
|
|
143
|
+
return;
|
|
144
|
+
state.queued = false;
|
|
145
|
+
attachClaims(doc, state);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
state.observer.observe(doc.documentElement, { childList: true, subtree: true });
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Replaces the generated identities saved edits refer to in one document and
|
|
152
|
+
* attaches each to its element, now and whenever the document's elements are
|
|
153
|
+
* replaced. Unmatched claims stay pending; their edits simply do not apply.
|
|
154
|
+
*/
|
|
155
|
+
export function applyRuntimeIdentityClaims(doc, claims) {
|
|
156
|
+
if (claims.length === 0) {
|
|
157
|
+
disposeRuntimeIdentityClaims(doc);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
let state = states.get(doc);
|
|
161
|
+
if (!state) {
|
|
162
|
+
state = { claims: new Map(), observer: null, queued: false };
|
|
163
|
+
states.set(doc, state);
|
|
164
|
+
}
|
|
165
|
+
state.claims = new Map(claims.map((claim) => [claim.cid, claim]));
|
|
166
|
+
attachClaims(doc, state);
|
|
167
|
+
observe(doc, state);
|
|
168
|
+
}
|
|
169
|
+
/** Stops reattachment for a disposed document session, including queued work. */
|
|
170
|
+
export function disposeRuntimeIdentityClaims(doc) {
|
|
171
|
+
states.get(doc)?.observer?.disconnect();
|
|
172
|
+
states.delete(doc);
|
|
173
|
+
}
|
|
174
|
+
/** Runtime validation for claims received from another window. */
|
|
175
|
+
export function isRuntimeIdentityClaim(value) {
|
|
176
|
+
if (!value || typeof value !== "object")
|
|
177
|
+
return false;
|
|
178
|
+
const claim = value;
|
|
179
|
+
const nullableString = (field) => field === null || typeof field === "string";
|
|
180
|
+
return isGeneratedCid(claim.cid)
|
|
181
|
+
&& (claim.src === null || isRuntimeGeneratedSource(claim.src))
|
|
182
|
+
&& typeof claim.tagName === "string"
|
|
183
|
+
&& nullableString(claim.text)
|
|
184
|
+
&& nullableString(claim.props)
|
|
185
|
+
&& nullableString(claim.ariaLabel)
|
|
186
|
+
&& nullableString(claim.domPath)
|
|
187
|
+
&& nullableString(claim.elementId);
|
|
188
|
+
}
|
|
@@ -11,6 +11,11 @@ export declare const RUNTIME_ELEMENT_CID_PREFIX = "nudge-ui-runtime-";
|
|
|
11
11
|
export declare const RUNTIME_EVIDENCE_MAX_LENGTH = 120;
|
|
12
12
|
/** Bounds an untrusted runtime evidence string without changing its content. */
|
|
13
13
|
export declare function boundRuntimeEvidence(value: string | null): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Describes an element by its nearest ancestors so a prompt without source
|
|
16
|
+
* locations still carries searchable class names, ids, and test ids.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runtimeDomPath(element: Element): string | null;
|
|
14
19
|
/** Normalizes rendered text before it becomes durable runtime evidence. */
|
|
15
20
|
export declare function normalizeRuntimeText(value: string | null): string | null;
|
|
16
21
|
/** Keeps a runtime tag suitable for the prompt's inline element notation. */
|
|
@@ -30,3 +35,13 @@ export declare function isRuntimeCreatedElement(element: Element | null | undefi
|
|
|
30
35
|
* page is replaced.
|
|
31
36
|
*/
|
|
32
37
|
export declare function installStaticHtmlRuntimeIdentity(doc?: Document): () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Assigns runtime identity only to elements the pointer reaches: the target,
|
|
40
|
+
* its ancestors, two levels of descendants for the DOM navigator, and its
|
|
41
|
+
* siblings, which structural moves capture as anchors.
|
|
42
|
+
*
|
|
43
|
+
* Unlike `installStaticHtmlRuntimeIdentity`, nothing observes DOM mutations,
|
|
44
|
+
* so a busy application pays no cost for subtrees nobody inspects. Install it
|
|
45
|
+
* before the inspector so its capture listeners run first.
|
|
46
|
+
*/
|
|
47
|
+
export declare function installLazyRuntimeIdentity(doc?: Document): () => void;
|
|
@@ -13,6 +13,40 @@ export const RUNTIME_EVIDENCE_MAX_LENGTH = 120;
|
|
|
13
13
|
export function boundRuntimeEvidence(value) {
|
|
14
14
|
return value === null ? null : value.slice(0, RUNTIME_EVIDENCE_MAX_LENGTH);
|
|
15
15
|
}
|
|
16
|
+
const DOM_PATH_MAX_LENGTH = 240;
|
|
17
|
+
const DOM_PATH_MAX_DEPTH = 4;
|
|
18
|
+
const DOM_PATH_MAX_CLASSES = 3;
|
|
19
|
+
const TEST_ID_ATTRIBUTES = ["data-testid", "data-test", "data-qa"];
|
|
20
|
+
function domPathSegment(element) {
|
|
21
|
+
let segment = element.localName;
|
|
22
|
+
if (element.id)
|
|
23
|
+
segment += `#${element.id}`;
|
|
24
|
+
const testAttribute = TEST_ID_ATTRIBUTES.find((name) => element.hasAttribute(name));
|
|
25
|
+
if (testAttribute)
|
|
26
|
+
segment += `[${testAttribute}="${element.getAttribute(testAttribute)}"]`;
|
|
27
|
+
const classes = Array.from(element.classList).slice(0, DOM_PATH_MAX_CLASSES);
|
|
28
|
+
if (classes.length > 0)
|
|
29
|
+
segment += `.${classes.join(".")}`;
|
|
30
|
+
return segment;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Describes an element by its nearest ancestors so a prompt without source
|
|
34
|
+
* locations still carries searchable class names, ids, and test ids.
|
|
35
|
+
*/
|
|
36
|
+
export function runtimeDomPath(element) {
|
|
37
|
+
const segments = [];
|
|
38
|
+
let current = element;
|
|
39
|
+
while (current && segments.length < DOM_PATH_MAX_DEPTH && !DOCUMENT_STRUCTURE_TAGS.has(current.tagName)) {
|
|
40
|
+
segments.unshift(domPathSegment(current));
|
|
41
|
+
if (current.id)
|
|
42
|
+
break;
|
|
43
|
+
current = current.parentElement;
|
|
44
|
+
}
|
|
45
|
+
if (segments.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
const path = segments.join(" > ");
|
|
48
|
+
return path.length > DOM_PATH_MAX_LENGTH ? `…${path.slice(-DOM_PATH_MAX_LENGTH)}` : path;
|
|
49
|
+
}
|
|
16
50
|
/** Normalizes rendered text before it becomes durable runtime evidence. */
|
|
17
51
|
export function normalizeRuntimeText(value) {
|
|
18
52
|
if (value === null)
|
|
@@ -77,36 +111,8 @@ function existingAttributeValues(doc, name) {
|
|
|
77
111
|
* page is replaced.
|
|
78
112
|
*/
|
|
79
113
|
export function installStaticHtmlRuntimeIdentity(doc = document) {
|
|
80
|
-
const
|
|
81
|
-
const usedSources = existingAttributeValues(doc, "data-src");
|
|
82
|
-
let nextId = 1;
|
|
114
|
+
const ensureIdentity = createIdentityAllocator(doc);
|
|
83
115
|
let disposed = false;
|
|
84
|
-
function nextIdentity() {
|
|
85
|
-
while (true) {
|
|
86
|
-
const id = nextId++;
|
|
87
|
-
const cid = `${RUNTIME_ELEMENT_CID_PREFIX}${id}`;
|
|
88
|
-
const src = `${RUNTIME_UNKNOWN_SOURCE_PREFIX}${id}`;
|
|
89
|
-
if (usedCids.has(cid) || usedSources.has(src))
|
|
90
|
-
continue;
|
|
91
|
-
usedCids.add(cid);
|
|
92
|
-
usedSources.add(src);
|
|
93
|
-
return { cid, src };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function ensureIdentity(element) {
|
|
97
|
-
if (!isEligibleElement(element))
|
|
98
|
-
return;
|
|
99
|
-
const needsCid = element.getAttribute("data-cid") === null;
|
|
100
|
-
const needsSource = element.getAttribute("data-src") === null;
|
|
101
|
-
if (!needsCid && !needsSource)
|
|
102
|
-
return;
|
|
103
|
-
const identity = nextIdentity();
|
|
104
|
-
if (needsCid)
|
|
105
|
-
element.setAttribute("data-cid", identity.cid);
|
|
106
|
-
if (needsSource)
|
|
107
|
-
element.setAttribute("data-src", identity.src);
|
|
108
|
-
runtimeCreatedElements.add(element);
|
|
109
|
-
}
|
|
110
116
|
function scanSubtree(node) {
|
|
111
117
|
if (disposed)
|
|
112
118
|
return;
|
|
@@ -147,3 +153,81 @@ export function installStaticHtmlRuntimeIdentity(doc = document) {
|
|
|
147
153
|
observer.disconnect();
|
|
148
154
|
};
|
|
149
155
|
}
|
|
156
|
+
const LAZY_IDENTITY_EVENTS = ["pointerover", "pointerdown", "mousedown", "click", "dblclick"];
|
|
157
|
+
const LAZY_DESCENDANT_DEPTH = 2;
|
|
158
|
+
/**
|
|
159
|
+
* Assigns runtime identity only to elements the pointer reaches: the target,
|
|
160
|
+
* its ancestors, two levels of descendants for the DOM navigator, and its
|
|
161
|
+
* siblings, which structural moves capture as anchors.
|
|
162
|
+
*
|
|
163
|
+
* Unlike `installStaticHtmlRuntimeIdentity`, nothing observes DOM mutations,
|
|
164
|
+
* so a busy application pays no cost for subtrees nobody inspects. Install it
|
|
165
|
+
* before the inspector so its capture listeners run first.
|
|
166
|
+
*/
|
|
167
|
+
export function installLazyRuntimeIdentity(doc = document) {
|
|
168
|
+
// Pointer order differs between page loads, so sequential numbers would
|
|
169
|
+
// hand a saved edit's identity to whichever element is hovered first after
|
|
170
|
+
// a reload. A per-install scope keeps fresh identities distinct from saved
|
|
171
|
+
// ones; `runtimeIdentityClaims` re-attaches the saved ones by evidence.
|
|
172
|
+
const ensureIdentity = createIdentityAllocator(doc, `${Math.random().toString(36).slice(2, 8)}-`);
|
|
173
|
+
function tagDescendants(element, depth) {
|
|
174
|
+
if (depth === 0)
|
|
175
|
+
return;
|
|
176
|
+
for (const child of Array.from(element.children)) {
|
|
177
|
+
ensureIdentity(child);
|
|
178
|
+
tagDescendants(child, depth - 1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function onPointer(event) {
|
|
182
|
+
const target = event.target;
|
|
183
|
+
if (!(target instanceof Element) || !isEligibleElement(target))
|
|
184
|
+
return;
|
|
185
|
+
for (let current = target; current; current = current.parentElement) {
|
|
186
|
+
// Ancestors of a tagged element were tagged with it.
|
|
187
|
+
if (current.hasAttribute("data-cid") && current !== target)
|
|
188
|
+
break;
|
|
189
|
+
ensureIdentity(current);
|
|
190
|
+
}
|
|
191
|
+
tagDescendants(target, LAZY_DESCENDANT_DEPTH);
|
|
192
|
+
if (target.parentElement)
|
|
193
|
+
tagDescendants(target.parentElement, 1);
|
|
194
|
+
}
|
|
195
|
+
for (const type of LAZY_IDENTITY_EVENTS)
|
|
196
|
+
doc.addEventListener(type, onPointer, true);
|
|
197
|
+
return () => {
|
|
198
|
+
for (const type of LAZY_IDENTITY_EVENTS)
|
|
199
|
+
doc.removeEventListener(type, onPointer, true);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function createIdentityAllocator(doc, scope = "") {
|
|
203
|
+
const usedCids = existingAttributeValues(doc, "data-cid");
|
|
204
|
+
const usedSources = existingAttributeValues(doc, "data-src");
|
|
205
|
+
let nextId = 1;
|
|
206
|
+
function nextIdentity() {
|
|
207
|
+
while (true) {
|
|
208
|
+
const id = nextId++;
|
|
209
|
+
const cid = `${RUNTIME_ELEMENT_CID_PREFIX}${scope}${id}`;
|
|
210
|
+
const src = `${RUNTIME_UNKNOWN_SOURCE_PREFIX}${scope}${id}`;
|
|
211
|
+
if (usedCids.has(cid) || usedSources.has(src))
|
|
212
|
+
continue;
|
|
213
|
+
usedCids.add(cid);
|
|
214
|
+
usedSources.add(src);
|
|
215
|
+
return { cid, src };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function ensureIdentity(element) {
|
|
219
|
+
if (!isEligibleElement(element))
|
|
220
|
+
return;
|
|
221
|
+
const needsCid = element.getAttribute("data-cid") === null;
|
|
222
|
+
const needsSource = element.getAttribute("data-src") === null;
|
|
223
|
+
if (!needsCid && !needsSource)
|
|
224
|
+
return;
|
|
225
|
+
const identity = nextIdentity();
|
|
226
|
+
if (needsCid)
|
|
227
|
+
element.setAttribute("data-cid", identity.cid);
|
|
228
|
+
if (needsSource)
|
|
229
|
+
element.setAttribute("data-src", identity.src);
|
|
230
|
+
runtimeCreatedElements.add(element);
|
|
231
|
+
}
|
|
232
|
+
return ensureIdentity;
|
|
233
|
+
}
|
|
@@ -22,6 +22,7 @@ export declare function relinkElement(el: HTMLElement): string | null;
|
|
|
22
22
|
export declare function getEditScope(el: HTMLElement): EditScope;
|
|
23
23
|
export interface BatchScopeFields {
|
|
24
24
|
scope: EditScope;
|
|
25
|
+
sourceInstanceCount?: number;
|
|
25
26
|
instanceOverride?: RenderedInstanceOverride;
|
|
26
27
|
}
|
|
27
28
|
/** Returns whether every element can be addressed safely by one batch edit. */
|
|
@@ -118,13 +118,24 @@ export function planBatchEditScopes(selectedElements) {
|
|
|
118
118
|
if (!decisions)
|
|
119
119
|
return null;
|
|
120
120
|
const plan = new Map();
|
|
121
|
+
const sourceCounts = new WeakMap();
|
|
121
122
|
for (const [element, decision] of decisions) {
|
|
122
|
-
|
|
123
|
+
const selector = selectorForElement(element);
|
|
124
|
+
let counts = sourceCounts.get(element.ownerDocument);
|
|
125
|
+
if (!counts) {
|
|
126
|
+
counts = new Map();
|
|
127
|
+
sourceCounts.set(element.ownerDocument, counts);
|
|
128
|
+
}
|
|
129
|
+
const count = selector ? counts.get(selector) ?? querySourceSiteMatches(element) : 0;
|
|
130
|
+
if (selector)
|
|
131
|
+
counts.set(selector, count);
|
|
132
|
+
const fields = decision.scope === "new-rendered-instance"
|
|
123
133
|
? {
|
|
124
134
|
scope: "rendered-instance",
|
|
125
135
|
instanceOverride: buildRenderedInstanceOverrideForTarget(decision.target),
|
|
126
136
|
}
|
|
127
|
-
: decision
|
|
137
|
+
: decision;
|
|
138
|
+
plan.set(element, count > 0 ? { ...fields, sourceInstanceCount: count } : fields);
|
|
128
139
|
}
|
|
129
140
|
return plan;
|
|
130
141
|
}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
/** Stable JSX instrumentation selector, shared by source and instance edits. */
|
|
2
2
|
export declare function sourceSiteSelector(cid: string, src: string): string | null;
|
|
3
|
+
/**
|
|
4
|
+
* Finds a source site whose position moved because its file gained or lost
|
|
5
|
+
* lines. It returns a site only when the original renders nothing and exactly
|
|
6
|
+
* one other position in the same file and component renders matching elements.
|
|
7
|
+
*/
|
|
8
|
+
export declare function relocateSourceSite(doc: Document, site: {
|
|
9
|
+
readonly cid: string;
|
|
10
|
+
readonly src: string;
|
|
11
|
+
}, matches: (element: HTMLElement) => boolean): {
|
|
12
|
+
cid: string;
|
|
13
|
+
src: string;
|
|
14
|
+
} | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeAttrValue } from "../projection/cssEscapes.js";
|
|
2
|
+
const SOURCE_POSITION = /^(.+):\d+:\d+$/;
|
|
2
3
|
/** Stable JSX instrumentation selector, shared by source and instance edits. */
|
|
3
4
|
export function sourceSiteSelector(cid, src) {
|
|
4
5
|
if (!cid)
|
|
@@ -10,3 +11,30 @@ export function sourceSiteSelector(cid, src) {
|
|
|
10
11
|
// single formatted line share a managed rule.
|
|
11
12
|
return `[data-cid="${escapeAttrValue(cid)}"][data-src="${escapeAttrValue(src)}"]`;
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Finds a source site whose position moved because its file gained or lost
|
|
16
|
+
* lines. It returns a site only when the original renders nothing and exactly
|
|
17
|
+
* one other position in the same file and component renders matching elements.
|
|
18
|
+
*/
|
|
19
|
+
export function relocateSourceSite(doc, site, matches) {
|
|
20
|
+
const file = SOURCE_POSITION.exec(site.src)?.[1];
|
|
21
|
+
const exact = sourceSiteSelector(site.cid, site.src);
|
|
22
|
+
if (!file || !exact)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
if (doc.querySelector(exact))
|
|
26
|
+
return null;
|
|
27
|
+
const sites = new Set();
|
|
28
|
+
const sameFile = `[data-cid="${escapeAttrValue(site.cid)}"][data-src^="${escapeAttrValue(`${file}:`)}"]`;
|
|
29
|
+
for (const element of doc.querySelectorAll(sameFile)) {
|
|
30
|
+
const src = element.getAttribute("data-src") ?? "";
|
|
31
|
+
if (SOURCE_POSITION.exec(src)?.[1] === file && matches(element))
|
|
32
|
+
sites.add(src);
|
|
33
|
+
}
|
|
34
|
+
const [src] = sites;
|
|
35
|
+
return sites.size === 1 && src ? { cid: site.cid, src } : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { IconRobot } from "@tabler/icons-react";
|
|
3
3
|
import { useAgentClient } from "../agent/client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useAgentConnectionStatus } from "../agent/connectionStatus.js";
|
|
5
5
|
import { useNudgeUiRuntimeConfig } from "../runtime/useRuntimeConfig.js";
|
|
6
6
|
import { Button } from "../ui/Button.js";
|
|
7
7
|
import { Tooltip } from "../ui/Tooltip.js";
|
|
@@ -29,7 +29,7 @@ export function AgentStatusButton({ onOpenSettings }) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Renders the connection snapshot provided by the project client. */
|
|
31
31
|
export function AgentStatusButtonView({ agent, onOpenSettings }) {
|
|
32
|
-
const status =
|
|
32
|
+
const status = useAgentConnectionStatus(agent);
|
|
33
33
|
const connected = agent.paired || agent.pairedElsewhere;
|
|
34
34
|
const listening = connected && agent.listenerActive;
|
|
35
35
|
return (_jsx(Tooltip, { content: connected ? status.label : "Agent & MCP settings", stableTrigger: true, children: _jsxs(Button, { variant: "quiet", className: "agent-status-button", "aria-label": connected ? `MCP · ${status.label}` : "Settings", "data-test": "settings-button", "data-mcp-connected": connected ? "true" : "false", "data-mcp-listening": listening ? "true" : "false", onClick: () => onOpenSettings("mcp"), children: [connected ? _jsx(ConnectedAgentAvatar, { name: agent.agentClientName }) : _jsxs("span", { className: "agent-status-button__avatars", children: [_jsx(CodexAvatar, {}), _jsx(ClaudeAvatar, {}), _jsx(CursorAvatar, {})] }), _jsx("span", { className: "agent-status-button__label", children: connected ? "MCP" : "Settings" }), connected ? (_jsx("span", { className: "agent-status-button__dot", "data-test": "agent-status-dot", "aria-hidden": "true" })) : null] }) }));
|