@spotpatch/runtime 1.12.0 → 1.14.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.
Files changed (38) hide show
  1. package/dist/{chunk-BXRMVHNT.js → chunk-ASRUAKA5.js} +1 -1
  2. package/dist/chunk-ASRUAKA5.js.map +1 -0
  3. package/dist/chunk-KIDHJQRG.js +14 -0
  4. package/dist/chunk-KIDHJQRG.js.map +1 -0
  5. package/dist/{chunk-7ES63LA7.js → chunk-P6X75LO5.js} +11 -1
  6. package/dist/chunk-P6X75LO5.js.map +1 -0
  7. package/dist/chunk-P7W3F4UA.js +115 -0
  8. package/dist/chunk-P7W3F4UA.js.map +1 -0
  9. package/dist/contextual-ask-panel.cjs +1637 -0
  10. package/dist/contextual-ask-panel.cjs.map +1 -0
  11. package/dist/contextual-ask-panel.d.cts +163 -0
  12. package/dist/contextual-ask-panel.d.ts +163 -0
  13. package/dist/contextual-ask-panel.js +1593 -0
  14. package/dist/contextual-ask-panel.js.map +1 -0
  15. package/dist/data-flow-panel.cjs +9 -0
  16. package/dist/data-flow-panel.cjs.map +1 -1
  17. package/dist/data-flow-panel.js +1 -1
  18. package/dist/external-handoff-panel.cjs +29 -9
  19. package/dist/external-handoff-panel.cjs.map +1 -1
  20. package/dist/external-handoff-panel.d.cts +3 -3
  21. package/dist/external-handoff-panel.d.ts +3 -3
  22. package/dist/external-handoff-panel.js +22 -11
  23. package/dist/external-handoff-panel.js.map +1 -1
  24. package/dist/index.cjs +1285 -279
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +1250 -356
  27. package/dist/index.js.map +1 -1
  28. package/dist/motion-extension-contract-B3m26CML.d.cts +72 -0
  29. package/dist/motion-extension-contract-B3m26CML.d.ts +72 -0
  30. package/dist/motion.cjs +1251 -0
  31. package/dist/motion.cjs.map +1 -0
  32. package/dist/motion.d.cts +11 -0
  33. package/dist/motion.d.ts +11 -0
  34. package/dist/motion.js +1092 -0
  35. package/dist/motion.js.map +1 -0
  36. package/package.json +31 -3
  37. package/dist/chunk-7ES63LA7.js.map +0 -1
  38. package/dist/chunk-BXRMVHNT.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ getContextualAskExtension
3
+ } from "./chunk-KIDHJQRG.js";
1
4
  import {
2
5
  getDataFlowExtension
3
6
  } from "./chunk-NII5OBAN.js";
@@ -10,13 +13,18 @@ import {
10
13
  } from "./chunk-2WA6XVA4.js";
11
14
  import {
12
15
  getExternalHandoffExtension
13
- } from "./chunk-BXRMVHNT.js";
16
+ } from "./chunk-ASRUAKA5.js";
17
+ import {
18
+ createBrandMark,
19
+ getFloatingSurfaceMotionExtension
20
+ } from "./chunk-P7W3F4UA.js";
14
21
  import {
22
+ FLOATING_SURFACE_LAYOUT,
15
23
  UI_MARKER_ATTRIBUTE,
16
24
  UI_Z_INDEX,
17
25
  createButton,
18
26
  createMarkedElement
19
- } from "./chunk-7ES63LA7.js";
27
+ } from "./chunk-P6X75LO5.js";
20
28
 
21
29
  // src/controller/runtime-controller.ts
22
30
  import { createReact18Adapter } from "@spotpatch/react-adapter";
@@ -430,7 +438,7 @@ function omitBrowserCodeContext(request) {
430
438
  };
431
439
  }
432
440
  function runtimeApiErrorCode(error) {
433
- return error instanceof RuntimeApiError ? error.code : void 0;
441
+ return error instanceof RuntimeApiError && isAgentErrorCode(error.code) ? error.code : void 0;
434
442
  }
435
443
  function createRuntimeApi(options) {
436
444
  const pendingRequests = /* @__PURE__ */ new Set();
@@ -1978,6 +1986,7 @@ function createAgentPanel(document, ai, localizer) {
1978
1986
  root.hidden = !ai.enabled;
1979
1987
  const header = createMarkedElement(document, "div");
1980
1988
  header.className = "spotpatch-agent-head";
1989
+ header.setAttribute("data-spotpatch-agent-identity", "");
1981
1990
  const title = createMarkedElement(document, "span");
1982
1991
  title.className = "spotpatch-agent-title";
1983
1992
  const badge = createMarkedElement(document, "span");
@@ -2419,151 +2428,6 @@ function createAgentPanel(document, ai, localizer) {
2419
2428
  });
2420
2429
  }
2421
2430
 
2422
- // src/ui/brand-mark-content.ts
2423
- var BRAND_MARK_CONTENT = `
2424
- <defs>
2425
- <linearGradient id="locator-gradient" x1="76" y1="92" x2="436" y2="374" gradientUnits="userSpaceOnUse">
2426
- <stop offset="0" stop-color="#B61CFF" />
2427
- <stop offset="0.38" stop-color="#6D35FF" />
2428
- <stop offset="0.72" stop-color="#168EFF" />
2429
- <stop offset="1" stop-color="#00D9E9" />
2430
- </linearGradient>
2431
- <linearGradient id="left-code-gradient" x1="165" y1="166" x2="236" y2="258" gradientUnits="userSpaceOnUse">
2432
- <stop stop-color="#A51EFF" />
2433
- <stop offset="1" stop-color="#653BFF" />
2434
- </linearGradient>
2435
- <linearGradient id="right-code-gradient" x1="276" y1="166" x2="347" y2="258" gradientUnits="userSpaceOnUse">
2436
- <stop stop-color="#158DFF" />
2437
- <stop offset="1" stop-color="#00D8E9" />
2438
- </linearGradient>
2439
- <linearGradient id="bolt-gradient" x1="270" y1="111" x2="252" y2="365" gradientUnits="userSpaceOnUse">
2440
- <stop stop-color="#6840FF" />
2441
- <stop offset="0.48" stop-color="#257BFF" />
2442
- <stop offset="1" stop-color="#00CBEF" />
2443
- </linearGradient>
2444
- </defs>
2445
- <path
2446
- fill="url(#locator-gradient)"
2447
- fill-rule="evenodd"
2448
- clip-rule="evenodd"
2449
- d="M256 52C345.47 52 418 124.53 418 214C418 267.55 391.98 316.24 354.04 348.02L256 468L157.96 348.02C120.02 316.24 94 267.55 94 214C94 124.53 166.53 52 256 52ZM256 88C186.41 88 130 144.41 130 214C130 258.2 152.76 297.08 187.2 319.57L256 403.8L324.8 319.57C359.24 297.08 382 258.2 382 214C382 144.41 325.59 88 256 88Z"
2450
- />
2451
- <rect x="238" y="20" width="36" height="84" rx="4" fill="url(#locator-gradient)" />
2452
- <rect x="62" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2453
- <rect x="366" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2454
- <path
2455
- d="M213.5 160L158 211.5L213.5 263L238 236.5L211 211.5L238 186.5L213.5 160Z"
2456
- fill="url(#left-code-gradient)"
2457
- />
2458
- <path
2459
- d="M298.5 160L354 211.5L298.5 263L274 236.5L301 211.5L274 186.5L298.5 160Z"
2460
- fill="url(#right-code-gradient)"
2461
- />
2462
- <path
2463
- d="M283 108L232 212L266 253L238 369L302 237L267 198L283 108Z"
2464
- fill="url(#bolt-gradient)"
2465
- />
2466
- `;
2467
-
2468
- // src/ui/brand-mark.ts
2469
- var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
2470
- function resolveBrandMarkContent(override) {
2471
- if (override !== void 0) {
2472
- return { content: override, viewBox: "0 0 512 512" };
2473
- }
2474
- const injected = typeof __SPOTPATCH_BRAND_MARK_CONTENT__ === "string" ? __SPOTPATCH_BRAND_MARK_CONTENT__ : void 0;
2475
- if (typeof injected === "string") {
2476
- return { content: injected, viewBox: "0 0 512 512" };
2477
- }
2478
- return true ? { content: BRAND_MARK_CONTENT, viewBox: "0 0 512 512" } : { content: FALLBACK_BRAND_MARK_CONTENT, viewBox: "0 0 1 1" };
2479
- }
2480
- function svgElement(document, tagName, attributes) {
2481
- const element2 = document.createElementNS(SVG_NAMESPACE, tagName);
2482
- for (const [name, value] of Object.entries(attributes)) {
2483
- element2.setAttribute(name, value);
2484
- }
2485
- return element2;
2486
- }
2487
- function createBrandMark(document, content) {
2488
- const resolved = resolveBrandMarkContent(content);
2489
- const svg = svgElement(document, "svg", {
2490
- xmlns: SVG_NAMESPACE,
2491
- class: "spotpatch-brand-mark",
2492
- viewBox: resolved.viewBox,
2493
- "aria-hidden": "true",
2494
- focusable: "false"
2495
- });
2496
- svg.innerHTML = resolved.content;
2497
- return svg;
2498
- }
2499
-
2500
- // src/ui/dialog-placement.ts
2501
- var VIEWPORT_MARGIN = 16;
2502
- var TARGET_GAP = 14;
2503
- var TARGET_INSET = 24;
2504
- var ANCHOR_INSET = 22;
2505
- function clamp(value, minimum, maximum) {
2506
- return Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
2507
- }
2508
- function calculateDialogPlacement({
2509
- dialogHeight,
2510
- dialogWidth,
2511
- target: target2,
2512
- viewportHeight,
2513
- viewportWidth
2514
- }) {
2515
- const maxLeft = viewportWidth - dialogWidth - VIEWPORT_MARGIN;
2516
- const maxTop = viewportHeight - dialogHeight - VIEWPORT_MARGIN;
2517
- const viewportLeft = clamp(
2518
- (viewportWidth - dialogWidth) / 2,
2519
- VIEWPORT_MARGIN,
2520
- maxLeft
2521
- );
2522
- const viewportTop = clamp(
2523
- (viewportHeight - dialogHeight) / 2,
2524
- VIEWPORT_MARGIN,
2525
- maxTop
2526
- );
2527
- if (target2 === void 0) {
2528
- return Object.freeze({
2529
- anchorX: clamp(dialogWidth / 2, ANCHOR_INSET, dialogWidth - ANCHOR_INSET),
2530
- anchorY: clamp(dialogHeight / 2, ANCHOR_INSET, dialogHeight - ANCHOR_INSET),
2531
- left: viewportLeft,
2532
- mode: "viewport",
2533
- top: viewportTop
2534
- });
2535
- }
2536
- const targetCenterX = target2.x + target2.width / 2;
2537
- const targetCenterY = target2.y + target2.height / 2;
2538
- const centeredLeft = clamp(targetCenterX - dialogWidth / 2, VIEWPORT_MARGIN, maxLeft);
2539
- const centeredTop = clamp(targetCenterY - dialogHeight / 2, VIEWPORT_MARGIN, maxTop);
2540
- let left = centeredLeft;
2541
- let top = centeredTop;
2542
- let mode = "viewport";
2543
- if (target2.width >= dialogWidth + TARGET_INSET * 2 && target2.height >= dialogHeight + TARGET_INSET * 2) {
2544
- mode = "center";
2545
- } else if (target2.y - dialogHeight - TARGET_GAP >= VIEWPORT_MARGIN) {
2546
- top = target2.y - dialogHeight - TARGET_GAP;
2547
- mode = "above";
2548
- } else if (target2.y + target2.height + TARGET_GAP + dialogHeight <= viewportHeight - VIEWPORT_MARGIN) {
2549
- top = target2.y + target2.height + TARGET_GAP;
2550
- mode = "below";
2551
- } else if (target2.x + target2.width + TARGET_GAP + dialogWidth <= viewportWidth - VIEWPORT_MARGIN) {
2552
- left = target2.x + target2.width + TARGET_GAP;
2553
- mode = "right";
2554
- } else if (target2.x - dialogWidth - TARGET_GAP >= VIEWPORT_MARGIN) {
2555
- left = target2.x - dialogWidth - TARGET_GAP;
2556
- mode = "left";
2557
- }
2558
- return Object.freeze({
2559
- anchorX: clamp(targetCenterX - left, ANCHOR_INSET, dialogWidth - ANCHOR_INSET),
2560
- anchorY: clamp(targetCenterY - top, ANCHOR_INSET, dialogHeight - ANCHOR_INSET),
2561
- left,
2562
- mode,
2563
- top
2564
- });
2565
- }
2566
-
2567
2431
  // src/ui/localization.ts
