@spotpatch/runtime 1.7.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -18,12 +18,17 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
23
  UI_MARKER_ATTRIBUTE: () => UI_MARKER_ATTRIBUTE,
24
- bootstrapSpotPatch: () => bootstrapSpotPatch
24
+ bootstrapSpotPatch: () => bootstrapSpotPatch,
25
+ createDataFlowRuntime: () => createDataFlowRuntime,
26
+ getDataFlowRuntime: () => getDataFlowRuntime,
27
+ installDataFlowPrelude: () => installDataFlowPrelude,
28
+ mergeComponentDataFlowReport: () => mergeComponentDataFlowReport,
29
+ mergePageDataFlowReport: () => mergePageDataFlowReport
25
30
  });
26
- module.exports = __toCommonJS(index_exports);
31
+ module.exports = __toCommonJS(src_exports);
27
32
 
28
33
  // src/controller/runtime-controller.ts
29
34
  var import_react_adapter = require("@spotpatch/react-adapter");
@@ -348,15 +353,36 @@ function parseJson(text2) {
348
353
  throw new RuntimeApiError();
349
354
  }
350
355
  }
351
- async function readJsonEnvelope(response) {
356
+ async function readJsonEnvelope(response, maximumBytes = MAX_JSON_RESPONSE_BYTES) {
352
357
  const payload = parseJson(
353
- await readBoundedText(response, MAX_JSON_RESPONSE_BYTES)
358
+ await readBoundedText(response, maximumBytes)
354
359
  );
355
360
  if (!response.ok) {
356
361
  throw new RuntimeApiError(readFailureCode(payload));
357
362
  }
358
363
  return readSuccessData(payload);
359
364
  }
365
+ function isStringArray(value) {
366
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
367
+ }
368
+ function isReportCompleteness(value) {
369
+ return isRecord2(value) && typeof value.complete === "boolean" && typeof value.visitedModules === "number" && typeof value.visitedCallsites === "number" && typeof value.frontierCount === "number";
370
+ }
371
+ function isDataFlowCapability(value) {
372
+ return isRecord2(value) && typeof value.enabled === "boolean" && (value.staticAnalysis === "available" || value.staticAnalysis === "partial" || value.staticAnalysis === "unavailable") && value.runtimeObservation === "dispatch-only" && value.responseShape === "consumed-fields-only" && value.aiAssistance === "disabled" && Array.isArray(value.reasons);
373
+ }
374
+ function isDataFlowDependency(value) {
375
+ return isRecord2(value) && typeof value.id === "string" && typeof value.kind === "string" && typeof value.direction === "string" && typeof value.execution === "string" && typeof value.proof === "string" && typeof value.association === "string" && Array.isArray(value.parameters) && isRecord2(value.response) && isStringArray(value.response.consumedFields) && isStringArray(value.evidenceIds) && isStringArray(value.observationIds);
376
+ }
377
+ function isDataFlowReportBase(value) {
378
+ return isRecord2(value) && value.schemaVersion === import_shared3.DATA_FLOW_SCHEMA_VERSION && typeof value.reportId === "string" && isRecord2(value.baseline) && typeof value.baseline.registryEpoch === "string" && typeof value.baseline.analyzerVersion === "string" && isStringArray(value.baseline.analyzedSourceVersions) && isDataFlowCapability(value.capability) && Array.isArray(value.dependencies) && value.dependencies.every(isDataFlowDependency) && Array.isArray(value.evidence) && Array.isArray(value.diagnostics) && isReportCompleteness(value.completeness);
379
+ }
380
+ function isComponentDataFlowReport(value) {
381
+ return isDataFlowReportBase(value) && isRecord2(value) && isRecord2(value.component) && isRecord2(value.component.source) && typeof value.component.source.fileId === "string" && typeof value.component.source.sourceVersion === "string";
382
+ }
383
+ function isPageDataFlowReport(value) {
384
+ return isDataFlowReportBase(value);
385
+ }
360
386
  function parseCapability(value, expected) {
361
387
  if (!isAgentCapabilitySnapshot(value) || value.providerProfileId !== expected.providerProfileId || value.modelProfileId !== expected.modelProfileId) {
362
388
  throw new RuntimeApiError();
@@ -408,7 +434,7 @@ function createRuntimeApi(options) {
408
434
  }
409
435
  pendingRequests.clear();
410
436
  }
411
- async function requestJson(endpoint, method, body) {
437
+ async function requestJson(endpoint, method, body, maximumResponseBytes = MAX_JSON_RESPONSE_BYTES) {
412
438
  const abortController = new AbortController();
413
439
  pendingRequests.add(abortController);
414
440
  try {
@@ -421,7 +447,7 @@ function createRuntimeApi(options) {
421
447
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
422
448
  signal: abortController.signal
423
449
  });
424
- return await readJsonEnvelope(response);
450
+ return await readJsonEnvelope(response, maximumResponseBytes);
425
451
  } finally {
426
452
  pendingRequests.delete(abortController);
427
453
  }
@@ -515,6 +541,30 @@ function createRuntimeApi(options) {
515
541
  }
516
542
  return Object.freeze({ ...data });
517
543
  },
544
+ async componentDataFlowReport(request) {
545
+ const data = await requestJson(
546
+ import_shared3.SPOTPATCH_ENDPOINTS.dataFlowComponentReport,
547
+ "POST",
548
+ request,
549
+ options.dataFlowReportMaxBytes
550
+ );
551
+ if (!isComponentDataFlowReport(data)) {
552
+ throw new RuntimeApiError();
553
+ }
554
+ return deepFreeze(data);
555
+ },
556
+ async pageDataFlowReport(request) {
557
+ const data = await requestJson(
558
+ import_shared3.SPOTPATCH_ENDPOINTS.dataFlowPageReport,
559
+ "POST",
560
+ request,
561
+ options.dataFlowReportMaxBytes
562
+ );
563
+ if (!isPageDataFlowReport(data)) {
564
+ throw new RuntimeApiError();
565
+ }
566
+ return deepFreeze(data);
567
+ },
518
568
  async openEditor(request) {
519
569
  const data = await requestJson(import_shared3.SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
520
570
  if (!isRecord2(data) || data.editor !== "auto" && data.editor !== "vscode" && data.editor !== "cursor") {
@@ -2455,6 +2505,12 @@ function createBrandMark(document, content) {
2455
2505
  return svg;
2456
2506
  }
2457
2507
 
2508
+ // src/ui/data-flow-panel-contract.ts
2509
+ var DATA_FLOW_EXTENSION_KEY = /* @__PURE__ */ Symbol.for("spotpatch.data-flow.extension.v1");
2510
+ function getDataFlowExtension(target2 = globalThis) {
2511
+ return target2[DATA_FLOW_EXTENSION_KEY];
2512
+ }
2513
+
2458
2514
  // src/ui/dialog-placement.ts
2459
2515
  var VIEWPORT_MARGIN = 16;
2460
2516
  var TARGET_GAP = 14;
@@ -2562,6 +2618,9 @@ var ERROR_MESSAGES_EN = Object.freeze({
2562
2618
  [import_shared6.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The selected source is outside the project.",
2563
2619
  [import_shared6.ERROR_CODES.SOURCE_TOO_LARGE]: "The selected source exceeds the safety limit.",
2564
2620
  [import_shared6.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request failed.",
2621
+ [import_shared6.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is disabled.",
2622
+ [import_shared6.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source changed. Select the component again.",
2623
+ [import_shared6.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "Component data-flow analysis was cancelled.",
2565
2624
  [import_shared6.ERROR_CODES.AI_DISABLED]: "AI execution is disabled in Vite configuration.",
2566
2625
  [import_shared6.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The provider Key environment variable is missing on the Vite process.",
2567
2626
  [import_shared6.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The provider rejected authentication. Check the server-side Key.",
@@ -2597,6 +2656,9 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2597
2656
  [import_shared6.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "\u9009\u4E2D\u6E90\u7801\u4F4D\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\u4E4B\u5916\u3002",
2598
2657
  [import_shared6.ERROR_CODES.SOURCE_TOO_LARGE]: "\u9009\u4E2D\u6E90\u7801\u8D85\u8FC7\u5B89\u5168\u5927\u5C0F\u9650\u5236\u3002",
2599
2658
  [import_shared6.ERROR_CODES.EDITOR_OPEN_FAILED]: "\u7F16\u8F91\u5668\u6253\u5F00\u8BF7\u6C42\u5931\u8D25\u3002",
2659
+ [import_shared6.ERROR_CODES.DATA_FLOW_DISABLED]: "\u7EC4\u4EF6\u6570\u636E\u94FE\u8DEF\u5206\u6790\u672A\u542F\u7528\u3002",
2660
+ [import_shared6.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "\u9009\u4E2D\u6E90\u7801\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u7EC4\u4EF6\u3002",
2661
+ [import_shared6.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "\u7EC4\u4EF6\u6570\u636E\u94FE\u8DEF\u5206\u6790\u5DF2\u53D6\u6D88\u3002",
2600
2662
  [import_shared6.ERROR_CODES.AI_DISABLED]: "Vite \u914D\u7F6E\u672A\u542F\u7528 AI \u6267\u884C\u3002",
2601
2663
  [import_shared6.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "\u542F\u52A8 Vite \u7684\u8FDB\u7A0B\u4E2D\u7F3A\u5C11\u6A21\u578B\u670D\u52A1 Key \u73AF\u5883\u53D8\u91CF\u3002",
2602
2664
  [import_shared6.ERROR_CODES.PROVIDER_AUTH_FAILED]: "\u6A21\u578B\u670D\u52A1\u9274\u6743\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u670D\u52A1\u7AEF Key\u3002",
@@ -2777,14 +2839,14 @@ var UI_MESSAGES = Object.freeze({
2777
2839
  mode: "Execution mode",
2778
2840
  review: "Review",
2779
2841
  autoGated: "Auto gated",
2780
- trustedFast: "Trusted fast",
2842
+ trustedFast: "Trusted direct",
2781
2843
  provider: "Provider",
2782
2844
  model: "Model",
2783
2845
  providerAriaLabel: "AI provider",
2784
2846
  modelAriaLabel: "AI model",
2785
2847
  providerUnavailable: "Provider configuration is unavailable.",
2786
2848
  consent: (provider) => `I understand selected context and allowed source may be sent to ${provider}; its data policy is my responsibility.`,
2787
- trustedFastConsent: (provider) => `Enable trusted fast mode for ${provider}: send allowed project context, include current local changes, and directly apply AI changes after required checks, including file deletions and configuration changes.`,
2849
+ trustedFastConsent: (provider) => `Enable trusted direct mode for ${provider}: send allowed project context, include current local changes, skip project validation checks, and immediately apply AI changes, including file deletions and configuration changes.`,
2788
2850
  connectionNotTested: "Optional connection check not run",
2789
2851
  workspaceNotChecked: "Local workspace not checked",
2790
2852
  checkingWorkspace: "Checking Git workspace and isolated execution\u2026",
@@ -2796,7 +2858,7 @@ var UI_MESSAGES = Object.freeze({
2796
2858
  capabilityVerified: "Agent capability verified",
2797
2859
  capabilityVerifiedAnnouncement: "AI provider capability verified.",
2798
2860
  testingCapability: "Testing authentication, tools, continuation, and streaming\u2026",
2799
- applying: "Applying validated changes to the project.",
2861
+ applying: "Applying changes to the project.",
2800
2862
  cancelling: "Cancelling Agent job.",
2801
2863
  reverting: "Reverting the applied Agent change.",
2802
2864
  consentRequired: "Confirm remote provider data transmission before running AI.",
@@ -2915,14 +2977,14 @@ var UI_MESSAGES = Object.freeze({
2915
2977
  mode: "\u6267\u884C\u65B9\u5F0F",
2916
2978
  review: "\u5BA1\u9605\u6A21\u5F0F",
2917
2979
  autoGated: "\u53D7\u63A7\u81EA\u52A8\u6A21\u5F0F",
2918
- trustedFast: "\u53EF\u4FE1\u5FEB\u901F\u6A21\u5F0F",
2980
+ trustedFast: "\u53EF\u4FE1\u6781\u901F\u6A21\u5F0F",
2919
2981
  provider: "\u6A21\u578B\u670D\u52A1",
2920
2982
  model: "\u6A21\u578B",
2921
2983
  providerAriaLabel: "AI \u6A21\u578B\u670D\u52A1",
2922
2984
  modelAriaLabel: "AI \u6A21\u578B",
2923
2985
  providerUnavailable: "\u6A21\u578B\u670D\u52A1\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
2924
2986
  consent: (provider) => `\u6211\u4E86\u89E3\u9009\u4E2D\u4E0A\u4E0B\u6587\u4E0E\u83B7\u51C6\u6E90\u7801\u53EF\u80FD\u53D1\u9001\u5230 ${provider}\uFF0C\u5E76\u81EA\u884C\u8D1F\u8D23\u5176\u6570\u636E\u7B56\u7565\u3002`,
2925
- trustedFastConsent: (provider) => `\u4E3A ${provider} \u542F\u7528\u53EF\u4FE1\u5FEB\u901F\u6A21\u5F0F\uFF1A\u53D1\u9001\u83B7\u51C6\u7684\u9879\u76EE\u4E0A\u4E0B\u6587\u3001\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\uFF0C\u5E76\u5728\u5FC5\u9700\u68C0\u67E5\u901A\u8FC7\u540E\u76F4\u63A5\u5E94\u7528 AI \u53D8\u66F4\uFF0C\u5305\u62EC\u5220\u9664\u6587\u4EF6\u4E0E\u914D\u7F6E\u53D8\u66F4\u3002`,
2987
+ trustedFastConsent: (provider) => `\u4E3A ${provider} \u542F\u7528\u53EF\u4FE1\u6781\u901F\u6A21\u5F0F\uFF1A\u53D1\u9001\u83B7\u51C6\u7684\u9879\u76EE\u4E0A\u4E0B\u6587\u3001\u7EB3\u5165\u5F53\u524D\u672C\u5730\u4FEE\u6539\u3001\u8DF3\u8FC7\u9879\u76EE\u9A8C\u8BC1\u68C0\u67E5\uFF0C\u5E76\u7ACB\u5373\u5E94\u7528 AI \u53D8\u66F4\uFF0C\u5305\u62EC\u5220\u9664\u6587\u4EF6\u4E0E\u914D\u7F6E\u53D8\u66F4\u3002`,
2926
2988
  connectionNotTested: "\u5C1A\u672A\u6267\u884C\u53EF\u9009\u8FDE\u63A5\u68C0\u67E5",
2927
2989
  workspaceNotChecked: "\u5C1A\u672A\u68C0\u67E5\u672C\u5730\u5DE5\u4F5C\u533A",
2928
2990
  checkingWorkspace: "\u6B63\u5728\u68C0\u67E5 Git \u5DE5\u4F5C\u533A\u4E0E\u9694\u79BB\u6267\u884C\u73AF\u5883\u2026\u2026",
@@ -2934,7 +2996,7 @@ var UI_MESSAGES = Object.freeze({
2934
2996
  capabilityVerified: "Agent \u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7",
2935
2997
  capabilityVerifiedAnnouncement: "AI \u6A21\u578B\u670D\u52A1\u80FD\u529B\u9A8C\u8BC1\u901A\u8FC7\u3002",
2936
2998
  testingCapability: "\u6B63\u5728\u9A8C\u8BC1\u9274\u6743\u3001\u5DE5\u5177\u8C03\u7528\u3001\u8FDE\u7EED\u8C03\u7528\u4E0E\u6D41\u5F0F\u54CD\u5E94\u2026\u2026",
2937
- applying: "\u6B63\u5728\u5C06\u5DF2\u9A8C\u8BC1\u53D8\u66F4\u5E94\u7528\u5230\u9879\u76EE\u3002",
2999
+ applying: "\u6B63\u5728\u5C06\u53D8\u66F4\u5E94\u7528\u5230\u9879\u76EE\u3002",
2938
3000
  cancelling: "\u6B63\u5728\u53D6\u6D88 Agent \u4EFB\u52A1\u3002",
2939
3001
  reverting: "\u6B63\u5728\u64A4\u9500\u5DF2\u5E94\u7528\u7684 Agent \u53D8\u66F4\u3002",
2940
3002
  consentRequired: "\u8FD0\u884C AI \u524D\uFF0C\u8BF7\u5148\u786E\u8BA4\u5141\u8BB8\u5411\u8FDC\u7A0B\u6A21\u578B\u670D\u52A1\u4F20\u8F93\u6570\u636E\u3002",
@@ -3741,7 +3803,18 @@ function summaryLine(summary, prefix) {
3741
3803
  const line = summary.split("\n").find((candidate) => candidate.startsWith(`${prefix}: `));
3742
3804
  return line?.slice(prefix.length + 2).trim();
3743
3805
  }
3744
- function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto") {
3806
+ function createUnavailableDataFlowPanel(document, changesRoot, diagnosticsRoot) {
3807
+ changesRoot.append(diagnosticsRoot);
3808
+ return Object.freeze({
3809
+ root: changesRoot,
3810
+ refreshButton: createButton(document, ""),
3811
+ styles: document.createElement("style"),
3812
+ dispose: () => void 0,
3813
+ render: () => void 0,
3814
+ resetView: () => void 0
3815
+ });
3816
+ }
3817
+ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", dataFlowEnabled = false) {
3745
3818
  const localizer = createUiLocalizer(document, localePreference);
3746
3819
  let messages = localizer.messages();
3747
3820
  const host = document.createElement("spotpatch-root");
@@ -3857,7 +3930,17 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3857
3930
  summary.className = "spotpatch-summary";
3858
3931
  diagnostics.append(diagnosticsLabel, summary);
3859
3932
  const agentPanel = createAgentPanel(document, ai, localizer);
3860
- selectionPanel.append(targetsPanel, diagnostics, agentPanel.root);
3933
+ const changesPanel = createMarkedElement(document, "div");
3934
+ changesPanel.append(targetsPanel, agentPanel.root);
3935
+ const dataFlowPanel = getDataFlowExtension()?.createPanel(
3936
+ document,
3937
+ dataFlowEnabled,
3938
+ localizer.locale,
3939
+ changesPanel,
3940
+ diagnostics,
3941
+ placeDialog
3942
+ ) ?? createUnavailableDataFlowPanel(document, changesPanel, diagnostics);
3943
+ selectionPanel.append(dataFlowPanel.root);
3861
3944
  const previewPanel = createMarkedElement(document, "div");
3862
3945
  previewPanel.className = "spotpatch-preview-panel";
3863
3946
  previewPanel.hidden = true;
@@ -3917,6 +4000,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3917
4000
  liveRegion.setAttribute("aria-atomic", "true");
3918
4001
  shadowRoot.append(
3919
4002
  createStyles(document),
4003
+ dataFlowPanel.styles,
3920
4004
  selectionHighlights,
3921
4005
  highlight,
3922
4006
  dialog,
@@ -3934,6 +4018,13 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3934
4018
  let currentTargets = [];
3935
4019
  let currentMaximum = 0;
3936
4020
  let currentEditorFeedbackState = "idle";
4021
+ let currentDataFlowState = Object.freeze({
4022
+ component: Object.freeze({
4023
+ status: dataFlowEnabled ? "idle" : "disabled"
4024
+ }),
4025
+ page: Object.freeze({ status: dataFlowEnabled ? "idle" : "disabled" }),
4026
+ observationCount: 0
4027
+ });
3937
4028
  function renderEditorStatus(state) {
3938
4029
  currentEditorFeedbackState = state;
3939
4030
  editorFeedback.dataset.state = state;
@@ -4235,6 +4326,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4235
4326
  previewButton.textContent = messages.actions.preview;
4236
4327
  copyButton.textContent = messages.actions.copy;
4237
4328
  backButton.textContent = messages.actions.back;
4329
+ dataFlowPanel.render(currentDataFlowState);
4238
4330
  triggerButton.title = messages.trigger.title(shortcut);
4239
4331
  triggerButton.textContent = currentStatus === "inspecting" ? messages.trigger.stop : messages.trigger.select;
4240
4332
  renderPanelStatus(currentStatus);
@@ -4273,6 +4365,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4273
4365
  repositoryLink,
4274
4366
  previewButton,
4275
4367
  copyButton,
4368
+ dataFlowRefreshButton: dataFlowPanel.refreshButton,
4276
4369
  backButton,
4277
4370
  closeButton,
4278
4371
  agentProviderSelect: agentPanel.providerSelect,
@@ -4293,6 +4386,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4293
4386
  renderPanelStatus(status);
4294
4387
  },
4295
4388
  renderEditorStatus,
4389
+ renderDataFlow(state) {
4390
+ currentDataFlowState = state;
4391
+ dataFlowPanel.render(state);
4392
+ placeDialog();
4393
+ },
4296
4394
  showHighlight(rect, label) {
4297
4395
  currentRect = rect;
4298
4396
  highlight.hidden = false;
@@ -4355,6 +4453,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4355
4453
  agentPanel.setSelectionVisible(false);
4356
4454
  agentPanel.setEditingEnabled(true);
4357
4455
  agentPanel.resetJob();
4456
+ dataFlowPanel.resetView();
4358
4457
  },
4359
4458
  hideSelectionTemporarily() {
4360
4459
  dialog.hidden = true;
@@ -4423,6 +4522,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4423
4522
  diagnostics.removeEventListener("toggle", placeDialog);
4424
4523
  localeButton.removeEventListener("click", localizer.toggle);
4425
4524
  unsubscribeLocale();
4525
+ dataFlowPanel.dispose();
4426
4526
  agentPanel.dispose();
4427
4527
  host.remove();
4428
4528
  }
@@ -4913,9 +5013,16 @@ function resolveBrowserDependencies(dependencies) {
4913
5013
  }
4914
5014
  function createController(config, dependencies = {}) {
4915
5015
  const browser = resolveBrowserDependencies(dependencies);
4916
- const view = dependencies.view ?? createRuntimeView(browser.document, config.shortcut, config.ai, config.locale);
5016
+ const view = dependencies.view ?? createRuntimeView(
5017
+ browser.document,
5018
+ config.shortcut,
5019
+ config.ai,
5020
+ config.locale,
5021
+ config.dataFlow.enabled
5022
+ );
4917
5023
  const api = dependencies.api ?? createRuntimeApi({
4918
5024
  apiBase: config.apiBase,
5025
+ dataFlowReportMaxBytes: config.dataFlow.limits.reportMaxBytes,
4919
5026
  fetch: browser.window.fetch.bind(browser.window),
4920
5027
  sessionToken: config.sessionToken
4921
5028
  });
@@ -4927,6 +5034,9 @@ function createController(config, dependencies = {}) {
4927
5034
  const restoredSelection = selectionSession.load();
4928
5035
  const sourceResolver = createSourceResolver({
4929
5036
  adapter: dependencies.reactAdapter ?? (0, import_react_adapter.createReact18Adapter)({
5037
+ ...config.dataFlow.enabled ? {
5038
+ getComponentRegistration: (component) => getDataFlowExtension()?.getComponentRegistration(component)
5039
+ } : {},
4930
5040
  maxComponentDepth: config.budget.maxComponentDepth
4931
5041
  }),
4932
5042
  onAdapterError() {
@@ -4950,6 +5060,8 @@ function createController(config, dependencies = {}) {
4950
5060
  apiStatus: marker === void 0 ? "not-required" : target2.code === void 0 ? "failed" : "connected",
4951
5061
  code: target2.code,
4952
5062
  collectionStatus: "ready",
5063
+ dataFlowReport: void 0,
5064
+ dataFlowStatus: "idle",
4953
5065
  element: void 0,
4954
5066
  elementContext: target2.element,
4955
5067
  instruction: target2.instruction,
@@ -4964,6 +5076,9 @@ function createController(config, dependencies = {}) {
4964
5076
  let workflowSelectionActive = false;
4965
5077
  let sessionRevision = 0;
4966
5078
  let editorRequestRevision = 0;
5079
+ let dataFlowRequestRevision = 0;
5080
+ let pageDataFlowReport;
5081
+ let pageDataFlowStatus = "idle";
4967
5082
  let addingTarget = false;
4968
5083
  let previewPrompt = "";
4969
5084
  let previousFocus;
@@ -4978,6 +5093,90 @@ function createController(config, dependencies = {}) {
4978
5093
  function activeTarget() {
4979
5094
  return targets.find((target2) => target2.id === activeTargetId) ?? targets.at(-1);
4980
5095
  }
5096
+ function dataFlowRequest(target2) {
5097
+ const react2 = target2.resolution.react;
5098
+ if (react2.componentSourceId !== void 0 && react2.sourceVersion !== void 0) {
5099
+ return Object.freeze({
5100
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5101
+ componentSourceId: react2.componentSourceId,
5102
+ sourceVersion: react2.sourceVersion
5103
+ });
5104
+ }
5105
+ const marker = target2.marker;
5106
+ return marker === void 0 ? void 0 : Object.freeze({
5107
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5108
+ fileId: marker.fileId,
5109
+ line: marker.line,
5110
+ column: marker.column
5111
+ });
5112
+ }
5113
+ function renderDataFlowView() {
5114
+ const dataFlowClient = getDataFlowExtension();
5115
+ const observations = dataFlowClient?.observations(browser.window.location.pathname) ?? [];
5116
+ const current = activeTarget();
5117
+ view.renderDataFlow({
5118
+ component: Object.freeze({
5119
+ status: config.dataFlow.enabled ? current?.dataFlowStatus ?? "idle" : "disabled",
5120
+ ...current?.dataFlowReport === void 0 ? {} : {
5121
+ report: dataFlowClient?.mergeComponentReport(
5122
+ current.dataFlowReport,
5123
+ observations
5124
+ ) ?? current.dataFlowReport
5125
+ }
5126
+ }),
5127
+ page: Object.freeze({
5128
+ status: config.dataFlow.enabled ? pageDataFlowStatus : "disabled",
5129
+ ...pageDataFlowReport === void 0 ? {} : {
5130
+ report: dataFlowClient?.mergePageReport(pageDataFlowReport, observations) ?? pageDataFlowReport
5131
+ }
5132
+ }),
5133
+ observationCount: observations.length
5134
+ });
5135
+ }
5136
+ async function loadDataFlowReports() {
5137
+ if (!config.dataFlow.enabled) {
5138
+ renderDataFlowView();
5139
+ return;
5140
+ }
5141
+ const current = activeTarget();
5142
+ const currentRequest = current === void 0 ? void 0 : dataFlowRequest(current);
5143
+ const pageTargets = targets.flatMap((target2) => {
5144
+ const request = dataFlowRequest(target2);
5145
+ return request === void 0 ? [] : [request];
5146
+ });
5147
+ const revision = ++dataFlowRequestRevision;
5148
+ const selectionRevision = sessionRevision;
5149
+ if (current !== void 0) current.dataFlowStatus = "loading";
5150
+ pageDataFlowStatus = pageTargets.length === 0 ? "idle" : "loading";
5151
+ renderDataFlowView();
5152
+ const [componentResult, pageResult] = await Promise.allSettled([
5153
+ currentRequest === void 0 ? Promise.resolve(void 0) : api.componentDataFlowReport(currentRequest),
5154
+ pageTargets.length === 0 ? Promise.resolve(void 0) : api.pageDataFlowReport({
5155
+ schemaVersion: import_shared9.DATA_FLOW_SCHEMA_VERSION,
5156
+ targets: pageTargets
5157
+ })
5158
+ ]);
5159
+ if (!mounted || revision !== dataFlowRequestRevision || selectionRevision !== sessionRevision) {
5160
+ return;
5161
+ }
5162
+ if (current !== void 0 && targets.includes(current)) {
5163
+ if (componentResult.status === "fulfilled") {
5164
+ current.dataFlowReport = componentResult.value;
5165
+ current.dataFlowStatus = componentResult.value === void 0 ? "idle" : "ready";
5166
+ } else {
5167
+ current.dataFlowReport = void 0;
5168
+ current.dataFlowStatus = "error";
5169
+ }
5170
+ }
5171
+ if (pageResult.status === "fulfilled") {
5172
+ pageDataFlowReport = pageResult.value;
5173
+ pageDataFlowStatus = pageResult.value === void 0 ? "idle" : "ready";
5174
+ } else {
5175
+ pageDataFlowReport = void 0;
5176
+ pageDataFlowStatus = "error";
5177
+ }
5178
+ renderDataFlowView();
5179
+ }
4981
5180
  function snapshotTarget(target2) {
4982
5181
  if (target2.elementContext === void 0 || target2.styles === void 0) {
4983
5182
  return void 0;
@@ -5146,6 +5345,9 @@ ${summary}`;
5146
5345
  resizeObserver?.disconnect();
5147
5346
  targets = [];
5148
5347
  activeTargetId = void 0;
5348
+ dataFlowRequestRevision += 1;
5349
+ pageDataFlowReport = void 0;
5350
+ pageDataFlowStatus = "idle";
5149
5351
  selectionOpen = false;
5150
5352
  workflowSelectionActive = false;
5151
5353
  addingTarget = false;
@@ -5200,6 +5402,7 @@ ${summary}`;
5200
5402
  workflowSelectionActive = true;
5201
5403
  }
5202
5404
  refreshSelectionView(true);
5405
+ void loadDataFlowReports();
5203
5406
  view.focusTargetInstruction(activeTargetId);
5204
5407
  persistSelection();
5205
5408
  return;
@@ -5350,6 +5553,7 @@ ${summary}`;
5350
5553
  transition({ type: "SELECT" });
5351
5554
  view.hideHighlight();
5352
5555
  refreshSelectionView(true);
5556
+ void loadDataFlowReports();
5353
5557
  view.focusTargetInstruction(duplicate.id);
5354
5558
  view.announce(view.messages().announcements.duplicate);
5355
5559
  return;
@@ -5381,7 +5585,9 @@ ${summary}`;
5381
5585
  styles: void 0,
5382
5586
  instruction: "",
5383
5587
  apiStatus: marker === void 0 ? "not-required" : "loading",
5384
- collectionStatus: "loading"
5588
+ collectionStatus: "loading",
5589
+ dataFlowReport: void 0,
5590
+ dataFlowStatus: "idle"
5385
5591
  };
5386
5592
  targets.push(target2);
5387
5593
  activeTargetId = target2.id;
@@ -5391,6 +5597,7 @@ ${summary}`;
5391
5597
  view.hideHighlight();
5392
5598
  resizeObserver?.observe(element2);
5393
5599
  refreshSelectionView(true);
5600
+ void loadDataFlowReports();
5394
5601
  selectionOpen = true;
5395
5602
  view.focusTargetInstruction(target2.id);
5396
5603
  scheduleBrowserContextCollection(target2, revision);
@@ -5495,11 +5702,15 @@ ${summary}`;
5495
5702
  }
5496
5703
  view.hideSelectionTemporarily();
5497
5704
  view.hideSelectionHighlights();
5705
+ pageDataFlowReport = void 0;
5706
+ pageDataFlowStatus = "idle";
5707
+ renderDataFlowView();
5498
5708
  selectionSession.clear();
5499
5709
  view.announce(view.messages().announcements.allTargetsRemoved);
5500
5710
  return;
5501
5711
  }
5502
5712
  refreshSelectionView();
5713
+ void loadDataFlowReports();
5503
5714
  persistSelection();
5504
5715
  view.announce(view.messages().announcements.targetRemoved);
5505
5716
  }
@@ -5662,11 +5873,15 @@ ${summary}`;
5662
5873
  }
5663
5874
  activeTargetId = targetId;
5664
5875
  refreshSelectionView();
5876
+ void loadDataFlowReports();
5665
5877
  view.focusTargetInstruction(targetId);
5666
5878
  }
5667
5879
  function handleReselect() {
5668
5880
  beginReselect();
5669
5881
  }
5882
+ function handleDataFlowRefresh() {
5883
+ void loadDataFlowReports();
5884
+ }
5670
5885
  function mount() {
5671
5886
  if (mounted) {
5672
5887
  return;
@@ -5685,6 +5900,7 @@ ${summary}`;
5685
5900
  view.targetList.addEventListener("input", handleTargetListInput);
5686
5901
  view.targetList.addEventListener("keydown", handleTargetListKeydown);
5687
5902
  view.openEditorButton.addEventListener("click", handleOpenEditorButtonClick);
5903
+ view.dataFlowRefreshButton.addEventListener("click", handleDataFlowRefresh);
5688
5904
  view.previewButton.addEventListener("click", handlePreview);
5689
5905
  view.copyButton.addEventListener("click", handleCopy);
5690
5906
  view.backButton.addEventListener("click", handleBack);
@@ -5755,6 +5971,7 @@ ${summary}`;
5755
5971
  view.targetList.removeEventListener("input", handleTargetListInput);
5756
5972
  view.targetList.removeEventListener("keydown", handleTargetListKeydown);
5757
5973
  view.openEditorButton.removeEventListener("click", handleOpenEditorButtonClick);
5974
+ view.dataFlowRefreshButton.removeEventListener("click", handleDataFlowRefresh);
5758
5975
  view.previewButton.removeEventListener("click", handlePreview);
5759
5976
  view.copyButton.removeEventListener("click", handleCopy);
5760
5977
  view.backButton.removeEventListener("click", handleBack);
@@ -5815,9 +6032,551 @@ function bootstrapSpotPatch(config) {
5815
6032
  target2[RUNTIME_INSTANCE_KEY] = controller;
5816
6033
  controller.mount();
5817
6034
  }
6035
+
6036
+ // src/data-flow/data-flow-runtime.ts
6037
+ var import_data_flow_runtime = require("@spotpatch/shared/data-flow-runtime");
6038
+ var RUNTIME_KEY = /* @__PURE__ */ Symbol.for(
6039
+ "spotpatch.data-flow.runtime.v1"
6040
+ );
6041
+ var REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for("react.memo");
6042
+ var REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for("react.forward_ref");
6043
+ function positiveNow(target2) {
6044
+ return target2.performance?.now() ?? Date.now();
6045
+ }
6046
+ function createOpaqueSequence(prefix) {
6047
+ let sequence = 0;
6048
+ const randomPrefix = (() => {
6049
+ try {
6050
+ const bytes = new Uint8Array(8);
6051
+ globalThis.crypto.getRandomValues(bytes);
6052
+ return Array.from(bytes, (value) => value.toString(36).padStart(2, "0")).join("");
6053
+ } catch {
6054
+ return "local";
6055
+ }
6056
+ })();
6057
+ return () => {
6058
+ sequence += 1;
6059
+ return `${prefix}_${randomPrefix}_${sequence.toString(36)}`;
6060
+ };
6061
+ }
6062
+ function freezeUrl(value, baseUrl) {
6063
+ try {
6064
+ const url = new URL(value, baseUrl);
6065
+ return Object.freeze({
6066
+ origin: url.origin,
6067
+ pathname: url.pathname,
6068
+ queryKeys: Object.freeze(
6069
+ [...new Set(url.searchParams.keys())].sort().slice(0, import_data_flow_runtime.DATA_FLOW_URL_QUERY_KEY_LIMIT)
6070
+ )
6071
+ });
6072
+ } catch {
6073
+ return Object.freeze({
6074
+ pathname: value.split(/[?#]/u, 1)[0] ?? "{invalid}",
6075
+ queryKeys: Object.freeze([])
6076
+ });
6077
+ }
6078
+ }
6079
+ function readFetchUrl(input) {
6080
+ if (typeof input === "string") return input;
6081
+ if (input instanceof URL) return input.toString();
6082
+ return input.url;
6083
+ }
6084
+ function readFetchMethod(input, init) {
6085
+ if (init?.method !== void 0) return init.method.toUpperCase();
6086
+ return typeof Request !== "undefined" && input instanceof Request ? input.method.toUpperCase() : "GET";
6087
+ }
6088
+ function isSpotPatchInternalUrl(value, baseUrl) {
6089
+ try {
6090
+ const pathname = new URL(value, baseUrl).pathname;
6091
+ return pathname === import_data_flow_runtime.SPOTPATCH_API_BASE || pathname.startsWith(`${import_data_flow_runtime.SPOTPATCH_API_BASE}/`);
6092
+ } catch {
6093
+ return false;
6094
+ }
6095
+ }
6096
+ function approximateBytes(value) {
6097
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
6098
+ }
6099
+ function createRingStore(limits, target2) {
6100
+ const entries = [];
6101
+ let totalBytes = 0;
6102
+ function removeExpired(now) {
6103
+ while (entries[0] !== void 0 && now - entries[0].recordedAt > limits.observationTtlMs) {
6104
+ const removed = entries.shift();
6105
+ if (removed !== void 0) totalBytes -= removed.bytes;
6106
+ }
6107
+ }
6108
+ return Object.freeze({
6109
+ add(observation) {
6110
+ const now = positiveNow(target2);
6111
+ removeExpired(now);
6112
+ const bytes = approximateBytes(observation);
6113
+ if (bytes > limits.observationMaxBytes) return;
6114
+ entries.push(Object.freeze({ bytes, observation, recordedAt: now }));
6115
+ totalBytes += bytes;
6116
+ while (entries.length > limits.observationMaxEntries || totalBytes > limits.observationMaxBytes) {
6117
+ const removed = entries.shift();
6118
+ if (removed !== void 0) totalBytes -= removed.bytes;
6119
+ }
6120
+ },
6121
+ clear() {
6122
+ entries.length = 0;
6123
+ totalBytes = 0;
6124
+ },
6125
+ values() {
6126
+ removeExpired(positiveNow(target2));
6127
+ return Object.freeze(entries.map(({ observation }) => observation));
6128
+ }
6129
+ });
6130
+ }
6131
+ function ownPropertyDescriptor(target2, key) {
6132
+ try {
6133
+ return Object.getOwnPropertyDescriptor(target2, key);
6134
+ } catch {
6135
+ return void 0;
6136
+ }
6137
+ }
6138
+ function nestedReactWrapperComponent(candidate) {
6139
+ const marker = ownPropertyDescriptor(candidate, "$$typeof")?.value;
6140
+ const key = marker === REACT_MEMO_TYPE ? "type" : marker === REACT_FORWARD_REF_TYPE ? "render" : void 0;
6141
+ if (key === void 0) return void 0;
6142
+ const nested = ownPropertyDescriptor(candidate, key)?.value;
6143
+ return typeof nested === "object" && nested !== null || typeof nested === "function" ? nested : void 0;
6144
+ }
6145
+ function installWritableDataProperty(target2, key, replacement, original = ownPropertyDescriptor(target2, key)) {
6146
+ if (original === void 0 || !("value" in original) || !original.writable) {
6147
+ return void 0;
6148
+ }
6149
+ try {
6150
+ Object.defineProperty(target2, key, { ...original, value: replacement });
6151
+ } catch {
6152
+ return void 0;
6153
+ }
6154
+ return () => {
6155
+ try {
6156
+ if (ownPropertyDescriptor(target2, key)?.value === replacement) {
6157
+ Object.defineProperty(target2, key, original);
6158
+ }
6159
+ } catch {
6160
+ }
6161
+ };
6162
+ }
6163
+ function recordWithoutAffectingHost(record2) {
6164
+ try {
6165
+ record2();
6166
+ } catch {
6167
+ }
6168
+ }
6169
+ function createDataFlowRuntime(config, target2 = globalThis) {
6170
+ const componentRegistry = /* @__PURE__ */ new WeakMap();
6171
+ const xhrMetadata = /* @__PURE__ */ new WeakMap();
6172
+ const store = createRingStore(config.limits, target2);
6173
+ const nextInvocationId = createOpaqueSequence("invocation");
6174
+ const nextObservationId = createOpaqueSequence("observation");
6175
+ const pageEpoch = createOpaqueSequence("page")();
6176
+ const nextRouteEpoch = createOpaqueSequence("route");
6177
+ let routeEpoch = nextRouteEpoch();
6178
+ let routeKey;
6179
+ let currentInvocation;
6180
+ let currentRequestFrame;
6181
+ let disposed = false;
6182
+ const originalFetch = (() => {
6183
+ try {
6184
+ return target2.fetch;
6185
+ } catch {
6186
+ return void 0;
6187
+ }
6188
+ })();
6189
+ function spotPatchFetch(input, init) {
6190
+ if (originalFetch === void 0) {
6191
+ throw new TypeError("Fetch is unavailable.");
6192
+ }
6193
+ const result = Reflect.apply(originalFetch, this, [input, init]);
6194
+ recordWithoutAffectingHost(() => {
6195
+ const frame = currentRequestFrame;
6196
+ const token = frame?.invocationToken;
6197
+ const rawUrl = readFetchUrl(input);
6198
+ if (isSpotPatchInternalUrl(
6199
+ rawUrl,
6200
+ target2.location?.href ?? "http://spotpatch.invalid/"
6201
+ )) {
6202
+ return;
6203
+ }
6204
+ store.add(
6205
+ Object.freeze({
6206
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6207
+ id: nextObservationId(),
6208
+ pageEpoch,
6209
+ routeEpoch,
6210
+ ...frame === void 0 ? {} : {
6211
+ requestCallsiteId: frame.requestCallsiteId,
6212
+ sourceVersion: frame.sourceVersion
6213
+ },
6214
+ ...token === void 0 ? {} : {
6215
+ invocationId: token.invocationId,
6216
+ componentSourceId: token.componentSourceId,
6217
+ triggerCallsiteId: token.triggerCallsiteId
6218
+ },
6219
+ transport: "fetch",
6220
+ method: readFetchMethod(input, init),
6221
+ url: freezeUrl(rawUrl, target2.location?.href ?? "http://spotpatch.invalid/"),
6222
+ outcome: "dispatched",
6223
+ freshness: "current",
6224
+ diagnosticIds: Object.freeze([])
6225
+ })
6226
+ );
6227
+ });
6228
+ return result;
6229
+ }
6230
+ const restoreFetch = originalFetch === void 0 ? void 0 : installWritableDataProperty(target2, "fetch", spotPatchFetch);
6231
+ const xhrPrototype = (() => {
6232
+ try {
6233
+ return target2.XMLHttpRequest?.prototype;
6234
+ } catch {
6235
+ return void 0;
6236
+ }
6237
+ })();
6238
+ const originalOpenDescriptor = xhrPrototype === void 0 ? void 0 : ownPropertyDescriptor(xhrPrototype, "open");
6239
+ const originalSendDescriptor = xhrPrototype === void 0 ? void 0 : ownPropertyDescriptor(xhrPrototype, "send");
6240
+ function spotPatchOpen(method, url, ...rest) {
6241
+ const original = originalOpenDescriptor?.value;
6242
+ if (typeof original === "function") {
6243
+ Reflect.apply(original, this, [method, url, ...rest]);
6244
+ }
6245
+ recordWithoutAffectingHost(() => {
6246
+ xhrMetadata.set(this, Object.freeze({ method, url: String(url) }));
6247
+ });
6248
+ }
6249
+ function spotPatchSend(body) {
6250
+ const original = originalSendDescriptor?.value;
6251
+ if (typeof original === "function") Reflect.apply(original, this, [body]);
6252
+ recordWithoutAffectingHost(() => {
6253
+ const metadata = xhrMetadata.get(this);
6254
+ if (metadata === void 0 || isSpotPatchInternalUrl(
6255
+ metadata.url,
6256
+ target2.location?.href ?? "http://spotpatch.invalid/"
6257
+ )) {
6258
+ return;
6259
+ }
6260
+ const frame = currentRequestFrame;
6261
+ const token = frame?.invocationToken;
6262
+ store.add(
6263
+ Object.freeze({
6264
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6265
+ id: nextObservationId(),
6266
+ pageEpoch,
6267
+ routeEpoch,
6268
+ ...frame === void 0 ? {} : {
6269
+ requestCallsiteId: frame.requestCallsiteId,
6270
+ sourceVersion: frame.sourceVersion
6271
+ },
6272
+ ...token === void 0 ? {} : {
6273
+ invocationId: token.invocationId,
6274
+ componentSourceId: token.componentSourceId,
6275
+ triggerCallsiteId: token.triggerCallsiteId
6276
+ },
6277
+ transport: "xhr",
6278
+ method: metadata.method.toUpperCase(),
6279
+ url: freezeUrl(
6280
+ metadata.url,
6281
+ target2.location?.href ?? "http://spotpatch.invalid/"
6282
+ ),
6283
+ outcome: "dispatched",
6284
+ freshness: "current",
6285
+ diagnosticIds: Object.freeze([])
6286
+ })
6287
+ );
6288
+ });
6289
+ }
6290
+ const restoreXhrOpen = xhrPrototype === void 0 || typeof originalOpenDescriptor?.value !== "function" ? void 0 : installWritableDataProperty(
6291
+ xhrPrototype,
6292
+ "open",
6293
+ spotPatchOpen,
6294
+ originalOpenDescriptor
6295
+ );
6296
+ const restoreXhrSend = xhrPrototype === void 0 || typeof originalSendDescriptor?.value !== "function" ? void 0 : installWritableDataProperty(
6297
+ xhrPrototype,
6298
+ "send",
6299
+ spotPatchSend,
6300
+ originalSendDescriptor
6301
+ );
6302
+ const runtime = Object.freeze({
6303
+ beginInvocation(metadata) {
6304
+ return Object.freeze({
6305
+ invocationId: nextInvocationId(),
6306
+ componentSourceId: metadata.componentSourceId,
6307
+ triggerCallsiteId: metadata.triggerCallsiteId,
6308
+ sourceVersion: metadata.sourceVersion
6309
+ });
6310
+ },
6311
+ bindInvocation(token, callback) {
6312
+ return function boundInvocation(...args) {
6313
+ return runtime.withInvocation(token, () => Reflect.apply(callback, this, args));
6314
+ };
6315
+ },
6316
+ bindTrigger(metadata, callback) {
6317
+ if (typeof callback !== "function") return callback;
6318
+ const callable = callback;
6319
+ return function boundTrigger(...args) {
6320
+ const token = runtime.beginInvocation(metadata);
6321
+ return runtime.withInvocation(token, () => Reflect.apply(callable, this, args));
6322
+ };
6323
+ },
6324
+ captureInvocation: () => currentInvocation,
6325
+ clear: store.clear,
6326
+ createTrpcLink() {
6327
+ return () => (options) => {
6328
+ recordWithoutAffectingHost(() => {
6329
+ const operation = options.op.path;
6330
+ const operationType = options.op.type;
6331
+ if (typeof operation !== "string" || operation.length === 0 || operation.length > 512 || operationType !== "query" && operationType !== "mutation" && operationType !== "subscription") {
6332
+ return;
6333
+ }
6334
+ const frame = currentRequestFrame;
6335
+ const token = frame?.invocationToken;
6336
+ store.add(
6337
+ Object.freeze({
6338
+ schemaVersion: import_data_flow_runtime.DATA_FLOW_SCHEMA_VERSION,
6339
+ id: nextObservationId(),
6340
+ pageEpoch,
6341
+ routeEpoch,
6342
+ ...frame === void 0 ? {} : {
6343
+ requestCallsiteId: frame.requestCallsiteId,
6344
+ sourceVersion: frame.sourceVersion
6345
+ },
6346
+ ...token === void 0 ? {} : {
6347
+ invocationId: token.invocationId,
6348
+ componentSourceId: token.componentSourceId,
6349
+ triggerCallsiteId: token.triggerCallsiteId
6350
+ },
6351
+ transport: "trpc",
6352
+ method: operationType.toUpperCase(),
6353
+ operation,
6354
+ url: Object.freeze({
6355
+ pathname: operation,
6356
+ queryKeys: Object.freeze([])
6357
+ }),
6358
+ outcome: "dispatched",
6359
+ freshness: "current",
6360
+ diagnosticIds: Object.freeze([])
6361
+ })
6362
+ );
6363
+ });
6364
+ return options.next(options.op);
6365
+ };
6366
+ },
6367
+ dispose() {
6368
+ if (disposed) return;
6369
+ disposed = true;
6370
+ store.clear();
6371
+ restoreFetch?.();
6372
+ restoreXhrOpen?.();
6373
+ restoreXhrSend?.();
6374
+ },
6375
+ getComponentRegistration: (component) => componentRegistry.get(component),
6376
+ getCurrentRequestFrame: () => currentRequestFrame,
6377
+ observations: () => Object.freeze(
6378
+ store.values().map(
6379
+ (observation) => observation.routeEpoch === routeEpoch ? observation : Object.freeze({ ...observation, freshness: "stale-route" })
6380
+ )
6381
+ ),
6382
+ registerComponent(component, componentSourceId, registeredSourceVersion) {
6383
+ const registration = Object.freeze({
6384
+ componentSourceId,
6385
+ sourceVersion: registeredSourceVersion
6386
+ });
6387
+ const pending = [component];
6388
+ const registered = /* @__PURE__ */ new Set();
6389
+ while (pending.length > 0) {
6390
+ const candidate = pending.pop();
6391
+ if (candidate === void 0 || registered.has(candidate)) continue;
6392
+ registered.add(candidate);
6393
+ componentRegistry.set(candidate, registration);
6394
+ const nested = nestedReactWrapperComponent(candidate);
6395
+ if (nested !== void 0) pending.push(nested);
6396
+ }
6397
+ },
6398
+ updateRoute(nextRouteKey) {
6399
+ if (routeKey === void 0) {
6400
+ routeKey = nextRouteKey;
6401
+ } else if (routeKey !== nextRouteKey) {
6402
+ routeKey = nextRouteKey;
6403
+ routeEpoch = nextRouteEpoch();
6404
+ }
6405
+ },
6406
+ withInvocation(token, callback) {
6407
+ const parent = currentInvocation;
6408
+ currentInvocation = token;
6409
+ try {
6410
+ return callback();
6411
+ } finally {
6412
+ currentInvocation = parent;
6413
+ }
6414
+ },
6415
+ withRequestFrame(token, metadata, callback) {
6416
+ const parent = currentRequestFrame;
6417
+ currentRequestFrame = Object.freeze({
6418
+ requestCallsiteId: metadata.requestCallsiteId,
6419
+ sourceVersion: metadata.sourceVersion,
6420
+ ...token === void 0 ? {} : { invocationToken: token }
6421
+ });
6422
+ try {
6423
+ return callback();
6424
+ } finally {
6425
+ currentRequestFrame = parent;
6426
+ }
6427
+ }
6428
+ });
6429
+ return runtime;
6430
+ }
6431
+ function installDataFlowPrelude(config, target2 = globalThis) {
6432
+ if (!config.enabled) return void 0;
6433
+ const runtime = target2[RUNTIME_KEY] ?? createDataFlowRuntime(config, target2);
6434
+ target2[RUNTIME_KEY] = runtime;
6435
+ return runtime;
6436
+ }
6437
+ function getDataFlowRuntime(target2 = globalThis) {
6438
+ return target2[RUNTIME_KEY];
6439
+ }
6440
+
6441
+ // src/data-flow/report-merger.ts
6442
+ var import_shared10 = require("@spotpatch/shared");
6443
+ function observationMatchesDependency(observation, dependency) {
6444
+ const origin = dependency.origin;
6445
+ const transportMatches = dependency.kind === "rpc" ? observation.transport === "trpc" && dependency.operation !== void 0 && observation.operation === dependency.operation : dependency.kind === "http" ? observation.transport === "fetch" || observation.transport === "xhr" : false;
6446
+ if (!transportMatches || observation.freshness !== "current" || origin === void 0 || observation.requestCallsiteId !== origin.requestCallsiteId || observation.sourceVersion !== origin.sourceVersion || dependency.method !== void 0 && observation.method.toUpperCase() !== dependency.method.toUpperCase() || dependency.url !== void 0 && observation.url.pathname !== dependency.url.pathname || dependency.url?.origin !== void 0 && observation.url.origin !== dependency.url.origin || origin.componentSourceId !== void 0 && observation.componentSourceId !== void 0 && observation.componentSourceId !== origin.componentSourceId || origin.triggerCallsiteId !== void 0 && observation.triggerCallsiteId !== void 0 && observation.triggerCallsiteId !== origin.triggerCallsiteId) {
6447
+ return false;
6448
+ }
6449
+ return dependency.association !== "transitive" || observation.componentSourceId === origin.componentSourceId && observation.triggerCallsiteId === origin.triggerCallsiteId;
6450
+ }
6451
+ function runtimeEvidence(observation) {
6452
+ return Object.freeze({
6453
+ id: observation.id,
6454
+ kind: "runtime-observation",
6455
+ summaryKey: "dataFlow.evidence.runtimeDispatch"
6456
+ });
6457
+ }
6458
+ function mergeDependencies(dependencies, observations) {
6459
+ const matchedObservationIds = /* @__PURE__ */ new Set();
6460
+ const merged = dependencies.map((dependency) => {
6461
+ const matches = observations.filter(
6462
+ (observation) => observationMatchesDependency(observation, dependency)
6463
+ );
6464
+ for (const observation of matches) matchedObservationIds.add(observation.id);
6465
+ if (matches.length === 0) return dependency;
6466
+ const observedOrigins = [
6467
+ ...new Set(
6468
+ matches.flatMap(({ url }) => url.origin === void 0 ? [] : [url.origin])
6469
+ )
6470
+ ];
6471
+ const observedOrigin = observedOrigins.length === 1 ? observedOrigins[0] : void 0;
6472
+ return Object.freeze({
6473
+ ...dependency,
6474
+ ...dependency.url === void 0 || dependency.url.origin !== void 0 || observedOrigin === void 0 ? {} : {
6475
+ url: Object.freeze({
6476
+ ...dependency.url,
6477
+ origin: observedOrigin
6478
+ })
6479
+ },
6480
+ execution: "observed",
6481
+ observationIds: Object.freeze([
6482
+ .../* @__PURE__ */ new Set([...dependency.observationIds, ...matches.map(({ id }) => id)])
6483
+ ]),
6484
+ evidenceIds: Object.freeze([
6485
+ .../* @__PURE__ */ new Set([...dependency.evidenceIds, ...matches.map(({ id }) => id)])
6486
+ ])
6487
+ });
6488
+ });
6489
+ return Object.freeze({
6490
+ dependencies: Object.freeze(merged),
6491
+ matchedObservationIds
6492
+ });
6493
+ }
6494
+ function appendRuntimeEvidence(evidence, observations, matchedIds) {
6495
+ const existing = new Set(evidence.map(({ id }) => id));
6496
+ return Object.freeze([
6497
+ ...evidence,
6498
+ ...observations.flatMap(
6499
+ (observation) => matchedIds.has(observation.id) && !existing.has(observation.id) ? [runtimeEvidence(observation)] : []
6500
+ )
6501
+ ]);
6502
+ }
6503
+ function mergeComponentDataFlowReport(report, observations) {
6504
+ const componentObservations = observations.filter(
6505
+ (observation) => observation.componentSourceId === void 0 || observation.componentSourceId === report.component.componentSourceId
6506
+ );
6507
+ const merged = mergeDependencies(report.dependencies, componentObservations);
6508
+ return (0, import_shared10.limitDataFlowReportCollections)(
6509
+ Object.freeze({
6510
+ ...report,
6511
+ dependencies: merged.dependencies,
6512
+ evidence: appendRuntimeEvidence(
6513
+ report.evidence,
6514
+ componentObservations,
6515
+ merged.matchedObservationIds
6516
+ )
6517
+ }),
6518
+ { mode: "observation" }
6519
+ );
6520
+ }
6521
+ function unassignedDependency(observation) {
6522
+ const isRpc = observation.transport === "trpc";
6523
+ return Object.freeze({
6524
+ id: observation.id,
6525
+ kind: isRpc ? "rpc" : "http",
6526
+ direction: observation.method === "GET" || observation.method === "HEAD" || observation.method === "QUERY" || observation.method === "SUBSCRIPTION" ? "read" : "write",
6527
+ execution: "observed",
6528
+ proof: "unavailable",
6529
+ association: "unassigned",
6530
+ method: observation.method,
6531
+ ...isRpc ? observation.operation === void 0 ? {} : { operation: observation.operation } : { url: observation.url },
6532
+ parameters: Object.freeze(
6533
+ (isRpc ? [] : observation.url.queryKeys).map(
6534
+ (path) => Object.freeze({
6535
+ path,
6536
+ position: "query",
6537
+ sensitive: (0, import_shared10.isSensitiveName)(path),
6538
+ valueState: "not-collected",
6539
+ evidenceIds: Object.freeze([observation.id])
6540
+ })
6541
+ )
6542
+ ),
6543
+ response: Object.freeze({
6544
+ consumedFields: Object.freeze([])
6545
+ }),
6546
+ suppliedBindings: Object.freeze([]),
6547
+ locationIds: Object.freeze([]),
6548
+ evidenceIds: Object.freeze([observation.id]),
6549
+ observationIds: Object.freeze([observation.id])
6550
+ });
6551
+ }
6552
+ function mergePageDataFlowReport(report, observations) {
6553
+ const currentObservations = observations.filter(
6554
+ ({ freshness }) => freshness === "current"
6555
+ );
6556
+ const merged = mergeDependencies(report.dependencies, currentObservations);
6557
+ const unassigned = currentObservations.filter(({ id }) => !merged.matchedObservationIds.has(id)).map(unassignedDependency);
6558
+ const allObservationIds = new Set(currentObservations.map(({ id }) => id));
6559
+ return (0, import_shared10.limitDataFlowReportCollections)(
6560
+ Object.freeze({
6561
+ ...report,
6562
+ dependencies: Object.freeze([...merged.dependencies, ...unassigned]),
6563
+ evidence: appendRuntimeEvidence(
6564
+ report.evidence,
6565
+ currentObservations,
6566
+ allObservationIds
6567
+ )
6568
+ }),
6569
+ { mode: "observation" }
6570
+ );
6571
+ }
5818
6572
  // Annotate the CommonJS export names for ESM import in node:
5819
6573
  0 && (module.exports = {
5820
6574
  UI_MARKER_ATTRIBUTE,
5821
- bootstrapSpotPatch
6575
+ bootstrapSpotPatch,
6576
+ createDataFlowRuntime,
6577
+ getDataFlowRuntime,
6578
+ installDataFlowPrelude,
6579
+ mergeComponentDataFlowReport,
6580
+ mergePageDataFlowReport
5822
6581
  });
5823
6582
  //# sourceMappingURL=index.cjs.map