2568
2432
  import {
2569
2433
  ERROR_CODES as ERROR_CODES2
@@ -2598,6 +2462,51 @@ var STATUS_ZH = Object.freeze({
2598
2462
  reverted: "\u5DF2\u64A4\u9500",
2599
2463
  failed: "\u5931\u8D25"
2600
2464
  });
2465
+ var EXECUTION_ACTIVITY_EN = Object.freeze({
2466
+ prepare: "Preparing context",
2467
+ dispatch: "Dispatching context",
2468
+ discover: "Discovering project files",
2469
+ search: "Searching source",
2470
+ read: "Reading",
2471
+ patch: "Generating a minimal patch",
2472
+ check: "Running",
2473
+ audit: "Auditing proposed changes",
2474
+ apply: "Applying verified changes",
2475
+ sync: "Returning results to SpotPatch",
2476
+ unknown: "Processing the current task"
2477
+ });
2478
+ var EXECUTION_ACTIVITY_ZH = Object.freeze({
2479
+ prepare: "\u6B63\u5728\u51C6\u5907\u4E0A\u4E0B\u6587",
2480
+ dispatch: "\u6B63\u5728\u6D3E\u53D1\u4E0A\u4E0B\u6587",
2481
+ discover: "\u6B63\u5728\u67E5\u627E\u9879\u76EE\u6587\u4EF6",
2482
+ search: "\u6B63\u5728\u641C\u7D22\u6E90\u7801",
2483
+ read: "\u6B63\u5728\u8BFB\u53D6",
2484
+ patch: "\u6B63\u5728\u751F\u6210\u6700\u5C0F\u4FEE\u6539\u8865\u4E01",
2485
+ check: "\u6B63\u5728\u8FD0\u884C",
2486
+ audit: "\u6B63\u5728\u5BA1\u8BA1\u5019\u9009\u4FEE\u6539",
2487
+ apply: "\u6B63\u5728\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6539",
2488
+ sync: "\u6B63\u5728\u5C06\u7ED3\u679C\u56DE\u6D41 SpotPatch",
2489
+ unknown: "\u6B63\u5728\u5904\u7406\u5F53\u524D\u4EFB\u52A1"
2490
+ });
2491
+ var EXECUTION_LANE_LABELS = Object.freeze({
2492
+ prepare: "prepare",
2493
+ dispatch: "dispatch",
2494
+ discover: "list",
2495
+ search: "search",
2496
+ read: "read",
2497
+ patch: "patch",
2498
+ check: "check",
2499
+ audit: "audit",
2500
+ apply: "apply",
2501
+ sync: "sync",
2502
+ unknown: "agent"
2503
+ });
2504
+ function executionActivityAction(labels, kind, detail) {
2505
+ return detail === void 0 || detail.length === 0 ? labels[kind] : `${labels[kind]} ${detail}`;
2506
+ }
2507
+ function executionActivityLane(kind, detail) {
2508
+ return detail === void 0 || detail.length === 0 ? EXECUTION_LANE_LABELS[kind] : `${EXECUTION_LANE_LABELS[kind]} \xB7 ${detail}`;
2509
+ }
2601
2510
  var EXTERNAL_HANDOFF_ERROR_EN = "The external Agent handoff request failed.";
2602
2511
  var EXTERNAL_HANDOFF_ERROR_ZH = "\u5916\u90E8 Agent \u4EA4\u63A5\u8BF7\u6C42\u5931\u8D25\u3002";
2603
2512
  var ERROR_MESSAGES_EN = Object.freeze({
@@ -2815,6 +2724,11 @@ var UI_MESSAGES = Object.freeze({
2815
2724
  previewTitle: "Review the request",
2816
2725
  previewSubtitle: "Verify the complete context before it leaves the browser."
2817
2726
  }),
2727
+ floatingSurface: Object.freeze({
2728
+ dragHandle: "Drag to move the SpotPatch workbench",
2729
+ resetPosition: "Reset workbench position",
2730
+ positionReset: "SpotPatch position reset to the lower-right corner."
2731
+ }),
2818
2732
  context: Object.freeze({
2819
2733
  collecting: "Collecting context",
2820
2734
  ready: "Context ready",
@@ -2901,6 +2815,27 @@ var UI_MESSAGES = Object.freeze({
2901
2815
  noOutput: "No output.",
2902
2816
  status: (status) => STATUS_EN[status]
2903
2817
  }),
2818
+ execution: Object.freeze({
2819
+ claude: "Claude",
2820
+ codex: "Codex",
2821
+ receivingTitle: "Receiving context",
2822
+ dispatchingTitle: (identity) => `Handing off to ${identity}`,
2823
+ runningTitle: (target2) => `Modifying ${target2}`,
2824
+ expandedRunningTitle: (identity) => `${identity} is making changes`,
2825
+ completedTitle: "Change complete",
2826
+ expandedCompletedTitle: "Changes complete",
2827
+ failedTitle: "Execution failed",
2828
+ receivingStatus: "Receiving",
2829
+ dispatchingStatus: "Dispatching",
2830
+ runningStatus: "Running",
2831
+ checkingStatus: "Checking",
2832
+ completedStatus: "Done",
2833
+ failedStatus: "Failed",
2834
+ resultReturned: "The result is back in SpotPatch",
2835
+ resultSummary: (files) => `${String(files)} ${files === 1 ? "file" : "files"}`,
2836
+ activityAction: (kind, detail) => executionActivityAction(EXECUTION_ACTIVITY_EN, kind, detail),
2837
+ activityLane: executionActivityLane
2838
+ }),
2904
2839
  announcements: Object.freeze({
2905
2840
  adapterDisabled: "React inspection was disabled after an adapter failure.",
2906
2841
  selectionEnabled: "Element selection enabled.",
@@ -2953,6 +2888,11 @@ var UI_MESSAGES = Object.freeze({
2953
2888
  previewTitle: "\u5BA1\u9605\u4FEE\u6539\u8BF7\u6C42",
2954
2889
  previewSubtitle: "\u53D1\u9001\u7ED9 AI \u524D\uFF0C\u8BF7\u6838\u5BF9\u5B8C\u6574\u4E0A\u4E0B\u6587\u4E0E\u6BCF\u9879\u76EE\u6807\u8BF4\u660E\u3002"
2955
2890
  }),
2891
+ floatingSurface: Object.freeze({
2892
+ dragHandle: "\u62D6\u62FD\u4EE5\u79FB\u52A8 SpotPatch \u5DE5\u4F5C\u53F0",
2893
+ resetPosition: "\u91CD\u7F6E\u5DE5\u4F5C\u53F0\u4F4D\u7F6E",
2894
+ positionReset: "SpotPatch \u5DF2\u6062\u590D\u5230\u53F3\u4E0B\u89D2\u3002"
2895
+ }),
2956
2896
  context: Object.freeze({
2957
2897
  collecting: "\u6B63\u5728\u91C7\u96C6\u4E0A\u4E0B\u6587",
2958
2898
  ready: "\u4E0A\u4E0B\u6587\u5DF2\u5C31\u7EEA",
@@ -3039,6 +2979,27 @@ var UI_MESSAGES = Object.freeze({
3039
2979
  noOutput: "\u6CA1\u6709\u8F93\u51FA\u3002",
3040
2980
  status: (status) => STATUS_ZH[status]
3041
2981
  }),
2982
+ execution: Object.freeze({
2983
+ claude: "Claude",
2984
+ codex: "Codex",
2985
+ receivingTitle: "\u6B63\u5728\u63A5\u6536\u4E0A\u4E0B\u6587",
2986
+ dispatchingTitle: (identity) => `\u6B63\u5728\u4EA4\u63A5\u7ED9 ${identity}`,
2987
+ runningTitle: (target2) => `\u6B63\u5728\u4FEE\u6539 ${target2}`,
2988
+ expandedRunningTitle: (identity) => `${identity} \u6B63\u5728\u6267\u884C\u4FEE\u6539`,
2989
+ completedTitle: "\u4FEE\u6539\u5B8C\u6210",
2990
+ expandedCompletedTitle: "\u4FEE\u6539\u5DF2\u5B8C\u6210",
2991
+ failedTitle: "\u6267\u884C\u5931\u8D25",
2992
+ receivingStatus: "\u63A5\u6536\u4E2D",
2993
+ dispatchingStatus: "\u6D3E\u53D1\u4E2D",
2994
+ runningStatus: "\u6267\u884C\u4E2D",
2995
+ checkingStatus: "\u68C0\u67E5\u4E2D",
2996
+ completedStatus: "\u5B8C\u6210",
2997
+ failedStatus: "\u5931\u8D25",
2998
+ resultReturned: "\u7ED3\u679C\u5DF2\u56DE\u6D41 SpotPatch",
2999
+ resultSummary: (files) => `${String(files)} \u4E2A\u6587\u4EF6`,
3000
+ activityAction: (kind, detail) => executionActivityAction(EXECUTION_ACTIVITY_ZH, kind, detail),
3001
+ activityLane: executionActivityLane
3002
+ }),
3042
3003
  announcements: Object.freeze({
3043
3004
  adapterDisabled: "React \u9002\u914D\u5668\u5F02\u5E38\uFF0C\u672C\u6B21\u4F1A\u8BDD\u5DF2\u505C\u7528 React \u68C0\u67E5\u3002",
3044
3005
  selectionEnabled: "\u5143\u7D20\u9009\u62E9\u5DF2\u542F\u7528\u3002",
@@ -3112,13 +3073,500 @@ function createUiLocalizer(document, preference = "auto") {
3112
3073
  });
3113
3074
  }
3114
3075
 
3115
- // src/ui/runtime-view.ts
3116
- var DIALOG_MAX_WIDTH = 460;
3117
- var DIALOG_MAX_HEIGHT = 620;
3118
- var DIALOG_FALLBACK_HEIGHT = Object.freeze({
3119
- previewing: 560,
3120
- selected: DIALOG_MAX_HEIGHT
3076
+ // src/state/floating-surface-session.ts
3077
+ var STORAGE_PREFIX2 = "spotpatch:floating-surface:";
3078
+ var SNAPSHOT_VERSION2 = 1;
3079
+ var MAX_SNAPSHOT_CHARACTERS2 = 1024;
3080
+ function record2(value) {
3081
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
3082
+ }
3083
+ function finiteRatio(value) {
3084
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
3085
+ }
3086
+ function alignment(value) {
3087
+ return value === "start" || value === "end";
3088
+ }
3089
+ function parseSnapshot2(value) {
3090
+ const snapshot = record2(value);
3091
+ if (snapshot?.version !== SNAPSHOT_VERSION2 || !alignment(snapshot.horizontal) || !alignment(snapshot.vertical) || !finiteRatio(snapshot.xRatio) || !finiteRatio(snapshot.yRatio)) {
3092
+ return void 0;
3093
+ }
3094
+ return Object.freeze({
3095
+ horizontal: snapshot.horizontal,
3096
+ vertical: snapshot.vertical,
3097
+ xRatio: snapshot.xRatio,
3098
+ yRatio: snapshot.yRatio
3099
+ });
3100
+ }
3101
+ function createFloatingSurfaceSession(window, sessionId) {
3102
+ const key = `${STORAGE_PREFIX2}${sessionId}`;
3103
+ let storage;
3104
+ try {
3105
+ storage = window.sessionStorage;
3106
+ } catch {
3107
+ storage = void 0;
3108
+ }
3109
+ return Object.freeze({
3110
+ clear() {
3111
+ try {
3112
+ storage?.removeItem(key);
3113
+ } catch {
3114
+ }
3115
+ },
3116
+ load() {
3117
+ let serialized;
3118
+ try {
3119
+ serialized = storage?.getItem(key) ?? null;
3120
+ } catch {
3121
+ return void 0;
3122
+ }
3123
+ if (serialized === null || serialized.length > MAX_SNAPSHOT_CHARACTERS2) {
3124
+ return void 0;
3125
+ }
3126
+ try {
3127
+ return parseSnapshot2(JSON.parse(serialized));
3128
+ } catch {
3129
+ return void 0;
3130
+ }
3131
+ },
3132
+ save(position) {
3133
+ try {
3134
+ storage?.setItem(
3135
+ key,
3136
+ JSON.stringify({ version: SNAPSHOT_VERSION2, ...position })
3137
+ );
3138
+ } catch {
3139
+ }
3140
+ }
3141
+ });
3142
+ }
3143
+
3144
+ // src/ui/floating-surface-position.ts
3145
+ var DEFAULT_FLOATING_SURFACE_POSITION = Object.freeze({
3146
+ horizontal: "end",
3147
+ vertical: "end",
3148
+ xRatio: 1,
3149
+ yRatio: 1
3121
3150
  });
3151
+ function clamp(value, minimum, maximum) {
3152
+ return Math.min(Math.max(value, minimum), maximum);
3153
+ }
3154
+ function finiteNonNegative(value) {
3155
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
3156
+ }
3157
+ function boundedRatio(value) {
3158
+ return Number.isFinite(value) ? clamp(value, 0, 1) : 0.5;
3159
+ }
3160
+ function normalizedViewport(viewport) {
3161
+ return Object.freeze({
3162
+ height: finiteNonNegative(viewport.height),
3163
+ left: Number.isFinite(viewport.left) ? viewport.left : 0,
3164
+ top: Number.isFinite(viewport.top) ? viewport.top : 0,
3165
+ width: finiteNonNegative(viewport.width)
3166
+ });
3167
+ }
3168
+ function horizontalAnchor(rect, alignment2) {
3169
+ return alignment2 === "end" ? rect.left + rect.width : rect.left;
3170
+ }
3171
+ function verticalAnchor(rect, alignment2) {
3172
+ return alignment2 === "end" ? rect.top + rect.height : rect.top;
3173
+ }
3174
+ function normalizedCoordinate(coordinate, origin, length) {
3175
+ if (length <= 0) {
3176
+ return 0.5;
3177
+ }
3178
+ return boundedRatio((coordinate - origin) / length);
3179
+ }
3180
+ function positionFromAnchor(anchor, viewport, horizontal, vertical) {
3181
+ return Object.freeze({
3182
+ horizontal,
3183
+ vertical,
3184
+ xRatio: normalizedCoordinate(anchor.x, viewport.left, viewport.width),
3185
+ yRatio: normalizedCoordinate(anchor.y, viewport.top, viewport.height)
3186
+ });
3187
+ }
3188
+ function resolveFloatingSurfaceRect(position, size, viewport) {
3189
+ const width = finiteNonNegative(size.width);
3190
+ const height = finiteNonNegative(size.height);
3191
+ const normalized = normalizedViewport(viewport);
3192
+ const viewportWidth = normalized.width;
3193
+ const viewportHeight = normalized.height;
3194
+ const leftBoundary = normalized.left;
3195
+ const topBoundary = normalized.top;
3196
+ const rightBoundary = leftBoundary + viewportWidth;
3197
+ const bottomBoundary = topBoundary + viewportHeight;
3198
+ const anchorX = leftBoundary + boundedRatio(position.xRatio) * viewportWidth;
3199
+ const anchorY = topBoundary + boundedRatio(position.yRatio) * viewportHeight;
3200
+ const requestedLeft = position.horizontal === "end" ? anchorX - width : anchorX;
3201
+ const requestedTop = position.vertical === "end" ? anchorY - height : anchorY;
3202
+ const maximumLeft = Math.max(leftBoundary, rightBoundary - width);
3203
+ const maximumTop = Math.max(topBoundary, bottomBoundary - height);
3204
+ return Object.freeze({
3205
+ height,
3206
+ left: clamp(requestedLeft, leftBoundary, maximumLeft),
3207
+ top: clamp(requestedTop, topBoundary, maximumTop),
3208
+ width
3209
+ });
3210
+ }
3211
+ function snapFloatingSurfaceRect(rect, viewport, snapDistance) {
3212
+ const distance = finiteNonNegative(snapDistance);
3213
+ const width = finiteNonNegative(rect.width);
3214
+ const height = finiteNonNegative(rect.height);
3215
+ const normalized = normalizedViewport(viewport);
3216
+ const viewportRight = normalized.left + normalized.width;
3217
+ const viewportBottom = normalized.top + normalized.height;
3218
+ const maximumLeft = Math.max(normalized.left, viewportRight - width);
3219
+ const maximumTop = Math.max(normalized.top, viewportBottom - height);
3220
+ const requestedLeft = clamp(rect.left, normalized.left, maximumLeft);
3221
+ const requestedTop = clamp(rect.top, normalized.top, maximumTop);
3222
+ const leftDistance = Math.abs(requestedLeft - normalized.left);
3223
+ const rightDistance = Math.abs(viewportRight - (requestedLeft + width));
3224
+ const topDistance = Math.abs(requestedTop - normalized.top);
3225
+ const bottomDistance = Math.abs(viewportBottom - (requestedTop + height));
3226
+ const left = Math.min(leftDistance, rightDistance) <= distance ? leftDistance <= rightDistance ? normalized.left : viewportRight - width : requestedLeft;
3227
+ const top = Math.min(topDistance, bottomDistance) <= distance ? topDistance <= bottomDistance ? normalized.top : viewportBottom - height : requestedTop;
3228
+ return Object.freeze({ height, left, top, width });
3229
+ }
3230
+ function positionFromFloatingSurfaceRect(rect, viewport, previous) {
3231
+ const bounded = snapFloatingSurfaceRect(rect, viewport, 0);
3232
+ const centerX = bounded.left + bounded.width / 2;
3233
+ const centerY = bounded.top + bounded.height / 2;
3234
+ const viewportCenterX = viewport.left + finiteNonNegative(viewport.width) / 2;
3235
+ const viewportCenterY = viewport.top + finiteNonNegative(viewport.height) / 2;
3236
+ const horizontal = centerX === viewportCenterX ? previous.horizontal : centerX < viewportCenterX ? "start" : "end";
3237
+ const vertical = centerY === viewportCenterY ? previous.vertical : centerY < viewportCenterY ? "start" : "end";
3238
+ return positionFromAnchor(
3239
+ {
3240
+ x: horizontalAnchor(bounded, horizontal),
3241
+ y: verticalAnchor(bounded, vertical)
3242
+ },
3243
+ viewport,
3244
+ horizontal,
3245
+ vertical
3246
+ );
3247
+ }
3248
+
3249
+ // src/ui/floating-surface-controller.ts
3250
+ function finite(value, fallback = 0) {
3251
+ return Number.isFinite(value) ? value : fallback;
3252
+ }
3253
+ function distanceBetween(first, second) {
3254
+ return Math.hypot(first.x - second.x, first.y - second.y);
3255
+ }
3256
+ function isPrimaryPointer(event) {
3257
+ return event.button === 0 && event.isPrimary;
3258
+ }
3259
+ function surfaceSize(surface) {
3260
+ const rect = surface.getBoundingClientRect();
3261
+ return Object.freeze({
3262
+ height: Math.max(0, finite(rect.height)),
3263
+ width: Math.max(0, finite(rect.width))
3264
+ });
3265
+ }
3266
+ function requestFrame(window, callback) {
3267
+ if (typeof window.requestAnimationFrame === "function") {
3268
+ return window.requestAnimationFrame(callback);
3269
+ }
3270
+ return window.setTimeout(() => {
3271
+ callback(Date.now());
3272
+ }, 0);
3273
+ }
3274
+ function cancelFrame(window, frame) {
3275
+ if (typeof window.cancelAnimationFrame === "function") {
3276
+ window.cancelAnimationFrame(frame);
3277
+ return;
3278
+ }
3279
+ window.clearTimeout(frame);
3280
+ }
3281
+ function createFloatingSurfaceController(window, session, layout) {
3282
+ let position = session.load() ?? DEFAULT_FLOATING_SURFACE_POSITION;
3283
+ let drag;
3284
+ let frame;
3285
+ let suppressClickFor;
3286
+ let suppressClickTimer;
3287
+ let disposed = false;
3288
+ const surfaces = /* @__PURE__ */ new Set();
3289
+ const cleanup = /* @__PURE__ */ new Set();
3290
+ function viewport() {
3291
+ const visualViewport = window.visualViewport;
3292
+ const width = Math.max(0, finite(visualViewport?.width ?? window.innerWidth));
3293
+ const height = Math.max(0, finite(visualViewport?.height ?? window.innerHeight));
3294
+ const inset = width <= layout.compactViewportWidth ? layout.compactInset : layout.desktopInset;
3295
+ return Object.freeze({
3296
+ height: Math.max(0, height - inset * 2),
3297
+ left: finite(visualViewport?.offsetLeft ?? 0) + inset,
3298
+ top: finite(visualViewport?.offsetTop ?? 0) + inset,
3299
+ width: Math.max(0, width - inset * 2)
3300
+ });
3301
+ }
3302
+ function isCompact() {
3303
+ return viewport().width + layout.compactInset * 2 <= layout.compactViewportWidth;
3304
+ }
3305
+ function apply(surface) {
3306
+ if (surface.hidden) {
3307
+ return void 0;
3308
+ }
3309
+ if (surface.dataset.motionMorphing === "true" && surface.dataset.floatingPositioned === "true") {
3310
+ return void 0;
3311
+ }
3312
+ const size = surfaceSize(surface);
3313
+ if (size.width === 0 || size.height === 0) {
3314
+ return void 0;
3315
+ }
3316
+ const rect = resolveFloatingSurfaceRect(position, size, viewport());
3317
+ surface.style.left = `${String(rect.left)}px`;
3318
+ surface.style.top = `${String(rect.top)}px`;
3319
+ surface.style.right = "auto";
3320
+ surface.style.bottom = "auto";
3321
+ surface.dataset.floatingPositioned = "true";
3322
+ return rect;
3323
+ }
3324
+ function reconcile() {
3325
+ if (disposed) {
3326
+ return;
3327
+ }
3328
+ if (frame !== void 0) {
3329
+ cancelFrame(window, frame);
3330
+ frame = void 0;
3331
+ }
3332
+ for (const surface of surfaces) {
3333
+ apply(surface);
3334
+ }
3335
+ }
3336
+ function requestReconcile() {
3337
+ if (disposed || frame !== void 0) {
3338
+ return;
3339
+ }
3340
+ frame = requestFrame(window, () => {
3341
+ frame = void 0;
3342
+ reconcile();
3343
+ });
3344
+ }
3345
+ function commit(nextPosition) {
3346
+ position = nextPosition;
3347
+ session.save(position);
3348
+ }
3349
+ function finishDrag(commitPosition) {
3350
+ const current = drag;
3351
+ if (current === void 0) {
3352
+ return;
3353
+ }
3354
+ drag = void 0;
3355
+ current.handle.dataset.dragging = "false";
3356
+ current.surface.dataset.dragging = "false";
3357
+ try {
3358
+ if (current.handle.hasPointerCapture(current.pointerId)) {
3359
+ current.handle.releasePointerCapture(current.pointerId);
3360
+ }
3361
+ } catch {
3362
+ }
3363
+ if (commitPosition) {
3364
+ const rect = apply(current.surface);
3365
+ if (rect !== void 0) {
3366
+ const snapped = snapFloatingSurfaceRect(
3367
+ rect,
3368
+ viewport(),
3369
+ layout.edgeSnapDistance
3370
+ );
3371
+ commit(positionFromFloatingSurfaceRect(snapped, viewport(), position));
3372
+ apply(current.surface);
3373
+ }
3374
+ } else {
3375
+ position = session.load() ?? DEFAULT_FLOATING_SURFACE_POSITION;
3376
+ reconcile();
3377
+ }
3378
+ }
3379
+ function updateDrag(event) {
3380
+ const current = drag;
3381
+ if (current === void 0) {
3382
+ return;
3383
+ }
3384
+ if (event.pointerId !== current.pointerId) {
3385
+ return;
3386
+ }
3387
+ const pointer = Object.freeze({ x: event.clientX, y: event.clientY });
3388
+ const start = Object.freeze({ x: current.startX, y: current.startY });
3389
+ if (current.handle.dataset.dragging !== "true" && distanceBetween(pointer, start) < layout.dragActivationDistance) {
3390
+ return;
3391
+ }
3392
+ if (current.handle.dataset.dragging !== "true") {
3393
+ current.onDragStart?.();
3394
+ }
3395
+ current.handle.dataset.dragging = "true";
3396
+ current.surface.dataset.dragging = "true";
3397
+ position = positionFromAnchor(
3398
+ {
3399
+ x: pointer.x + current.anchorOffsetX,
3400
+ y: pointer.y + current.anchorOffsetY
3401
+ },
3402
+ viewport(),
3403
+ position.horizontal,
3404
+ position.vertical
3405
+ );
3406
+ requestReconcile();
3407
+ event.preventDefault();
3408
+ }
3409
+ function addListener(target2, type, listener, options) {
3410
+ target2.addEventListener(type, listener, options);
3411
+ cleanup.add(() => {
3412
+ target2.removeEventListener(type, listener, options);
3413
+ });
3414
+ }
3415
+ function attachDraggable(handle, surface, options = {}) {
3416
+ const onPointerDown = (event) => {
3417
+ if (disposed || drag !== void 0 || !isPrimaryPointer(event) || options.canStartDrag?.(event) === false) {
3418
+ return;
3419
+ }
3420
+ const rect = apply(surface) ?? surface.getBoundingClientRect();
3421
+ const anchorX = position.horizontal === "end" ? rect.left + rect.width : rect.left;
3422
+ const anchorY = position.vertical === "end" ? rect.top + rect.height : rect.top;
3423
+ drag = Object.freeze({
3424
+ anchorOffsetX: anchorX - event.clientX,
3425
+ anchorOffsetY: anchorY - event.clientY,
3426
+ handle,
3427
+ ...options.onDragStart === void 0 ? {} : { onDragStart: options.onDragStart },
3428
+ pointerId: event.pointerId,
3429
+ startX: event.clientX,
3430
+ startY: event.clientY,
3431
+ suppressClickOnDrag: options.suppressClickOnDrag ?? false,
3432
+ surface
3433
+ });
3434
+ try {
3435
+ handle.setPointerCapture(event.pointerId);
3436
+ } catch {
3437
+ }
3438
+ };
3439
+ const onLostPointerCapture = (event) => {
3440
+ if (drag?.pointerId === event.pointerId) {
3441
+ finishDrag(handle.dataset.dragging === "true");
3442
+ }
3443
+ };
3444
+ const onClickCapture = (event) => {
3445
+ if (suppressClickFor !== handle) {
3446
+ return;
3447
+ }
3448
+ suppressClickFor = void 0;
3449
+ if (suppressClickTimer !== void 0) {
3450
+ window.clearTimeout(suppressClickTimer);
3451
+ suppressClickTimer = void 0;
3452
+ }
3453
+ event.preventDefault();
3454
+ event.stopImmediatePropagation();
3455
+ };
3456
+ addListener(handle, "pointerdown", (event) => {
3457
+ onPointerDown(event);
3458
+ });
3459
+ addListener(handle, "lostpointercapture", (event) => {
3460
+ onLostPointerCapture(event);
3461
+ });
3462
+ addListener(
3463
+ handle,
3464
+ "click",
3465
+ (event) => {
3466
+ onClickCapture(event);
3467
+ },
3468
+ true
3469
+ );
3470
+ }
3471
+ function reset() {
3472
+ finishDrag(false);
3473
+ position = DEFAULT_FLOATING_SURFACE_POSITION;
3474
+ session.clear();
3475
+ reconcile();
3476
+ }
3477
+ function cancelOnWindowBlur() {
3478
+ finishDrag(false);
3479
+ }
3480
+ function cancelOnEscape(event) {
3481
+ if (event.key !== "Escape" || drag === void 0) {
3482
+ return;
3483
+ }
3484
+ finishDrag(false);
3485
+ event.preventDefault();
3486
+ event.stopImmediatePropagation();
3487
+ }
3488
+ function endPointer(event) {
3489
+ if (drag?.pointerId !== event.pointerId) {
3490
+ return;
3491
+ }
3492
+ const current = drag;
3493
+ const moved = current.handle.dataset.dragging === "true";
3494
+ finishDrag(moved);
3495
+ if (moved && current.suppressClickOnDrag) {
3496
+ suppressClickFor = current.handle;
3497
+ suppressClickTimer = window.setTimeout(() => {
3498
+ suppressClickFor = void 0;
3499
+ suppressClickTimer = void 0;
3500
+ }, 0);
3501
+ }
3502
+ }
3503
+ function cancelPointer(event) {
3504
+ if (drag?.pointerId === event.pointerId) {
3505
+ finishDrag(false);
3506
+ }
3507
+ }
3508
+ addListener(window, "pointermove", (event) => {
3509
+ updateDrag(event);
3510
+ });
3511
+ addListener(window, "pointerup", (event) => {
3512
+ endPointer(event);
3513
+ });
3514
+ addListener(window, "pointercancel", (event) => {
3515
+ cancelPointer(event);
3516
+ });
3517
+ addListener(window, "resize", requestReconcile);
3518
+ addListener(window, "blur", cancelOnWindowBlur);
3519
+ addListener(
3520
+ window,
3521
+ "keydown",
3522
+ (event) => {
3523
+ cancelOnEscape(event);
3524
+ },
3525
+ true
3526
+ );
3527
+ window.visualViewport?.addEventListener("resize", requestReconcile);
3528
+ window.visualViewport?.addEventListener("scroll", requestReconcile);
3529
+ cleanup.add(
3530
+ () => window.visualViewport?.removeEventListener("resize", requestReconcile)
3531
+ );
3532
+ cleanup.add(
3533
+ () => window.visualViewport?.removeEventListener("scroll", requestReconcile)
3534
+ );
3535
+ return Object.freeze({
3536
+ attachDraggable,
3537
+ dispose() {
3538
+ if (disposed) {
3539
+ return;
3540
+ }
3541
+ disposed = true;
3542
+ finishDrag(false);
3543
+ if (frame !== void 0) {
3544
+ cancelFrame(window, frame);
3545
+ frame = void 0;
3546
+ }
3547
+ if (suppressClickTimer !== void 0) {
3548
+ window.clearTimeout(suppressClickTimer);
3549
+ suppressClickTimer = void 0;
3550
+ }
3551
+ suppressClickFor = void 0;
3552
+ for (const disposeListener of cleanup) {
3553
+ disposeListener();
3554
+ }
3555
+ cleanup.clear();
3556
+ surfaces.clear();
3557
+ },
3558
+ isCompact,
3559
+ reconcile,
3560
+ registerSurface(surface) {
3561
+ surfaces.add(surface);
3562
+ },
3563
+ requestReconcile,
3564
+ reset
3565
+ });
3566
+ }
3567
+
3568
+ // src/ui/runtime-view.ts
3569
+ var SUCCESS_SETTLE_MS = 1500;
3122
3570
  function resolveStyleNonce(document) {
3123
3571
  const nonces = new Set(
3124
3572
  [...document.querySelectorAll("script[nonce]")].map((script) => script.nonce?.trim() ?? "").filter(Boolean)
@@ -3149,53 +3597,20 @@ function createStyles(document) {
3149
3597
  --spotpatch-success: #34d399;
3150
3598
  --spotpatch-warning: #f59e0b;
3151
3599
  --spotpatch-text-on-accent: #0b0b12;
3152
- --spotpatch-radius-panel: 16px;
3153
3600
  --spotpatch-radius-card: 10px;
3154
- --spotpatch-shadow-panel: 0 30px 60px -20px rgb(0 0 0 / 70%);
3155
- color-scheme: dark;
3156
3601
  color: var(--spotpatch-text);
3157
3602
  font-family: Inter, "SF Pro Display", "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
3158
3603
  font-size: 14px;
3159
3604
  line-height: 1.5;
3160
- text-rendering: optimizeLegibility;
3161
- -webkit-font-smoothing: antialiased;
3162
3605
  }
3163
3606
  [hidden] { display: none !important; }
3164
3607
  button, textarea, select { font: inherit; }
3165
- button { -webkit-tap-highlight-color: transparent; }
3166
- .spotpatch-trigger {
3608
+ .spotpatch-floating-surface {
3167
3609
  position: fixed;
3168
- right: 24px;
3169
- bottom: 24px;
3610
+ right: ${String(FLOATING_SURFACE_LAYOUT.desktopInset)}px;
3611
+ bottom: ${String(FLOATING_SURFACE_LAYOUT.desktopInset)}px;
3170
3612
  z-index: ${String(UI_Z_INDEX.controls)};
3171
- display: inline-flex;
3172
- align-items: center;
3173
- gap: 10px;
3174
- min-height: 44px;
3175
- border: 1px solid rgb(255 255 255 / 12%);
3176
- border-radius: 999px;
3177
- padding: 10px 17px;
3178
- color: #f8fafc;
3179
- background: #171b23;
3180
- box-shadow: 0 12px 34px rgb(0 0 0 / 25%), inset 0 1px rgb(255 255 255 / 5%);
3181
- cursor: pointer;
3182
- font-size: 14px;
3183
- font-weight: 650;
3184
- transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
3185
- }
3186
- .spotpatch-trigger::before {
3187
- width: 9px;
3188
- height: 9px;
3189
- border-radius: 999px;
3190
- background: var(--spotpatch-accent);
3191
- box-shadow: 0 0 0 4px rgb(99 102 241 / 10%);
3192
- content: "";
3193
- }
3194
- .spotpatch-trigger:hover { transform: translateY(-1px); border-color: rgb(129 140 248 / 52%); }
3195
- .spotpatch-trigger[aria-pressed="true"] {
3196
- border-color: rgb(124 58 237 / 55%);
3197
- background: #24203b;
3198
- box-shadow: 0 14px 42px rgb(76 29 149 / 24%), inset 0 1px rgb(255 255 255 / 10%);
3613
+ isolation: isolate;
3199
3614
  }
3200
3615
  .spotpatch-highlight {
3201
3616
  position: fixed;
@@ -3264,82 +3679,28 @@ function createStyles(document) {
3264
3679
  background: #5546dc;
3265
3680
  }
3266
3681
  .spotpatch-dialog {
3267
- --spotpatch-anchor-x: 50%;
3268
- --spotpatch-anchor-y: 50%;
3269
- position: fixed;
3270
- top: 16px;
3271
- left: 16px;
3272
- z-index: ${String(UI_Z_INDEX.controls)};
3682
+ position: relative;
3273
3683
  box-sizing: border-box;
3274
- width: min(${String(DIALOG_MAX_WIDTH)}px, calc(100vw - 32px));
3275
- color: #edf0f7;
3684
+ width: min(${String(FLOATING_SURFACE_LAYOUT.workbenchMaxWidth)}px, calc(100vw - ${String(FLOATING_SURFACE_LAYOUT.desktopInset * 2)}px));
3276
3685
  outline: none;
3277
- filter: drop-shadow(var(--spotpatch-shadow-panel));
3278
3686
  }
3279
- .spotpatch-anchor {
3280
- position: absolute;
3281
- z-index: 0;
3282
- width: 14px;
3283
- height: 14px;
3284
- border: 1px solid rgb(255 255 255 / 12%);
3285
- background: var(--spotpatch-bg);
3286
- transform: rotate(45deg);
3287
- }
3288
- .spotpatch-dialog[data-placement="above"] .spotpatch-anchor {
3289
- bottom: -6px;
3290
- left: calc(var(--spotpatch-anchor-x) - 7px);
3291
- border-top: 0;
3292
- border-left: 0;
3293
- }
3294
- .spotpatch-dialog[data-placement="below"] .spotpatch-anchor {
3295
- top: -6px;
3296
- left: calc(var(--spotpatch-anchor-x) - 7px);
3297
- border-right: 0;
3298
- border-bottom: 0;
3299
- }
3300
- .spotpatch-dialog[data-placement="left"] .spotpatch-anchor {
3301
- top: calc(var(--spotpatch-anchor-y) - 7px);
3302
- right: -6px;
3303
- border-bottom: 0;
3304
- border-left: 0;
3305
- }
3306
- .spotpatch-dialog[data-placement="right"] .spotpatch-anchor {
3307
- top: calc(var(--spotpatch-anchor-y) - 7px);
3308
- left: -6px;
3309
- border-top: 0;
3310
- border-right: 0;
3311
- }
3312
- .spotpatch-dialog[data-placement="center"] .spotpatch-anchor,
3313
- .spotpatch-dialog[data-placement="viewport"] .spotpatch-anchor { display: none; }
3314
3687
  .spotpatch-shell {
3315
3688
  position: relative;
3316
3689
  z-index: 1;
3317
3690
  display: flex;
3318
3691
  box-sizing: border-box;
3319
- max-height: min(${String(DIALOG_MAX_HEIGHT)}px, calc(100vh - 32px));
3692
+ max-height: min(${String(FLOATING_SURFACE_LAYOUT.workbenchMaxHeight)}px, calc(100vh - ${String(FLOATING_SURFACE_LAYOUT.desktopInset * 2)}px));
3320
3693
  overflow: hidden;
3321
3694
  flex-direction: column;
3322
- border: 1px solid var(--spotpatch-border);
3323
- border-radius: var(--spotpatch-radius-panel);
3324
- background: var(--spotpatch-bg);
3325
- box-shadow: inset 0 1px rgb(255 255 255 / 5%), 0 0 60px -32px rgb(139 123 255 / 20%);
3326
- }
3327
- .spotpatch-shell::before {
3328
- position: absolute;
3329
- z-index: 2;
3330
- top: 0;
3331
- right: 20%;
3332
- left: 20%;
3333
- height: 1px;
3334
- background: linear-gradient(90deg, transparent, var(--spotpatch-accent), var(--spotpatch-accent-cyan), transparent);
3335
- content: "";
3336
- opacity: .65;
3337
- pointer-events: none;
3338
3695
  }
3339
3696
  .spotpatch-header {
3340
3697
  position: relative;
3341
3698
  padding: 0 18px 14px;
3342
3699
  }
3700
+ .spotpatch-header[data-spotpatch-drag-handle] { cursor: grab; touch-action: none; user-select: none; }
3701
+ .spotpatch-header[data-dragging="true"] { cursor: grabbing; }
3702
+ .spotpatch-header button,
3703
+ .spotpatch-header a { cursor: pointer; user-select: auto; }
3343
3704
  .spotpatch-brand-row {
3344
3705
  display: flex;
3345
3706
  align-items: center;
@@ -3363,18 +3724,6 @@ function createStyles(document) {
3363
3724
  filter: drop-shadow(0 6px 12px rgb(76 29 149 / 22%));
3364
3725
  }
3365
3726
  .spotpatch-brand-copy { display: grid; min-width: 0; gap: 1px; }
3366
- .spotpatch-brand-name {
3367
- color: #fff;
3368
- font-size: 13.5px;
3369
- font-weight: 680;
3370
- letter-spacing: -.01em;
3371
- }
3372
- .spotpatch-brand-context {
3373
- color: #8e98aa;
3374
- font-size: 10.5px;
3375
- font-weight: 560;
3376
- letter-spacing: .02em;
3377
- }
3378
3727
  .spotpatch-header-controls {
3379
3728
  display: inline-flex;
3380
3729
  align-items: center;
@@ -3383,6 +3732,7 @@ function createStyles(document) {
3383
3732
  }
3384
3733
  .spotpatch-repository,
3385
3734
  .spotpatch-locale,
3735
+ .spotpatch-reset-position,
3386
3736
  .spotpatch-close {
3387
3737
  height: 27px;
3388
3738
  border: 1px solid rgb(255 255 255 / 11%);
@@ -3406,6 +3756,16 @@ function createStyles(document) {
3406
3756
  font-size: 12px;
3407
3757
  font-weight: 650;
3408
3758
  }
3759
+ .spotpatch-reset-position {
3760
+ display: inline-grid;
3761
+ width: 27px;
3762
+ padding: 0;
3763
+ place-items: center;
3764
+ color: #c5cad5;
3765
+ cursor: pointer;
3766
+ font-size: 15px;
3767
+ line-height: 1;
3768
+ }
3409
3769
  .spotpatch-close {
3410
3770
  display: inline-grid;
3411
3771
  width: 27px;
@@ -3419,21 +3779,8 @@ function createStyles(document) {
3419
3779
  }
3420
3780
  .spotpatch-close:hover,
3421
3781
  .spotpatch-locale:hover,
3782
+ .spotpatch-reset-position:hover,
3422
3783
  .spotpatch-repository:hover { border-color: rgb(129 112 247 / 45%); color: #fff; background: rgb(109 93 246 / 10%); }
3423
- .spotpatch-title {
3424
- margin: 0;
3425
- color: #fff;
3426
- font-size: 18px;
3427
- font-weight: 680;
3428
- letter-spacing: -.015em;
3429
- }
3430
- .spotpatch-subtitle {
3431
- max-width: 390px;
3432
- margin: 4px 0 0;
3433
- color: var(--spotpatch-text-secondary);
3434
- font-size: 12.5px;
3435
- line-height: 1.5;
3436
- }
3437
3784
  .spotpatch-target-row {
3438
3785
  display: flex;
3439
3786
  align-items: center;
@@ -3797,6 +4144,7 @@ function createStyles(document) {
3797
4144
  .spotpatch-target-open:focus-visible,
3798
4145
  .spotpatch-target-remove:focus-visible,
3799
4146
  .spotpatch-close:focus-visible,
4147
+ .spotpatch-reset-position:focus-visible,
3800
4148
  .spotpatch-trigger:focus-visible,
3801
4149
  .spotpatch-target-select:focus-visible,
3802
4150
  .spotpatch-target-editor textarea:focus-visible,
@@ -3821,8 +4169,8 @@ function createStyles(document) {
3821
4169
  .spotpatch-diagnostics > summary::before { transition: none; }
3822
4170
  }
3823
4171
  @media (max-width: 520px) {
3824
- .spotpatch-dialog { top: 8px; left: 8px; width: calc(100vw - 16px); }
3825
- .spotpatch-shell { max-height: calc(100vh - 16px); border-radius: 14px; }
4172
+ .spotpatch-dialog { width: calc(100vw - 16px); }
4173
+ .spotpatch-shell { max-height: calc(100dvh - 16px); }
3826
4174
  .spotpatch-header, .spotpatch-body { padding-left: 14px; padding-right: 14px; }
3827
4175
  .spotpatch-brand-row { margin-right: -14px; margin-left: -14px; padding-right: 14px; padding-left: 14px; }
3828
4176
  .spotpatch-actions { padding-right: 14px; padding-left: 14px; }
@@ -3838,6 +4186,38 @@ function summaryLine(summary, prefix) {
3838
4186
  const line = summary.split("\n").find((candidate) => candidate.startsWith(`${prefix}: `));
3839
4187
  return line?.slice(prefix.length + 2).trim();
3840
4188
  }
4189
+ function startsOnInteractiveControl(event) {
4190
+ const target2 = event.target;
4191
+ return target2 instanceof Element && target2.closest(
4192
+ "a, button, input, select, textarea, summary, [contenteditable='true']"
4193
+ ) !== null;
4194
+ }
4195
+ function createFallbackExecutionIsland(document) {
4196
+ const root = createButton(document, "", "spotpatch-execution-island");
4197
+ root.hidden = true;
4198
+ root.setAttribute("aria-hidden", "true");
4199
+ const logo = createBrandMark(
4200
+ document,
4201
+ void 0,
4202
+ "spotpatch-fallback-execution-island"
4203
+ );
4204
+ logo.classList.add("spotpatch-execution-logo");
4205
+ const copy = createMarkedElement(document, "span");
4206
+ root.append(logo, copy);
4207
+ return Object.freeze({
4208
+ canExpand: () => false,
4209
+ isExpanded: () => false,
4210
+ root,
4211
+ setExpanded: () => void 0,
4212
+ render(projection) {
4213
+ const text2 = [projection.headline, projection.action, projection.meta].filter((value) => value.length > 0).join(" \xB7 ");
4214
+ copy.textContent = text2;
4215
+ root.setAttribute("aria-label", text2);
4216
+ },
4217
+ dispose() {
4218
+ }
4219
+ });
4220
+ }
3841
4221
  function createUnavailableDataFlowPanel(document, changesRoot, diagnosticsRoot) {
3842
4222
  changesRoot.append(diagnosticsRoot);
3843
4223
  return Object.freeze({
@@ -3849,12 +4229,38 @@ function createUnavailableDataFlowPanel(document, changesRoot, diagnosticsRoot)
3849
4229
  resetView: () => void 0
3850
4230
  });
3851
4231
  }
3852
- function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", dataFlowEnabled = false, externalAgentEnabled = false, framework = "vite", sessionId = "") {
4232
+ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", dataFlowEnabled = false, externalAgentEnabled = false, framework = "vite", sessionId = "", contextualAskEnabled = false) {
3853
4233
  const localizer = createUiLocalizer(document, localePreference);
4234
+ const associatedWindow = document.defaultView;
4235
+ if (associatedWindow === null) {
4236
+ throw new Error("SpotPatch requires a document with an associated window.");
4237
+ }
4238
+ const runtimeWindow = associatedWindow;
4239
+ const floatingSurface = createFloatingSurfaceController(
4240
+ runtimeWindow,
4241
+ createFloatingSurfaceSession(runtimeWindow, sessionId),
4242
+ FLOATING_SURFACE_LAYOUT
4243
+ );
3854
4244
  let messages = localizer.messages();
4245
+ let currentStatus = "idle";
4246
+ let contextualAskMode = "change";
4247
+ let contextualAskTitle = "";
4248
+ let contextualAskSubtitle = "";
4249
+ let plannerVisible = false;
4250
+ let executionSuppressed = false;
4251
+ let executionProjection;
4252
+ let pendingAgentMotion;
4253
+ let successSettleKey;
4254
+ let settledSuccessKey;
4255
+ let successSettleTimer;
4256
+ let observedExecutionStartedAt;
3855
4257
  const host = document.createElement("spotpatch-root");
3856
4258
  host.setAttribute(UI_MARKER_ATTRIBUTE, "");
3857
4259
  const shadowRoot = host.attachShadow({ mode: "open" });
4260
+ const floatingSurfaceRoot = createMarkedElement(document, "div");
4261
+ floatingSurfaceRoot.className = "spotpatch-floating-surface";
4262
+ floatingSurfaceRoot.dataset.scene = "pill";
4263
+ floatingSurfaceRoot.dataset.tone = "neutral";
3858
4264
  const triggerButton = createButton(
3859
4265
  document,
3860
4266
  messages.trigger.select,
@@ -3862,6 +4268,10 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3862
4268
  );
3863
4269
  triggerButton.title = messages.trigger.title(shortcut);
3864
4270
  triggerButton.setAttribute("aria-pressed", "false");
4271
+ const motionExtension = getFloatingSurfaceMotionExtension();
4272
+ const motionExecutionIsland = motionExtension?.createExecutionIsland(document);
4273
+ const executionIslandView = motionExecutionIsland ?? createFallbackExecutionIsland(document);
4274
+ const executionIsland = executionIslandView.root;
3865
4275
  const highlight = createMarkedElement(document, "div");
3866
4276
  highlight.className = "spotpatch-highlight";
3867
4277
  highlight.hidden = true;
@@ -3875,16 +4285,13 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3875
4285
  dialog.className = "spotpatch-dialog";
3876
4286
  dialog.hidden = true;
3877
4287
  dialog.tabIndex = -1;
3878
- dialog.dataset.placement = "viewport";
3879
4288
  dialog.setAttribute("role", "dialog");
3880
4289
  dialog.setAttribute("aria-labelledby", "spotpatch-selection-title");
3881
- const anchor = createMarkedElement(document, "span");
3882
- anchor.className = "spotpatch-anchor";
3883
- anchor.setAttribute("aria-hidden", "true");
3884
4290
  const shell = createMarkedElement(document, "div");
3885
4291
  shell.className = "spotpatch-shell";
3886
4292
  const header = createMarkedElement(document, "header");
3887
4293
  header.className = "spotpatch-header";
4294
+ header.setAttribute("data-spotpatch-drag-handle", "");
3888
4295
  const brandRow = createMarkedElement(document, "div");
3889
4296
  brandRow.className = "spotpatch-brand-row";
3890
4297
  const brand = createMarkedElement(document, "div");
@@ -3905,8 +4312,9 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3905
4312
  repositoryLink.target = "_blank";
3906
4313
  repositoryLink.rel = "noopener noreferrer";
3907
4314
  const localeButton = createButton(document, "", "spotpatch-locale");
4315
+ const resetPositionButton = createButton(document, "\u2316", "spotpatch-reset-position");
3908
4316
  const closeButton = createButton(document, "\xD7", "spotpatch-close");
3909
- headerControls.append(repositoryLink, localeButton, closeButton);
4317
+ headerControls.append(repositoryLink, localeButton, resetPositionButton, closeButton);
3910
4318
  brandRow.append(brand, headerControls);
3911
4319
  const title = createMarkedElement(document, "h2");
3912
4320
  title.id = "spotpatch-selection-title";
@@ -3966,13 +4374,20 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3966
4374
  diagnostics.append(diagnosticsLabel, summary);
3967
4375
  const agentPanel = createAgentPanel(document, ai, localizer);
3968
4376
  const changesPanel = createMarkedElement(document, "div");
4377
+ function requestFloatingSurfaceLayout() {
4378
+ if (plannerVisible) {
4379
+ floatingSurface.requestReconcile();
4380
+ }
4381
+ }
3969
4382
  const externalHandoffPanel = externalAgentEnabled ? getExternalHandoffExtension()?.createPanel(
3970
4383
  document,
3971
4384
  framework,
3972
4385
  localizer.locale,
3973
4386
  sessionId,
3974
4387
  localizer.subscribe,
3975
- placeDialog
4388
+ requestFloatingSurfaceLayout,
4389
+ renderExternalDispatch,
4390
+ renderExternalControl
3976
4391
  ) : void 0;
3977
4392
  changesPanel.append(
3978
4393
  targetsPanel,
@@ -3985,7 +4400,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3985
4400
  localizer.locale,
3986
4401
  changesPanel,
3987
4402
  diagnostics,
3988
- placeDialog
4403
+ requestFloatingSurfaceLayout
3989
4404
  ) ?? createUnavailableDataFlowPanel(document, changesPanel, diagnostics);
3990
4405
  selectionPanel.append(dataFlowPanel.root);
3991
4406
  const previewPanel = createMarkedElement(document, "div");
@@ -4040,15 +4455,40 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4040
4455
  backButton
4041
4456
  );
4042
4457
  actions.append(editorFeedback, secondaryActions, primaryActions);
4458
+ const contextualAskPanel = contextualAskEnabled ? getContextualAskExtension()?.createPanel({
4459
+ document,
4460
+ locale: localizer.locale,
4461
+ subscribeLocale: localizer.subscribe,
4462
+ changeRoot: dataFlowPanel.root,
4463
+ changeActions: actions,
4464
+ announce,
4465
+ onModeChange(mode, askTitle, askSubtitle) {
4466
+ contextualAskMode = mode;
4467
+ contextualAskTitle = askTitle;
4468
+ contextualAskSubtitle = askSubtitle;
4469
+ if (host.isConnected) renderPanelStatus(currentStatus);
4470
+ },
4471
+ onExecutionChange(projection) {
4472
+ executionProjection = projection;
4473
+ executionSuppressed = false;
4474
+ renderFloatingSurfaceMotion();
4475
+ },
4476
+ onViewChange: requestFloatingSurfaceLayout
4477
+ }) : void 0;
4478
+ if (contextualAskPanel !== void 0) {
4479
+ selectionPanel.prepend(contextualAskPanel.root);
4480
+ }
4043
4481
  shell.append(header, body, actions);
4044
- dialog.append(anchor, shell);
4482
+ dialog.append(shell);
4045
4483
  const liveRegion = createMarkedElement(document, "div");
4046
4484
  liveRegion.className = "spotpatch-live";
4047
4485
  liveRegion.setAttribute("aria-live", "polite");
4048
4486
  liveRegion.setAttribute("aria-atomic", "true");
4049
4487
  const styles2 = [
4050
4488
  createStyles(document),
4489
+ ...motionExtension === void 0 ? [] : [motionExtension.createStyles(document)],
4051
4490
  dataFlowPanel.styles,
4491
+ ...contextualAskPanel === void 0 ? [] : [contextualAskPanel.styles],
4052
4492
  ...externalHandoffPanel === void 0 ? [] : [externalHandoffPanel.styles]
4053
4493
  ];
4054
4494
  const styleNonce = resolveStyleNonce(document);
@@ -4061,14 +4501,38 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4061
4501
  ...styles2,
4062
4502
  selectionHighlights,
4063
4503
  highlight,
4064
- dialog,
4065
- triggerButton,
4504
+ floatingSurfaceRoot,
4066
4505
  liveRegion
4067
4506
  );
4507
+ floatingSurfaceRoot.append(triggerButton, dialog, executionIsland);
4068
4508
  document.documentElement.append(host);
4069
- let currentRect;
4070
- let activeSelectionRect;
4071
- let currentStatus = "idle";
4509
+ floatingSurface.registerSurface(floatingSurfaceRoot);
4510
+ const motionController = motionExtension === void 0 || motionExecutionIsland === void 0 ? void 0 : motionExtension.createController(
4511
+ document,
4512
+ Object.freeze({
4513
+ execution: motionExecutionIsland.elements,
4514
+ pill: triggerButton,
4515
+ planner: dialog,
4516
+ surface: floatingSurfaceRoot
4517
+ }),
4518
+ floatingSurface.reconcile
4519
+ );
4520
+ const cancelMotionOnDrag = () => {
4521
+ motionController?.cancel();
4522
+ };
4523
+ floatingSurface.attachDraggable(triggerButton, floatingSurfaceRoot, {
4524
+ onDragStart: cancelMotionOnDrag,
4525
+ suppressClickOnDrag: true
4526
+ });
4527
+ floatingSurface.attachDraggable(header, floatingSurfaceRoot, {
4528
+ canStartDrag: (event) => !floatingSurface.isCompact() && !startsOnInteractiveControl(event),
4529
+ onDragStart: cancelMotionOnDrag
4530
+ });
4531
+ floatingSurface.attachDraggable(executionIsland, floatingSurfaceRoot, {
4532
+ onDragStart: cancelMotionOnDrag,
4533
+ suppressClickOnDrag: true
4534
+ });
4535
+ floatingSurface.reconcile();
4072
4536
  let currentCanOpenEditor = false;
4073
4537
  let currentCanPreview = false;
4074
4538
  let currentSummaryText = "";
@@ -4083,12 +4547,274 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4083
4547
  page: Object.freeze({ status: dataFlowEnabled ? "idle" : "disabled" }),
4084
4548
  observationCount: 0
4085
4549
  });
4550
+ function currentTargetSummary() {
4551
+ const target2 = currentTargets.find((candidate) => candidate.active);
4552
+ return target2 === void 0 ? Object.freeze({
4553
+ label: messages.context.selectedElement,
4554
+ source: ""
4555
+ }) : Object.freeze({ label: target2.label, source: target2.source });
4556
+ }
4557
+ function currentTargetContext() {
4558
+ const target2 = currentTargetSummary();
4559
+ return target2.source.length === 0 ? target2.label : `${target2.label} \xB7 ${target2.source}`;
4560
+ }
4561
+ function beginObservedExecution() {
4562
+ observedExecutionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
4563
+ return observedExecutionStartedAt;
4564
+ }
4565
+ function observedExecutionStart() {
4566
+ return observedExecutionStartedAt ?? beginObservedExecution();
4567
+ }
4568
+ function clearSuccessSettle(resetKeys) {
4569
+ if (successSettleTimer !== void 0) {
4570
+ runtimeWindow.clearTimeout(successSettleTimer);
4571
+ successSettleTimer = void 0;
4572
+ }
4573
+ if (resetKeys) {
4574
+ successSettleKey = void 0;
4575
+ settledSuccessKey = void 0;
4576
+ }
4577
+ }
4578
+ function showSuccessUntilSettled(key) {
4579
+ if (settledSuccessKey === key) return false;
4580
+ if (successSettleKey === key) return true;
4581
+ clearSuccessSettle(false);
4582
+ successSettleKey = key;
4583
+ successSettleTimer = runtimeWindow.setTimeout(() => {
4584
+ successSettleTimer = void 0;
4585
+ if (!host.isConnected) return;
4586
+ settledSuccessKey = key;
4587
+ executionProjection = void 0;
4588
+ observedExecutionStartedAt = void 0;
4589
+ executionSuppressed = false;
4590
+ plannerVisible = false;
4591
+ renderFloatingSurfaceMotion();
4592
+ }, SUCCESS_SETTLE_MS);
4593
+ return true;
4594
+ }
4595
+ function externalAgentIdentity(kind) {
4596
+ return kind.startsWith("claude") ? messages.execution.claude : kind.startsWith("codex") ? messages.execution.codex : kind;
4597
+ }
4598
+ function projectAgentActivity(item) {
4599
+ return Object.freeze({
4600
+ ...item.detail === void 0 ? {} : { detail: item.detail },
4601
+ key: item.key,
4602
+ kind: item.kind,
4603
+ label: messages.execution.activityLane(item.kind, item.detail),
4604
+ state: item.state
4605
+ });
4606
+ }
4607
+ function motionActivity(key, kind, detail, state = "active") {
4608
+ return Object.freeze({
4609
+ ...detail === void 0 ? {} : { detail },
4610
+ key,
4611
+ kind,
4612
+ label: messages.execution.activityLane(kind, detail),
4613
+ state
4614
+ });
4615
+ }
4616
+ function renderFloatingSurfaceMotion() {
4617
+ const capturing = currentStatus === "inspecting";
4618
+ const pillActive = !plannerVisible || capturing;
4619
+ const projection = pillActive ? {
4620
+ scene: capturing ? "capturing" : "pill",
4621
+ tone: capturing ? "capturing" : "neutral",
4622
+ headline: triggerButton.textContent,
4623
+ action: "",
4624
+ meta: "",
4625
+ recentActivities: Object.freeze([])
4626
+ } : !executionSuppressed && executionProjection !== void 0 ? executionProjection : {
4627
+ scene: "planner",
4628
+ tone: "ready",
4629
+ headline: messages.dialog.editTitle,
4630
+ action: messages.dialog.editSubtitle,
4631
+ meta: messages.context.ready,
4632
+ recentActivities: Object.freeze([])
4633
+ };
4634
+ if (motionController !== void 0) {
4635
+ motionController.render(projection, () => {
4636
+ executionIslandView.render(projection);
4637
+ });
4638
+ return;
4639
+ }
4640
+ executionIslandView.render(projection);
4641
+ floatingSurfaceRoot.dataset.scene = projection.scene;
4642
+ floatingSurfaceRoot.dataset.tone = projection.tone;
4643
+ const plannerActive = projection.scene === "planner";
4644
+ triggerButton.hidden = !pillActive;
4645
+ triggerButton.inert = !pillActive;
4646
+ dialog.hidden = !plannerActive;
4647
+ dialog.inert = !plannerActive;
4648
+ dialog.setAttribute("aria-hidden", String(!plannerActive));
4649
+ executionIsland.hidden = pillActive || plannerActive;
4650
+ executionIsland.inert = pillActive || plannerActive;
4651
+ executionIsland.setAttribute("aria-hidden", String(pillActive || plannerActive));
4652
+ floatingSurface.reconcile();
4653
+ }
4654
+ function renderExternalDispatch(dispatch) {
4655
+ if (dispatch === null) {
4656
+ pendingAgentMotion = void 0;
4657
+ observedExecutionStartedAt = void 0;
4658
+ } else if (pendingAgentMotion !== void 0 && dispatch.phase !== "failed" && dispatch.phase !== "delivery-unknown") {
4659
+ startStagedAgentRequest();
4660
+ } else if (dispatch.phase === "failed" || dispatch.phase === "delivery-unknown") {
4661
+ pendingAgentMotion = void 0;
4662
+ }
4663
+ if (dispatch !== null && !executionSuppressed) {
4664
+ const failed = dispatch.phase === "failed" || dispatch.phase === "delivery-unknown";
4665
+ const completed = dispatch.phase === "completed";
4666
+ const startedAt = failed || completed ? void 0 : observedExecutionStart();
4667
+ if (failed || completed) observedExecutionStartedAt = void 0;
4668
+ const successKey = `dispatch:${String(dispatch.revision)}`;
4669
+ if (completed && !showSuccessUntilSettled(successKey)) {
4670
+ executionProjection = void 0;
4671
+ renderFloatingSurfaceMotion();
4672
+ return;
4673
+ }
4674
+ const identity = externalAgentIdentity(dispatch.adapterKind);
4675
+ const target2 = currentTargetSummary();
4676
+ const activityKind = dispatch.phase === "queued" || dispatch.phase === "dispatching" || dispatch.phase === "dispatched" ? "dispatch" : completed ? "sync" : "unknown";
4677
+ const activity = motionActivity(
4678
+ `dispatch:${String(dispatch.revision)}:${dispatch.phase}`,
4679
+ activityKind,
4680
+ dispatch.phase,
4681
+ failed ? "failure" : completed ? "success" : "active"
4682
+ );
4683
+ const scene = failed ? "failed" : completed ? "success" : dispatch.phase === "working" ? "running" : "handoff";
4684
+ executionProjection = {
4685
+ scene,
4686
+ tone: failed ? "danger" : completed ? "success" : "running",
4687
+ headline: completed ? messages.execution.completedTitle : failed ? messages.execution.failedTitle : scene === "running" ? messages.execution.runningTitle(target2.label) : messages.execution.dispatchingTitle(identity),
4688
+ action: completed ? messages.execution.resultReturned : target2.source,
4689
+ expandedHeadline: completed ? messages.execution.expandedCompletedTitle : failed ? messages.execution.failedTitle : scene === "running" ? messages.execution.expandedRunningTitle(identity) : messages.execution.dispatchingTitle(identity),
4690
+ expandedAction: completed ? messages.execution.resultReturned : currentTargetContext(),
4691
+ meta: failed ? messages.execution.failedStatus : completed ? messages.execution.completedStatus : scene === "running" ? messages.execution.runningStatus : messages.execution.dispatchingStatus,
4692
+ activity,
4693
+ recentActivities: Object.freeze([activity]),
4694
+ ...startedAt === void 0 ? {} : { startedAt }
4695
+ };
4696
+ } else if (dispatch === null) {
4697
+ executionProjection = void 0;
4698
+ }
4699
+ renderFloatingSurfaceMotion();
4700
+ }
4701
+ function renderExternalControl(status) {
4702
+ const task = status?.task;
4703
+ if (status === void 0 || task === void 0 || executionSuppressed) return;
4704
+ const phase = task.managedPhase;
4705
+ const failed = phase === "failed" || phase === "cleanup-warning";
4706
+ const completed = phase === "completed" || phase === "review-required";
4707
+ const cancelled = phase === "cancelled";
4708
+ if (failed || cancelled) {
4709
+ pendingAgentMotion = void 0;
4710
+ } else if (pendingAgentMotion !== void 0) {
4711
+ startStagedAgentRequest();
4712
+ }
4713
+ if (cancelled) {
4714
+ executionProjection = void 0;
4715
+ observedExecutionStartedAt = void 0;
4716
+ renderFloatingSurfaceMotion();
4717
+ return;
4718
+ }
4719
+ const startedAt = failed || completed ? void 0 : observedExecutionStart();
4720
+ if (failed || completed) observedExecutionStartedAt = void 0;
4721
+ if (phase === "completed" && !showSuccessUntilSettled(`managed:${String(task.revision)}`)) {
4722
+ executionProjection = void 0;
4723
+ renderFloatingSurfaceMotion();
4724
+ return;
4725
+ }
4726
+ const scene = failed ? "failed" : completed ? "success" : phase === "preparing" ? "handoff" : "running";
4727
+ const tone = failed ? "danger" : completed ? "success" : "running";
4728
+ const identity = messages.execution.codex;
4729
+ const target2 = currentTargetSummary();
4730
+ const activityKind = phase === "preparing" ? "prepare" : phase === "auditing" ? "audit" : phase === "validating" ? "check" : phase === "applying" ? "apply" : completed ? "sync" : "unknown";
4731
+ const latestCheck = task.checks.at(-1);
4732
+ const latestFile = task.files.at(-1);
4733
+ const activityDetail = phase === "validating" ? latestCheck?.id : phase === "auditing" || phase === "applying" ? latestFile?.path : void 0;
4734
+ const activity = motionActivity(
4735
+ `managed:${String(task.revision)}:${phase}:${activityDetail ?? ""}`,
4736
+ activityKind,
4737
+ activityDetail,
4738
+ failed ? "failure" : completed ? "success" : "active"
4739
+ );
4740
+ const recentActivities = Object.freeze([
4741
+ ...task.files.slice(-2).map(
4742
+ (file) => motionActivity(
4743
+ `managed-file:${String(task.revision)}:${file.path}`,
4744
+ "patch",
4745
+ file.path,
4746
+ "success"
4747
+ )
4748
+ ),
4749
+ ...task.checks.slice(-2).map(
4750
+ (check) => motionActivity(
4751
+ `managed-check:${String(task.revision)}:${check.id}`,
4752
+ "check",
4753
+ check.id,
4754
+ check.outcome === "passed" ? "success" : "failure"
4755
+ )
4756
+ ),
4757
+ activity
4758
+ ]);
4759
+ executionProjection = {
4760
+ scene,
4761
+ tone,
4762
+ headline: completed ? messages.execution.completedTitle : failed ? messages.execution.failedTitle : scene === "handoff" ? messages.execution.dispatchingTitle(identity) : phase === "validating" ? messages.execution.activityAction("check", activityDetail) : messages.execution.runningTitle(target2.label),
4763
+ action: completed ? task.files.length === 0 ? messages.execution.resultReturned : messages.execution.resultSummary(task.files.length) : target2.source,
4764
+ expandedHeadline: completed ? messages.execution.expandedCompletedTitle : failed ? messages.execution.failedTitle : scene === "handoff" ? messages.execution.dispatchingTitle(identity) : messages.execution.expandedRunningTitle(identity),
4765
+ expandedAction: completed ? messages.execution.resultReturned : currentTargetContext(),
4766
+ meta: failed ? messages.execution.failedStatus : completed ? messages.execution.completedStatus : phase === "validating" ? messages.execution.checkingStatus : scene === "handoff" ? messages.execution.dispatchingStatus : messages.execution.runningStatus,
4767
+ activity,
4768
+ recentActivities,
4769
+ ...startedAt === void 0 ? {} : { startedAt }
4770
+ };
4771
+ renderFloatingSurfaceMotion();
4772
+ }
4773
+ function beginAgentRequest(target2, agentCard) {
4774
+ if (!plannerVisible) {
4775
+ return;
4776
+ }
4777
+ clearSuccessSettle(true);
4778
+ const startedAt = beginObservedExecution();
4779
+ executionSuppressed = false;
4780
+ const activity = motionActivity(
4781
+ `dispatch-start:${currentTargetContext()}`,
4782
+ "dispatch",
4783
+ currentTargetContext()
4784
+ );
4785
+ executionProjection = {
4786
+ scene: "agent-charging",
4787
+ tone: "running",
4788
+ headline: messages.execution.receivingTitle,
4789
+ action: currentTargetContext(),
4790
+ expandedHeadline: messages.execution.receivingTitle,
4791
+ expandedAction: currentTargetContext(),
4792
+ meta: messages.execution.receivingStatus,
4793
+ activity,
4794
+ recentActivities: Object.freeze([activity]),
4795
+ startedAt
4796
+ };
4797
+ renderFloatingSurfaceMotion();
4798
+ motionController?.dispatch(target2, agentCard);
4799
+ }
4800
+ function stageAgentRequest(source2, target2) {
4801
+ if (!plannerVisible || source2.disabled) return;
4802
+ pendingAgentMotion = Object.freeze({ source: source2, target: target2 });
4803
+ }
4804
+ function startStagedAgentRequest() {
4805
+ const pending = pendingAgentMotion;
4806
+ pendingAgentMotion = void 0;
4807
+ if (pending === void 0 || !pending.source.isConnected || !pending.target.isConnected) {
4808
+ return;
4809
+ }
4810
+ beginAgentRequest(pending.source, pending.target);
4811
+ }
4086
4812
  function renderEditorStatus(state) {
4087
4813
  currentEditorFeedbackState = state;
4088
4814
  editorFeedback.dataset.state = state;
4089
4815
  editorFeedback.hidden = state === "idle";
4090
4816
  editorFeedback.textContent = state === "opening" ? messages.announcements.editorOpening : state === "success" ? messages.announcements.editorOpened : state === "error" ? messages.announcements.editorFailed : "";
4091
- placeDialog();
4817
+ requestFloatingSurfaceLayout();
4092
4818
  }
4093
4819
  function statusText(target2) {
4094
4820
  if (target2.status === "ready") {
@@ -4223,6 +4949,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4223
4949
  replacement?.setSelectionRange(selectionStart, selectionEnd);
4224
4950
  }
4225
4951
  }
4952
+ requestFloatingSurfaceLayout();
4226
4953
  }
4227
4954
  function updateTargetInstruction(targetId, instruction) {
4228
4955
  currentTargets = currentTargets.map(
@@ -4271,8 +4998,6 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4271
4998
  }
4272
4999
  function showSelectionHighlights(targets) {
4273
5000
  selectionHighlights.replaceChildren();
4274
- activeSelectionRect = targets.find((target2) => target2.active)?.rect;
4275
- currentRect = activeSelectionRect;
4276
5001
  for (const [index, target2] of targets.entries()) {
4277
5002
  const box = createMarkedElement(document, "div");
4278
5003
  box.className = "spotpatch-selection-highlight";
@@ -4286,31 +5011,6 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4286
5011
  box.append(label);
4287
5012
  selectionHighlights.append(box);
4288
5013
  }
4289
- placeDialog();
4290
- }
4291
- function placeDialog() {
4292
- if (dialog.hidden) {
4293
- return;
4294
- }
4295
- const view = document.defaultView;
4296
- const viewportWidth = view?.innerWidth ?? document.documentElement.clientWidth;
4297
- const viewportHeight = view?.innerHeight ?? document.documentElement.clientHeight;
4298
- const measured = dialog.getBoundingClientRect();
4299
- const status = currentStatus === "previewing" ? "previewing" : "selected";
4300
- const dialogWidth = measured.width > 0 ? measured.width : Math.min(DIALOG_MAX_WIDTH, viewportWidth - 32);
4301
- const dialogHeight = measured.height > 0 ? measured.height : Math.min(DIALOG_FALLBACK_HEIGHT[status], viewportHeight - 32);
4302
- const placement = calculateDialogPlacement({
4303
- dialogWidth,
4304
- dialogHeight,
4305
- viewportWidth,
4306
- viewportHeight,
4307
- ...currentRect === void 0 ? {} : { target: currentRect }
4308
- });
4309
- dialog.style.left = `${String(placement.left)}px`;
4310
- dialog.style.top = `${String(placement.top)}px`;
4311
- dialog.style.setProperty("--spotpatch-anchor-x", `${String(placement.anchorX)}px`);
4312
- dialog.style.setProperty("--spotpatch-anchor-y", `${String(placement.anchorY)}px`);
4313
- dialog.dataset.placement = placement.mode;
4314
5014
  }
4315
5015
  function updateContextOverview(summaryText) {
4316
5016
  const summaryMessages = messages.summary;
@@ -4341,14 +5041,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4341
5041
  previewButton.disabled = !canPreview;
4342
5042
  agentPanel.setContextReady(canPreview);
4343
5043
  externalHandoffPanel?.setContextReady(canPreview);
5044
+ contextualAskPanel?.setSelectionPreview({
5045
+ contextReady: currentTargets.every((target2) => target2.status !== "loading"),
5046
+ targetCount: currentTargets.length,
5047
+ sourceCount: currentTargets.filter((target2) => target2.canOpenEditor).length
5048
+ });
4344
5049
  updateContextOverview(summaryText);
4345
- placeDialog();
5050
+ requestFloatingSurfaceLayout();
4346
5051
  }
4347
5052
  function renderPanelStatus(status) {
4348
5053
  currentStatus = status;
4349
5054
  const selected = status === "selected";
4350
5055
  const previewing = status === "previewing";
4351
- triggerButton.hidden = selected || previewing;
4352
5056
  selectionPanel.hidden = !selected;
4353
5057
  previewPanel.hidden = !previewing;
4354
5058
  reselectButton.hidden = !selected;
@@ -4358,11 +5062,12 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4358
5062
  secondaryActions.hidden = !selected;
4359
5063
  agentPanel.setSelectionVisible(selected);
4360
5064
  externalHandoffPanel?.setSelectionVisible(selected);
5065
+ contextualAskPanel?.setSelectionVisible(selected);
4361
5066
  copyButton.hidden = !previewing;
4362
5067
  backButton.hidden = !previewing;
4363
- title.textContent = previewing ? messages.dialog.previewTitle : messages.dialog.editTitle;
4364
- subtitle.textContent = previewing ? messages.dialog.previewSubtitle : messages.dialog.editSubtitle;
4365
- placeDialog();
5068
+ title.textContent = previewing ? messages.dialog.previewTitle : contextualAskMode === "ask" && contextualAskTitle.length > 0 ? contextualAskTitle : messages.dialog.editTitle;
5069
+ subtitle.textContent = previewing ? messages.dialog.previewSubtitle : contextualAskMode === "ask" && contextualAskSubtitle.length > 0 ? contextualAskSubtitle : messages.dialog.editSubtitle;
5070
+ renderFloatingSurfaceMotion();
4366
5071
  }
4367
5072
  function applyMessages() {
4368
5073
  messages = localizer.messages();
@@ -4374,8 +5079,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4374
5079
  localeButton.textContent = messages.alternateLocaleName;
4375
5080
  localeButton.title = messages.switchLocale;
4376
5081
  localeButton.setAttribute("aria-label", messages.switchLocale);
5082
+ resetPositionButton.title = messages.floatingSurface.resetPosition;
5083
+ resetPositionButton.setAttribute(
5084
+ "aria-label",
5085
+ messages.floatingSurface.resetPosition
5086
+ );
4377
5087
  closeButton.setAttribute("aria-label", messages.dialog.close);
4378
5088
  closeButton.title = messages.dialog.close;
5089
+ header.title = messages.floatingSurface.dragHandle;
4379
5090
  targetsPanel.setAttribute("aria-label", messages.targets.ariaLabel);
4380
5091
  targetsTitle.textContent = messages.targets.title;
4381
5092
  diagnosticsTitle.textContent = messages.diagnostics.title;
@@ -4411,8 +5122,58 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4411
5122
  contextState.textContent = messages.context.collecting;
4412
5123
  }
4413
5124
  }
4414
- diagnostics.addEventListener("toggle", placeDialog);
5125
+ function announce(message) {
5126
+ liveRegion.textContent = "";
5127
+ liveRegion.textContent = message;
5128
+ }
5129
+ function resetFloatingSurfacePosition() {
5130
+ floatingSurface.reset();
5131
+ announce(messages.floatingSurface.positionReset);
5132
+ }
5133
+ diagnostics.addEventListener("toggle", requestFloatingSurfaceLayout);
4415
5134
  localeButton.addEventListener("click", localizer.toggle);
5135
+ resetPositionButton.addEventListener("click", resetFloatingSurfacePosition);
5136
+ const onAgentRun = () => {
5137
+ stageAgentRequest(agentPanel.runButton, agentPanel.root);
5138
+ };
5139
+ const onExternalSend = () => {
5140
+ if (externalHandoffPanel !== void 0) {
5141
+ stageAgentRequest(externalHandoffPanel.sendButton, externalHandoffPanel.root);
5142
+ }
5143
+ };
5144
+ const onExecutionOpen = () => {
5145
+ if (executionIslandView.canExpand() && !executionIslandView.isExpanded()) {
5146
+ if (motionController === void 0) {
5147
+ executionIslandView.setExpanded(true);
5148
+ floatingSurface.reconcile();
5149
+ } else {
5150
+ motionController.updateLayout(() => {
5151
+ executionIslandView.setExpanded(true);
5152
+ });
5153
+ }
5154
+ return;
5155
+ }
5156
+ clearSuccessSettle(false);
5157
+ executionSuppressed = true;
5158
+ renderFloatingSurfaceMotion();
5159
+ };
5160
+ const onExecutionKeyDown = (event) => {
5161
+ if (event.key !== "Escape" || !executionIslandView.isExpanded()) return;
5162
+ event.preventDefault();
5163
+ event.stopPropagation();
5164
+ if (motionController === void 0) {
5165
+ executionIslandView.setExpanded(false);
5166
+ floatingSurface.reconcile();
5167
+ } else {
5168
+ motionController.updateLayout(() => {
5169
+ executionIslandView.setExpanded(false);
5170
+ });
5171
+ }
5172
+ };
5173
+ agentPanel.runButton.addEventListener("click", onAgentRun);
5174
+ externalHandoffPanel?.sendButton.addEventListener("click", onExternalSend);
5175
+ executionIsland.addEventListener("click", onExecutionOpen);
5176
+ executionIsland.addEventListener("keydown", onExecutionKeyDown);
4416
5177
  const unsubscribeLocale = localizer.subscribe(applyMessages);
4417
5178
  applyMessages();
4418
5179
  return Object.freeze({
@@ -4440,8 +5201,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4440
5201
  agentRevertButton: agentPanel.revertButton,
4441
5202
  agentResetButton: agentPanel.resetButton,
4442
5203
  ...externalHandoffPanel === void 0 ? {} : { externalHandoffPanel },
5204
+ ...contextualAskPanel === void 0 ? {} : { contextualAskPanel },
4443
5205
  renderStatus(status) {
4444
5206
  const inspecting = status === "inspecting";
5207
+ if (status === "idle" || inspecting) {
5208
+ plannerVisible = false;
5209
+ } else {
5210
+ plannerVisible = true;
5211
+ }
4445
5212
  triggerButton.setAttribute("aria-pressed", String(inspecting));
4446
5213
  triggerButton.textContent = inspecting ? messages.trigger.stop : messages.trigger.select;
4447
5214
  renderPanelStatus(status);
@@ -4450,36 +5217,28 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4450
5217
  renderDataFlow(state) {
4451
5218
  currentDataFlowState = state;
4452
5219
  dataFlowPanel.render(state);
4453
- placeDialog();
5220
+ requestFloatingSurfaceLayout();
4454
5221
  },
4455
5222
  showHighlight(rect, label) {
4456
- currentRect = rect;
4457
5223
  highlight.hidden = false;
4458
5224
  highlight.style.transform = `translate(${String(rect.x)}px, ${String(rect.y)}px)`;
4459
5225
  highlight.style.width = `${String(rect.width)}px`;
4460
5226
  highlight.style.height = `${String(rect.height)}px`;
4461
5227
  highlightLabel.textContent = label;
4462
5228
  targetLabel.textContent = label;
4463
- placeDialog();
4464
5229
  },
4465
5230
  hideHighlight() {
4466
- currentRect = activeSelectionRect;
4467
5231
  highlight.hidden = true;
4468
5232
  highlightLabel.textContent = "";
4469
- placeDialog();
4470
5233
  },
4471
5234
  showSelectionHighlights,
4472
5235
  hideSelectionHighlights() {
4473
5236
  selectionHighlights.replaceChildren();
4474
- activeSelectionRect = void 0;
4475
- currentRect = void 0;
4476
- placeDialog();
4477
5237
  },
4478
5238
  showSelection(summaryText, canOpenEditor, canPreview) {
4479
5239
  updateSelection(summaryText, canOpenEditor, canPreview);
4480
- triggerButton.hidden = true;
4481
- dialog.hidden = false;
4482
- placeDialog();
5240
+ plannerVisible = true;
5241
+ renderPanelStatus(currentStatus === "previewing" ? "previewing" : "selected");
4483
5242
  },
4484
5243
  updateSelection,
4485
5244
  renderTargets,
@@ -4491,8 +5250,9 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4491
5250
  externalHandoffPanel?.setContextReady(enabled);
4492
5251
  },
4493
5252
  hideSelection() {
4494
- dialog.hidden = true;
4495
- triggerButton.hidden = false;
5253
+ plannerVisible = false;
5254
+ executionSuppressed = false;
5255
+ executionProjection = void 0;
4496
5256
  targetList.replaceChildren();
4497
5257
  currentTargets = [];
4498
5258
  currentMaximum = 0;
@@ -4515,17 +5275,21 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4515
5275
  agentPanel.setSelectionVisible(false);
4516
5276
  externalHandoffPanel?.setContextReady(false);
4517
5277
  externalHandoffPanel?.setSelectionVisible(false);
5278
+ contextualAskPanel?.setSelectionVisible(false);
4518
5279
  agentPanel.setEditingEnabled(true);
4519
5280
  agentPanel.resetJob();
4520
5281
  dataFlowPanel.resetView();
5282
+ renderFloatingSurfaceMotion();
4521
5283
  },
4522
5284
  hideSelectionTemporarily() {
4523
- dialog.hidden = true;
4524
- triggerButton.hidden = false;
5285
+ plannerVisible = false;
5286
+ executionSuppressed = false;
5287
+ executionProjection = void 0;
5288
+ renderFloatingSurfaceMotion();
4525
5289
  },
4526
5290
  showPreview(prompt) {
4527
5291
  promptOutput.textContent = prompt;
4528
- placeDialog();
5292
+ requestFloatingSurfaceLayout();
4529
5293
  },
4530
5294
  readAgentSelection() {
4531
5295
  return agentPanel.readSelection();
@@ -4549,25 +5313,86 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4549
5313
  previewButton.disabled = !enabled || !currentCanPreview;
4550
5314
  externalHandoffPanel?.setContextReady(enabled && currentCanPreview);
4551
5315
  agentPanel.setEditingEnabled(enabled);
4552
- placeDialog();
5316
+ requestFloatingSurfaceLayout();
4553
5317
  },
4554
5318
  renderAgentCapability(state, message, capabilitySnapshot, errorCode) {
4555
5319
  agentPanel.renderCapability(state, message, capabilitySnapshot, errorCode);
5320
+ if (state === "error") {
5321
+ pendingAgentMotion = void 0;
5322
+ renderFloatingSurfaceMotion();
5323
+ }
4556
5324
  const agentReady = state === "ready" && capabilitySnapshot?.state === "agent-ready";
4557
5325
  previewButton.classList.toggle("spotpatch-primary", !agentReady);
4558
- placeDialog();
5326
+ requestFloatingSurfaceLayout();
4559
5327
  },
4560
5328
  renderAgentWorkspaceHealth(state, snapshot, errorCode) {
4561
5329
  agentPanel.renderWorkspaceHealth(state, snapshot, errorCode);
4562
- placeDialog();
5330
+ if (state === "blocked") {
5331
+ pendingAgentMotion = void 0;
5332
+ renderFloatingSurfaceMotion();
5333
+ }
5334
+ requestFloatingSurfaceLayout();
4563
5335
  },
4564
5336
  renderAgentJob(snapshot, result, activities, errorCode) {
4565
5337
  agentPanel.renderJob(snapshot, result, activities, errorCode);
4566
- placeDialog();
5338
+ if (pendingAgentMotion !== void 0 && snapshot.status !== "failed" && snapshot.status !== "cancelled") {
5339
+ startStagedAgentRequest();
5340
+ }
5341
+ if (snapshot.status === "failed" || snapshot.status === "cancelled") {
5342
+ pendingAgentMotion = void 0;
5343
+ }
5344
+ const successful = snapshot.status === "awaiting-review" || snapshot.status === "applied" || snapshot.status === "completed" || snapshot.status === "reverted";
5345
+ const failed = snapshot.status === "failed";
5346
+ const cancelled = snapshot.status === "cancelled";
5347
+ const terminalSuccess = snapshot.status === "applied" || snapshot.status === "completed" || snapshot.status === "reverted";
5348
+ const scene = successful ? "success" : failed ? "failed" : snapshot.status === "queued" || snapshot.status === "preparing" ? "handoff" : "running";
5349
+ if (!executionSuppressed) {
5350
+ if (terminalSuccess && !showSuccessUntilSettled(`job:${snapshot.jobId}:${snapshot.status}`)) {
5351
+ executionProjection = void 0;
5352
+ renderFloatingSurfaceMotion();
5353
+ requestFloatingSurfaceLayout();
5354
+ return;
5355
+ }
5356
+ const identity = snapshot.providerLabel;
5357
+ const target2 = currentTargetSummary();
5358
+ const projectedActivities = Object.freeze(activities.map(projectAgentActivity));
5359
+ const latestActivity = activities.at(-1);
5360
+ const phaseKind = snapshot.status === "queued" || snapshot.status === "preparing" ? "prepare" : snapshot.status === "validating" ? "check" : snapshot.status === "applying" || snapshot.status === "reverting" ? "apply" : snapshot.status === "awaiting-review" || successful ? "sync" : "unknown";
5361
+ const phaseActivity = motionActivity(
5362
+ `job-phase:${snapshot.jobId}:${snapshot.status}:${snapshot.updatedAt}`,
5363
+ phaseKind,
5364
+ void 0,
5365
+ successful ? "success" : failed ? "failure" : "active"
5366
+ );
5367
+ const activity = snapshot.status === "running" && latestActivity !== void 0 ? projectAgentActivity(latestActivity) : phaseActivity;
5368
+ executionProjection = cancelled ? void 0 : {
5369
+ scene,
5370
+ tone: successful ? "success" : failed ? "danger" : "running",
5371
+ headline: successful ? messages.execution.completedTitle : failed ? messages.execution.failedTitle : scene === "handoff" ? messages.execution.dispatchingTitle(identity) : snapshot.status === "validating" ? messages.execution.activityAction(
5372
+ "check",
5373
+ latestActivity?.detail
5374
+ ) : snapshot.status === "applying" || snapshot.status === "reverting" ? messages.execution.activityAction(phaseKind) : messages.execution.runningTitle(target2.label),
5375
+ action: successful ? result === void 0 || result.files.length === 0 ? messages.execution.resultReturned : messages.execution.resultSummary(result.files.length) : latestActivity?.detail ?? target2.source,
5376
+ expandedHeadline: successful ? messages.execution.expandedCompletedTitle : failed ? messages.execution.failedTitle : scene === "handoff" ? messages.execution.dispatchingTitle(identity) : messages.execution.expandedRunningTitle(identity),
5377
+ expandedAction: successful ? result === void 0 || result.files.length === 0 ? messages.execution.resultReturned : messages.execution.resultSummary(result.files.length) : currentTargetContext(),
5378
+ meta: successful ? messages.execution.completedStatus : failed ? messages.execution.failedStatus : snapshot.status === "validating" ? messages.execution.checkingStatus : scene === "handoff" ? messages.execution.dispatchingStatus : messages.execution.runningStatus,
5379
+ activity,
5380
+ recentActivities: projectedActivities.length === 0 ? Object.freeze([phaseActivity]) : projectedActivities,
5381
+ startedAt: snapshot.createdAt
5382
+ };
5383
+ }
5384
+ renderFloatingSurfaceMotion();
5385
+ requestFloatingSurfaceLayout();
4567
5386
  },
4568
5387
  resetAgentJob() {
4569
5388
  agentPanel.resetJob();
4570
- placeDialog();
5389
+ observedExecutionStartedAt = void 0;
5390
+ if (executionProjection !== void 0) {
5391
+ executionProjection = void 0;
5392
+ executionSuppressed = false;
5393
+ renderFloatingSurfaceMotion();
5394
+ }
5395
+ requestFloatingSurfaceLayout();
4571
5396
  },
4572
5397
  focusTargetInstruction(targetId) {
4573
5398
  instructionInput(targetId)?.focus({ preventScroll: true });
@@ -4575,21 +5400,28 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4575
5400
  focusPrompt() {
4576
5401
  promptOutput.focus({ preventScroll: true });
4577
5402
  },
4578
- announce(message) {
4579
- liveRegion.textContent = "";
4580
- liveRegion.textContent = message;
4581
- },
5403
+ announce,
4582
5404
  locale: localizer.locale,
4583
5405
  messages: localizer.messages,
4584
5406
  agentWorkspaceConsentGranted: agentPanel.workspaceConsentGranted,
4585
5407
  subscribeLocale: localizer.subscribe,
4586
5408
  dispose() {
4587
- diagnostics.removeEventListener("toggle", placeDialog);
5409
+ diagnostics.removeEventListener("toggle", requestFloatingSurfaceLayout);
4588
5410
  localeButton.removeEventListener("click", localizer.toggle);
5411
+ resetPositionButton.removeEventListener("click", resetFloatingSurfacePosition);
5412
+ agentPanel.runButton.removeEventListener("click", onAgentRun);
5413
+ externalHandoffPanel?.sendButton.removeEventListener("click", onExternalSend);
5414
+ executionIsland.removeEventListener("click", onExecutionOpen);
5415
+ executionIsland.removeEventListener("keydown", onExecutionKeyDown);
4589
5416
  unsubscribeLocale();
4590
5417
  dataFlowPanel.dispose();
4591
5418
  externalHandoffPanel?.dispose();
5419
+ contextualAskPanel?.dispose();
4592
5420
  agentPanel.dispose();
5421
+ motionController?.dispose();
5422
+ executionIslandView.dispose();
5423
+ clearSuccessSettle(true);
5424
+ floatingSurface.dispose();
4593
5425
  host.remove();
4594
5426
  }
4595
5427
  });
@@ -4658,17 +5490,33 @@ import {
4658
5490
  function capabilityKey(providerProfileId, modelProfileId) {
4659
5491
  return `${providerProfileId}:${modelProfileId}`;
4660
5492
  }
5493
+ var TOOL_ACTIVITY_KIND = Object.freeze({
5494
+ list_files: "discover",
5495
+ search_text: "search",
5496
+ read_file: "read",
5497
+ replace_text: "patch",
5498
+ apply_patch: "patch",
5499
+ run_check: "check"
5500
+ });
5501
+ function toolActivityKind(toolName) {
5502
+ return TOOL_ACTIVITY_KIND[toolName] ?? "unknown";
5503
+ }
4661
5504
  function activityFromEvent(event) {
4662
5505
  if (event.type === "tool") {
5506
+ const detail = event.data.relativePath ?? event.data.checkLabel;
4663
5507
  return Object.freeze({
4664
5508
  key: `tool:${String(event.data.turn)}:${event.data.toolCallId}`,
4665
- label: `${event.data.toolName} \xB7 ${event.data.state}`,
5509
+ kind: toolActivityKind(event.data.toolName),
5510
+ ...detail === void 0 ? {} : { detail },
5511
+ label: `${event.data.toolName}${detail === void 0 ? "" : ` \xB7 ${detail}`} \xB7 ${event.data.state}`,
4666
5512
  state: event.data.state === "started" ? "active" : event.data.state === "succeeded" ? "success" : "failure"
4667
5513
  });
4668
5514
  }
4669
5515
  if (event.type === "check") {
4670
5516
  return Object.freeze({
4671
5517
  key: `check:${event.data.result.checkId}:${String(event.sequence)}`,
5518
+ kind: "check",
5519
+ detail: event.data.result.label,
4672
5520
  label: `${event.data.result.label} \xB7 ${event.data.result.status}`,
4673
5521
  state: event.data.result.status === "passed" ? "success" : event.data.result.status === "failed" || event.data.result.status === "timed-out" ? "failure" : "info"
4674
5522
  });
@@ -5089,7 +5937,8 @@ function createController(config, dependencies = {}) {
5089
5937
  config.dataFlow.enabled,
5090
5938
  config.externalAgent.enabled,
5091
5939
  config.framework,
5092
- config.sessionId
5940
+ config.sessionId,
5941
+ config.contextualAsk.enabled
5093
5942
  );
5094
5943
  const api = dependencies.api ?? createRuntimeApi({
5095
5944
  apiBase: config.apiBase,
@@ -5292,6 +6141,19 @@ function createController(config, dependencies = {}) {
5292
6141
  (target2) => target2.instruction.trim().length > 0 && target2.elementContext !== void 0 && target2.styles !== void 0 && target2.apiStatus !== "loading"
5293
6142
  );
5294
6143
  }
6144
+ function selectedAskSelection() {
6145
+ const snapshots = targets.flatMap((target2) => {
6146
+ const snapshot = snapshotTarget(target2);
6147
+ return snapshot === void 0 ? [] : [snapshot];
6148
+ });
6149
+ if (snapshots.length === 0 || snapshots.length !== targets.length) {
6150
+ return void 0;
6151
+ }
6152
+ return Object.freeze({
6153
+ locale: view.locale(),
6154
+ targets: snapshots
6155
+ });
6156
+ }
5295
6157
  function selectionSummary() {
5296
6158
  const messages = view.messages().summary;
5297
6159
  return targets.map((target2, index) => {
@@ -5413,6 +6275,7 @@ ${summary}`;
5413
6275
  api.cancelPending();
5414
6276
  agentWorkflow.disposeSelection();
5415
6277
  externalHandoffWorkflow?.cancelPending();
6278
+ contextualAskWorkflow?.cancelPending();
5416
6279
  clearCollectionTimers();
5417
6280
  resizeObserver?.disconnect();
5418
6281
  targets = [];
@@ -5538,6 +6401,27 @@ ${summary}`;
5538
6401
  config.sessionToken,
5539
6402
  browser.window
5540
6403
  ) : void 0;
6404
+ const contextualAskWorkflow = config.contextualAsk.enabled && view.contextualAskPanel !== void 0 ? getContextualAskExtension()?.createWorkflow({
6405
+ ...clipboard === void 0 ? {} : { clipboard },
6406
+ createId,
6407
+ fetch: browser.window.fetch.bind(browser.window),
6408
+ getSelection: selectedAskSelection,
6409
+ onBusyChange(busy) {
6410
+ view.setAgentEditingEnabled(!busy);
6411
+ },
6412
+ onConvert() {
6413
+ view.focusTargetInstruction(activeTargetId);
6414
+ },
6415
+ async onOpenSource(source2) {
6416
+ await api.openEditor({
6417
+ fileId: source2.fileId,
6418
+ line: source2.startLine,
6419
+ column: 1
6420
+ });
6421
+ },
6422
+ panel: view.contextualAskPanel,
6423
+ sessionToken: config.sessionToken
6424
+ }) : void 0;
5541
6425
  async function loadSourceContext(target2, revision) {
5542
6426
  const marker = target2.marker;
5543
6427
  if (marker === void 0) {
@@ -5651,6 +6535,7 @@ ${summary}`;
5651
6535
  previewPrompt = "";
5652
6536
  agentWorkflow.beginSelection();
5653
6537
  workflowSelectionActive = true;
6538
+ contextualAskWorkflow?.beginSelection();
5654
6539
  }
5655
6540
  targetSequence += 1;
5656
6541
  const target2 = {
@@ -5669,6 +6554,9 @@ ${summary}`;
5669
6554
  dataFlowStatus: "idle"
5670
6555
  };
5671
6556
  targets.push(target2);
6557
+ if (!initialSelection) {
6558
+ contextualAskWorkflow?.selectionChanged();
6559
+ }
5672
6560
  activeTargetId = target2.id;
5673
6561
  addingTarget = false;
5674
6562
  const revision = sessionRevision;
@@ -5785,10 +6673,12 @@ ${summary}`;
5785
6673
  pageDataFlowStatus = "idle";
5786
6674
  renderDataFlowView();
5787
6675
  selectionSession.clear();
6676
+ contextualAskWorkflow?.selectionChanged();
5788
6677
  view.announce(view.messages().announcements.allTargetsRemoved);
5789
6678
  return;
5790
6679
  }
5791
6680
  refreshSelectionView();
6681
+ contextualAskWorkflow?.selectionChanged();
5792
6682
  void loadDataFlowReports();
5793
6683
  persistSelection();
5794
6684
  view.announce(view.messages().announcements.targetRemoved);
@@ -5967,6 +6857,7 @@ ${summary}`;
5967
6857
  }
5968
6858
  mounted = true;
5969
6859
  externalHandoffWorkflow?.mount();
6860
+ contextualAskWorkflow?.mount();
5970
6861
  view.renderStatus(state.status);
5971
6862
  browser.document.addEventListener("pointermove", handlePointerMove, true);
5972
6863
  browser.document.addEventListener("click", handleClick, true);
@@ -6018,6 +6909,7 @@ ${summary}`;
6018
6909
  if (selectionOpen && targets.length > 0) {
6019
6910
  transition({ type: "RESTORE" });
6020
6911
  agentWorkflow.beginSelection();
6912
+ contextualAskWorkflow?.beginSelection();
6021
6913
  workflowSelectionActive = true;
6022
6914
  refreshSelectionView(true);
6023
6915
  }
@@ -6034,6 +6926,7 @@ ${summary}`;
6034
6926
  api.dispose();
6035
6927
  agentWorkflow.disposeSelection();
6036
6928
  externalHandoffWorkflow?.dispose();
6929
+ contextualAskWorkflow?.dispose();
6037
6930
  sourceResolver.dispose();
6038
6931
  return;
6039
6932
  }
@@ -6098,6 +6991,7 @@ ${summary}`;
6098
6991
  api.dispose();
6099
6992
  agentWorkflow.disposeSelection();
6100
6993
  externalHandoffWorkflow?.dispose();
6994
+ contextualAskWorkflow?.dispose();
6101
6995
  sourceResolver.dispose();
6102
6996
  view.dispose();
6103
6997
  state = INITIAL_RUNTIME_STATE;