@norman-else/dsh-claude 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -4,13 +4,14 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  module.exports;
6
6
  var { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } = require("react");
7
- var { DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFullscreenOutline16, IconQueueOutline14, IconRefreshOutline14, IconSearchOutline16, IconSendOutline14, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
7
+ var { DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFullscreenOutline16, IconQueueOutline14, IconRefreshOutline14, IconRightUpOutline14, IconSearchOutline16, IconSendOutline14, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Toast, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
8
8
  var { Fragment: Fragment$1, jsx, jsxs } = require("react/jsx-runtime");
9
9
  var { createPortal } = require("react-dom");
10
10
  /** Claude's subagent dispatch tools; rendered as plugin-owned group cards
11
11
  * gathering subagent activity instead of native tool cards. */
12
12
  const TASK_TOOL_NAMES = /* @__PURE__ */ new Set(["Task", "Agent"]);
13
13
  const CLAUDE_DOCTOR_PATH = "/plugins/dsh-claude/doctor";
14
+ const CLAUDE_CLIENT_DIAGNOSTICS_PATH = "/plugins/dsh-claude/client-diagnostics";
14
15
  const CLAUDE_UPDATE_CHECK_PATH = "/plugins/dsh-claude/update/check";
15
16
  const CLAUDE_USAGE_PATH = "/plugins/dsh-claude/usage";
16
17
  const CLAUDE_UPDATE_PATH = "/plugins/dsh-claude/update";
@@ -1551,7 +1552,7 @@ window.__ModuleLoader__.load({
1551
1552
  };
1552
1553
  const repositoryBarFrame = {
1553
1554
  width: "calc(100% - 64px)",
1554
- maxWidth: "var(--dsh-conversation-composer-max-width, 782px)",
1555
+ maxWidth: "var(--dsh-composer-card-max-width, 782px)",
1555
1556
  minWidth: 0,
1556
1557
  margin: "0 auto",
1557
1558
  boxSizing: "border-box"
@@ -2063,9 +2064,11 @@ window.__ModuleLoader__.load({
2063
2064
  fontWeight: 600
2064
2065
  };
2065
2066
  const diffGhCommentLink = {
2066
- color: "var(--dsw-static-blue-450)",
2067
- textDecoration: "none",
2068
- fontWeight: 650
2067
+ display: "inline-flex",
2068
+ alignItems: "center",
2069
+ flex: "none",
2070
+ color: "var(--dsw-alias-label-tertiary)",
2071
+ textDecoration: "none"
2069
2072
  };
2070
2073
  const diffModalConflicts = {
2071
2074
  display: "flex",
@@ -2538,6 +2541,156 @@ window.__ModuleLoader__.load({
2538
2541
  font: "inherit",
2539
2542
  resize: "vertical"
2540
2543
  };
2544
+ const diffGhComment = {
2545
+ display: "flex",
2546
+ flexDirection: "column",
2547
+ gap: 4
2548
+ };
2549
+ /** Every comment after the first opens with a rule, the way GitHub separates
2550
+ * the entries of one conversation. */
2551
+ const diffGhCommentNext = {
2552
+ marginTop: 8,
2553
+ paddingTop: 8,
2554
+ borderTop: "1px solid var(--dsw-alias-border-l1, color-mix(in srgb, currentColor 12%, transparent))"
2555
+ };
2556
+ const diffGhCommentAge = {
2557
+ flex: "none",
2558
+ color: "var(--dsw-alias-label-tertiary)",
2559
+ fontSize: 11
2560
+ };
2561
+ /** Big enough to aim at: the 11px "›" glyph was a hard target. */
2562
+ const diffFileChevron = {
2563
+ width: 14,
2564
+ height: 14,
2565
+ display: "inline-flex",
2566
+ alignItems: "center",
2567
+ justifyContent: "center",
2568
+ flex: "none",
2569
+ color: "var(--dsw-alias-label-secondary)",
2570
+ transition: "transform 120ms ease"
2571
+ };
2572
+ const diffSummaryAction = {
2573
+ marginLeft: "auto",
2574
+ padding: "2px 8px",
2575
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2576
+ borderRadius: 6,
2577
+ background: "transparent",
2578
+ color: "var(--dsw-alias-label-secondary)",
2579
+ font: "inherit",
2580
+ fontSize: 12,
2581
+ cursor: "pointer"
2582
+ };
2583
+ const diffCommentNav = {
2584
+ display: "inline-flex",
2585
+ alignItems: "center",
2586
+ gap: 2
2587
+ };
2588
+ const diffCommentNavCount = {
2589
+ minWidth: 34,
2590
+ textAlign: "center",
2591
+ color: "var(--dsw-alias-label-tertiary)",
2592
+ fontSize: 12,
2593
+ fontVariantNumeric: "tabular-nums"
2594
+ };
2595
+ /** Where the walk stopped, so the eye lands without hunting for it. */
2596
+ const diffCommentBlockActive = {
2597
+ borderColor: "var(--dsw-static-blue-450)",
2598
+ boxShadow: "0 0 0 1px var(--dsw-static-blue-450)"
2599
+ };
2600
+ const diffThreadBadges = {
2601
+ display: "flex",
2602
+ gap: 6
2603
+ };
2604
+ const diffThreadBadge = {
2605
+ flex: "none",
2606
+ padding: "0 6px",
2607
+ borderRadius: 999,
2608
+ background: "var(--dsw-alias-bg-layer-2)",
2609
+ color: "var(--dsw-alias-label-tertiary)",
2610
+ fontSize: 11,
2611
+ lineHeight: "18px"
2612
+ };
2613
+ const diffThreadSummary = {
2614
+ width: "100%",
2615
+ display: "flex",
2616
+ alignItems: "center",
2617
+ gap: 6,
2618
+ minWidth: 0,
2619
+ padding: 0,
2620
+ border: "none",
2621
+ background: "transparent",
2622
+ color: "var(--dsw-alias-label-tertiary)",
2623
+ font: "inherit",
2624
+ fontSize: 12,
2625
+ textAlign: "left",
2626
+ cursor: "pointer"
2627
+ };
2628
+ const diffThreadExcerpt = {
2629
+ minWidth: 0,
2630
+ flex: 1,
2631
+ overflow: "hidden",
2632
+ textOverflow: "ellipsis",
2633
+ whiteSpace: "nowrap"
2634
+ };
2635
+ /** The composer is a region of its own: without the rule it reads as more of
2636
+ * the comment it sits under. */
2637
+ const diffThreadComposer = {
2638
+ display: "flex",
2639
+ flexDirection: "column",
2640
+ gap: 6,
2641
+ marginTop: 10,
2642
+ paddingTop: 10,
2643
+ borderTop: "1px solid var(--dsw-alias-border-l1, color-mix(in srgb, currentColor 12%, transparent))"
2644
+ };
2645
+ const diffThreadComposerCaption = {
2646
+ color: "var(--dsw-alias-label-tertiary)",
2647
+ fontSize: 11,
2648
+ lineHeight: "16px"
2649
+ };
2650
+ const mentionField = {
2651
+ position: "relative",
2652
+ display: "block"
2653
+ };
2654
+ const mentionList = {
2655
+ position: "absolute",
2656
+ zIndex: 1100,
2657
+ top: "100%",
2658
+ left: 0,
2659
+ width: 240,
2660
+ maxHeight: 176,
2661
+ boxSizing: "border-box",
2662
+ display: "flex",
2663
+ flexDirection: "column",
2664
+ gap: 2,
2665
+ padding: 4,
2666
+ overflowY: "auto",
2667
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2668
+ borderRadius: 8,
2669
+ background: "var(--dsw-specific-menu)",
2670
+ boxShadow: "var(--dsw-shadow-lv3)"
2671
+ };
2672
+ const mentionItem = {
2673
+ height: 28,
2674
+ display: "flex",
2675
+ alignItems: "center",
2676
+ gap: 6,
2677
+ padding: "0 8px",
2678
+ border: "none",
2679
+ borderRadius: 6,
2680
+ background: "transparent",
2681
+ color: "var(--dsw-alias-label-primary)",
2682
+ font: "inherit",
2683
+ fontSize: 13,
2684
+ textAlign: "left",
2685
+ cursor: "pointer"
2686
+ };
2687
+ const mentionItemActive = { background: "var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2))" };
2688
+ const mentionAvatar = {
2689
+ width: 16,
2690
+ height: 16,
2691
+ flex: "none",
2692
+ borderRadius: "50%"
2693
+ };
2541
2694
  const diffCommentActions = {
2542
2695
  display: "flex",
2543
2696
  justifyContent: "flex-end",
@@ -2568,7 +2721,7 @@ window.__ModuleLoader__.load({
2568
2721
  };
2569
2722
  const reviewCommentBarFrame = {
2570
2723
  width: "calc(100% - 64px)",
2571
- maxWidth: "var(--dsh-conversation-composer-max-width, 782px)",
2724
+ maxWidth: "var(--dsh-composer-card-max-width, 782px)",
2572
2725
  margin: "0 auto",
2573
2726
  boxSizing: "border-box"
2574
2727
  };
@@ -2987,12 +3140,12 @@ window.__ModuleLoader__.load({
2987
3140
  background: var(--dsw-alias-bg-layer-2);
2988
3141
  }
2989
3142
  .dshClaudeRepositoryActionModalContent h2 {
2990
- font-size: 18px;
2991
- line-height: 26px;
3143
+ font-size: 16px;
3144
+ line-height: 24px;
2992
3145
  }
2993
3146
  .dshClaudeRepositoryActionModalContent > p {
2994
- font-size: 14px;
2995
- line-height: 22px;
3147
+ font-size: 13px;
3148
+ line-height: 20px;
2996
3149
  }
2997
3150
  .dshClaudeRepositoryActionModal input:is([type='checkbox'], [type='radio']) {
2998
3151
  width: 16px;
@@ -3009,7 +3162,7 @@ window.__ModuleLoader__.load({
3009
3162
  const diffModalBody = {
3010
3163
  display: "flex",
3011
3164
  flexDirection: "column",
3012
- gap: 16,
3165
+ gap: 14,
3013
3166
  width: "100%",
3014
3167
  minWidth: 0,
3015
3168
  boxSizing: "border-box"
@@ -3020,8 +3173,8 @@ window.__ModuleLoader__.load({
3020
3173
  gap: 16,
3021
3174
  minWidth: 0,
3022
3175
  color: "var(--dsw-alias-label-secondary)",
3023
- fontSize: 14,
3024
- lineHeight: "22px"
3176
+ fontSize: 13,
3177
+ lineHeight: "20px"
3025
3178
  };
3026
3179
  const diffModalMetaText = {
3027
3180
  minWidth: 0,
@@ -3042,11 +3195,11 @@ window.__ModuleLoader__.load({
3042
3195
  justifyContent: "space-between",
3043
3196
  gap: 12,
3044
3197
  minWidth: 0,
3045
- padding: "10px 14px",
3198
+ padding: "8px 12px",
3046
3199
  borderBottom: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
3047
3200
  color: "var(--dsw-alias-label-secondary)",
3048
- fontSize: 13,
3049
- lineHeight: "20px"
3201
+ fontSize: 12,
3202
+ lineHeight: "18px"
3050
3203
  };
3051
3204
  const diffModalFilePath = {
3052
3205
  minWidth: 0,
@@ -3058,34 +3211,34 @@ window.__ModuleLoader__.load({
3058
3211
  const diffModalField = {
3059
3212
  display: "flex",
3060
3213
  flexDirection: "column",
3061
- gap: 8,
3214
+ gap: 6,
3062
3215
  color: "var(--dsw-alias-label-secondary)",
3063
- fontSize: 14,
3064
- lineHeight: "22px",
3216
+ fontSize: 12,
3217
+ lineHeight: "18px",
3065
3218
  fontWeight: 550
3066
3219
  };
3067
3220
  const diffModalTextarea = {
3068
3221
  ...settingTextInput,
3069
- minHeight: 140,
3070
- padding: "10px 14px",
3071
- fontSize: 14,
3072
- lineHeight: "22px",
3222
+ minHeight: 120,
3223
+ padding: "9px 12px",
3224
+ fontSize: 13,
3225
+ lineHeight: "20px",
3073
3226
  resize: "vertical"
3074
3227
  };
3075
3228
  const diffModalTextInput = {
3076
3229
  ...settingTextInput,
3077
- minHeight: 44,
3078
- padding: "9px 14px",
3079
- fontSize: 14,
3080
- lineHeight: "22px"
3230
+ minHeight: 36,
3231
+ padding: "7px 12px",
3232
+ fontSize: 13,
3233
+ lineHeight: "20px"
3081
3234
  };
3082
3235
  const diffModalCheckbox = {
3083
3236
  display: "flex",
3084
3237
  alignItems: "center",
3085
- gap: 10,
3238
+ gap: 8,
3086
3239
  color: "var(--dsw-alias-label-secondary)",
3087
- fontSize: 14,
3088
- lineHeight: "22px"
3240
+ fontSize: 13,
3241
+ lineHeight: "20px"
3089
3242
  };
3090
3243
  const diffModalFooter = {
3091
3244
  display: "flex",
@@ -3093,28 +3246,22 @@ window.__ModuleLoader__.load({
3093
3246
  gap: 12
3094
3247
  };
3095
3248
  const diffModalButton = {
3096
- minHeight: 36,
3097
- padding: "8px 18px",
3098
- fontSize: 14,
3099
- lineHeight: "22px"
3249
+ minHeight: 34,
3250
+ padding: "6px 16px",
3251
+ fontSize: 13,
3252
+ lineHeight: "20px"
3100
3253
  };
3101
3254
  const diffModalStatus = {
3102
3255
  margin: 0,
3103
3256
  color: "var(--dsw-alias-label-secondary)",
3104
- fontSize: 14,
3105
- lineHeight: "22px"
3257
+ fontSize: 13,
3258
+ lineHeight: "20px"
3106
3259
  };
3107
3260
  const diffModalError = {
3108
- margin: "10px 0 0",
3261
+ margin: "8px 0 0",
3109
3262
  color: "var(--dsw-alias-state-error-primary)",
3110
- fontSize: 14,
3111
- lineHeight: "22px"
3112
- };
3113
- const diffModalSuccess = {
3114
- margin: 0,
3115
- color: "var(--dsw-alias-state-success-primary)",
3116
- fontSize: 14,
3117
- lineHeight: "22px"
3263
+ fontSize: 13,
3264
+ lineHeight: "20px"
3118
3265
  };
3119
3266
  const rewindActionCss = `
3120
3267
  [data-dsh-claude-rewind]:empty {
@@ -3589,6 +3736,32 @@ window.__ModuleLoader__.load({
3589
3736
  });
3590
3737
  }
3591
3738
  //#endregion
3739
+ //#region src/client/markdown-labels.tsx
3740
+ /** Stable across renders: MarkdownText keys its streaming renderer on this
3741
+ * object's identity and reparses from scratch whenever it changes. */
3742
+ function useClaudeMarkdownLabels(t) {
3743
+ return useMemo(() => ({
3744
+ code: {
3745
+ copyLabel: t("markdownCopy"),
3746
+ copiedLabel: t("markdownCopied")
3747
+ },
3748
+ footnotes: t("markdownFootnotes")
3749
+ }), [t]);
3750
+ }
3751
+ /** MarkdownText with the labels the running Host demands.
3752
+ *
3753
+ * The published primitives package still types the old `codeLabels` prop, so
3754
+ * the new shape cannot typecheck against it — the cast is confined here rather
3755
+ * than repeated at every call site. Drop it once the installed
3756
+ * @deepseek-ai/dsh-client-ui-primitives matches the Desktop build. */
3757
+ function ClaudeMarkdown({ text, labels, streaming }) {
3758
+ return /* @__PURE__ */ jsx(MarkdownText, {
3759
+ text,
3760
+ labels,
3761
+ ...streaming === void 0 ? {} : { streaming }
3762
+ });
3763
+ }
3764
+ //#endregion
3592
3765
  //#region src/rewind.ts
3593
3766
  function isRewound(ranges, seq) {
3594
3767
  return ranges.some((range) => seq >= range.start && seq <= range.end);
@@ -4429,12 +4602,16 @@ window.__ModuleLoader__.load({
4429
4602
  marker.turn,
4430
4603
  tasks
4431
4604
  ]);
4605
+ const markdownLabels = useClaudeMarkdownLabels(t);
4432
4606
  if (items.length === 0) return null;
4433
4607
  return /* @__PURE__ */ jsx("div", {
4434
4608
  className: "dsh-claude-flow",
4435
4609
  children: items.map((item) => item.kind === "text" ? /* @__PURE__ */ jsx("div", {
4436
4610
  className: "dsh-claude-transcript-text",
4437
- children: /* @__PURE__ */ jsx(MarkdownText, { text: item.text })
4611
+ children: /* @__PURE__ */ jsx(ClaudeMarkdown, {
4612
+ text: item.text,
4613
+ labels: markdownLabels
4614
+ })
4438
4615
  }, `text:${item.ordinal}`) : item.kind === "compaction" ? /* @__PURE__ */ jsx(ClaudeCompactionDivider, {
4439
4616
  compaction: item.compaction,
4440
4617
  t
@@ -5486,6 +5663,36 @@ window.__ModuleLoader__.load({
5486
5663
  })).then(result);
5487
5664
  }
5488
5665
  //#endregion
5666
+ //#region src/client/action-toast.tsx
5667
+ /** Always a new id, even for the same text: the id is the Toast's key, and
5668
+ * reporting the same outcome twice has to replay the banner rather than
5669
+ * update the text of one that is already fading out. */
5670
+ function nextActionToast(current, text) {
5671
+ return {
5672
+ id: (current?.id ?? 0) + 1,
5673
+ text
5674
+ };
5675
+ }
5676
+ /** Report a finished Git action the way the Host reports everything else: a
5677
+ * banner over the window, not a line inside the dialog that asked for it. The
5678
+ * dialog closes the moment the work lands, so the notice has to outlive it. */
5679
+ function useActionToast() {
5680
+ const [shown, setShown] = useState();
5681
+ const done = useCallback(() => {
5682
+ setShown(void 0);
5683
+ }, []);
5684
+ const report = useCallback((text) => {
5685
+ setShown((current) => nextActionToast(current, text));
5686
+ }, []);
5687
+ return {
5688
+ toast: shown === void 0 ? null : /* @__PURE__ */ jsx(Toast, {
5689
+ text: shown.text,
5690
+ onDone: done
5691
+ }, shown.id),
5692
+ report
5693
+ };
5694
+ }
5695
+ //#endregion
5489
5696
  //#region src/client/repository-setup-api.ts
5490
5697
  const HOST_STAGES = /* @__PURE__ */ new Set([
5491
5698
  "inspecting",
@@ -5657,6 +5864,20 @@ window.__ModuleLoader__.load({
5657
5864
  return body;
5658
5865
  }
5659
5866
  //#endregion
5867
+ //#region src/client/relative-age.ts
5868
+ /** Compact age of an ISO timestamp, in the shape the panels already use
5869
+ * ("<1h", "4h", "3d", "2mo"). `now` is a parameter so callers that re-render
5870
+ * on a clock — and tests — stay deterministic. */
5871
+ function relativeAge(value, now = Date.now()) {
5872
+ if (value === void 0) return void 0;
5873
+ const elapsedHours = Math.max(0, Math.floor((now - Date.parse(value)) / 36e5));
5874
+ if (!Number.isFinite(elapsedHours)) return void 0;
5875
+ if (elapsedHours < 1) return "<1h";
5876
+ if (elapsedHours < 24) return `${elapsedHours}h`;
5877
+ const days = Math.floor(elapsedHours / 24);
5878
+ return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
5879
+ }
5880
+ //#endregion
5660
5881
  //#region src/github-url.ts
5661
5882
  /** Only GitHub's own image hosts; the browser loads these directly, so a URL
5662
5883
  * the API did not vouch for must never become an outbound request. */
@@ -5675,28 +5896,95 @@ window.__ModuleLoader__.load({
5675
5896
  function record$2(value) {
5676
5897
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5677
5898
  }
5678
- async function loadJson(path, sessionId, pullNumber, signal) {
5679
- const response = await fetch(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}?sessionId=${encodeURIComponent(sessionId)}&number=${pullNumber}`, {
5680
- method: "GET",
5681
- credentials: "same-origin",
5682
- headers: { accept: "application/json" },
5683
- signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS, signal)
5684
- });
5899
+ function feedbackUrl(path, sessionId, pullNumber, extra = "") {
5900
+ return `${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}?sessionId=${encodeURIComponent(sessionId)}&number=${pullNumber}${extra}`;
5901
+ }
5902
+ async function answer(response) {
5685
5903
  const body = record$2(await response.json());
5686
5904
  if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
5687
5905
  if (body === void 0) throw new Error("Invalid pull request feedback response.");
5688
5906
  return body;
5689
5907
  }
5690
- async function loadPullRequestComments(sessionId, pullNumber, signal) {
5908
+ async function loadJson(path, sessionId, pullNumber, signal, extra = "") {
5909
+ return answer(await fetch(feedbackUrl(path, sessionId, pullNumber, extra), {
5910
+ method: "GET",
5911
+ credentials: "same-origin",
5912
+ headers: { accept: "application/json" },
5913
+ signal: pluginRequestSignal(PLUGIN_READ_TIMEOUT_MS, signal)
5914
+ }));
5915
+ }
5916
+ /** Writes reach GitHub through `gh`, so they get the action deadline. */
5917
+ async function postJson(path, sessionId, pullNumber, input) {
5918
+ return answer(await fetch(feedbackUrl(path, sessionId, pullNumber), {
5919
+ method: "POST",
5920
+ credentials: "same-origin",
5921
+ headers: {
5922
+ accept: "application/json",
5923
+ "content-type": "application/json"
5924
+ },
5925
+ signal: pluginRequestSignal(PLUGIN_ACTION_TIMEOUT_MS),
5926
+ body: JSON.stringify(input)
5927
+ }));
5928
+ }
5929
+ function reviewComment(value) {
5930
+ const input = record$2(value);
5931
+ if (input === void 0 || typeof input.id !== "number" || typeof input.path !== "string" || typeof input.author !== "string" || typeof input.body !== "string" || typeof input.url !== "string" || input.avatarUrl !== void 0 && githubAvatarUrl(input.avatarUrl) === void 0 || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || input.createdAt !== void 0 && typeof input.createdAt !== "string" || input.bot !== void 0 && typeof input.bot !== "boolean") return void 0;
5932
+ return input;
5933
+ }
5934
+ async function loadPullRequestThreads(sessionId, pullNumber, signal) {
5691
5935
  const body = await loadJson("/comments", sessionId, pullNumber, signal);
5692
- if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
5693
- const comments = [];
5694
- for (const item of body.comments) {
5936
+ if (!Array.isArray(body.threads)) throw new Error("Invalid pull request comments response.");
5937
+ const threads = [];
5938
+ for (const item of body.threads) {
5695
5939
  const input = record$2(item);
5696
- if (input === void 0 || typeof input.id !== "number" || typeof input.path !== "string" || typeof input.author !== "string" || typeof input.body !== "string" || typeof input.url !== "string" || input.avatarUrl !== void 0 && githubAvatarUrl(input.avatarUrl) === void 0 || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number") continue;
5697
- comments.push(input);
5940
+ if (input === void 0 || typeof input.id !== "string" || typeof input.path !== "string" || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || !Array.isArray(input.comments)) continue;
5941
+ const comments = input.comments.map(reviewComment).filter((value) => value !== void 0);
5942
+ if (comments.length === 0) continue;
5943
+ threads.push({
5944
+ id: input.id,
5945
+ path: input.path,
5946
+ ...typeof input.line === "number" ? { line: input.line } : {},
5947
+ side: input.side,
5948
+ resolved: input.resolved === true,
5949
+ outdated: input.outdated === true,
5950
+ comments
5951
+ });
5952
+ }
5953
+ return threads;
5954
+ }
5955
+ /** Post one reply into the thread that `commentId` belongs to. */
5956
+ async function replyToReviewThread(sessionId, pullNumber, commentId, body) {
5957
+ const comment = reviewComment((await postJson("/reply", sessionId, pullNumber, {
5958
+ commentId,
5959
+ body
5960
+ })).comment);
5961
+ if (comment === void 0) throw new Error("Invalid pull request reply response.");
5962
+ return comment;
5963
+ }
5964
+ /** Resolve or reopen a thread; returns the state GitHub reports afterwards. */
5965
+ async function setReviewThreadResolved(sessionId, pullNumber, threadId, resolved) {
5966
+ const answer = await postJson("/resolve", sessionId, pullNumber, {
5967
+ threadId,
5968
+ resolved
5969
+ });
5970
+ if (typeof answer.resolved !== "boolean") throw new Error("Invalid pull request resolve response.");
5971
+ return answer.resolved;
5972
+ }
5973
+ /** Logins GitHub would notify, for the reply composer's `@` completion. */
5974
+ async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
5975
+ const body = await loadJson("/mentionables", sessionId, pullNumber, signal, `&q=${encodeURIComponent(query)}`);
5976
+ if (!Array.isArray(body.users)) return [];
5977
+ const users = [];
5978
+ for (const item of body.users) {
5979
+ const input = record$2(item);
5980
+ if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
5981
+ const avatarUrl = githubAvatarUrl(input.avatarUrl);
5982
+ users.push({
5983
+ login: input.login,
5984
+ ...avatarUrl === void 0 ? {} : { avatarUrl }
5985
+ });
5698
5986
  }
5699
- return comments;
5987
+ return users;
5700
5988
  }
5701
5989
  async function loadFailingChecks(sessionId, pullNumber, signal) {
5702
5990
  const body = await loadJson("/checks", sessionId, pullNumber, signal);
@@ -5709,9 +5997,17 @@ window.__ModuleLoader__.load({
5709
5997
  }
5710
5998
  return checks;
5711
5999
  }
5712
- /** Draft handed to Claude when the user forwards GitHub review comments. */
5713
- function composeCommentsPrompt(comments) {
5714
- return `Please address the following GitHub pull request review comments. Make the requested changes, or explain briefly when a comment should not be applied.\n\n${comments.map((comment) => `- ${comment.path}${comment.line === void 0 ? "" : `:${comment.line}`} (@${comment.author}): ${comment.body.replaceAll("\n", "\n ")}`).join("\n")}`;
6000
+ /** Draft handed to Claude when the user forwards GitHub review comments. A
6001
+ * resolved thread is a settled conversation: forwarding it would ask Claude to
6002
+ * redo work the reviewers already signed off. */
6003
+ function composeCommentsPrompt(threads) {
6004
+ const open = threads.filter((thread) => !thread.resolved);
6005
+ if (open.length === 0) return "";
6006
+ return `Please address the following GitHub pull request review comments. Make the requested changes, or explain briefly when a comment should not be applied.\n\n${open.map((thread) => {
6007
+ const [first, ...rest] = thread.comments;
6008
+ if (first === void 0) return "";
6009
+ return [`- ${`${thread.path}${thread.line === void 0 ? "" : `:${thread.line}`}`} (@${first.author}): ${first.body.replaceAll("\n", "\n ")}`, ...rest.map((reply) => ` (@${reply.author}): ${reply.body.replaceAll("\n", "\n ")}`)].join("\n");
6010
+ }).filter((block) => block.length > 0).join("\n")}`;
5715
6011
  }
5716
6012
  /** Draft handed to Claude when the user forwards failing CI checks. */
5717
6013
  function composeChecksPrompt(checks) {
@@ -5759,12 +6055,13 @@ window.__ModuleLoader__.load({
5759
6055
  if (checks.length === 0) return void 0;
5760
6056
  return checks.map((check) => `${check.name}|${check.link ?? ""}`).sort().join("\n");
5761
6057
  }
5762
- function planAutoFix(memory, comments, checks) {
5763
- const fresh = comments.filter((comment) => !memory.handledCommentIds.has(comment.id));
6058
+ function planAutoFix(memory, threads, checks) {
6059
+ const unhandled = threads.filter((thread) => !thread.resolved).filter((thread) => thread.comments.some((comment) => !memory.handledCommentIds.has(comment.id)));
6060
+ const fresh = unhandled.flatMap((thread) => thread.comments);
5764
6061
  const signature = checksSignature(checks);
5765
6062
  const checksChanged = signature !== void 0 && signature !== memory.handledChecksSignature;
5766
6063
  const sections = [];
5767
- if (fresh.length > 0) sections.push(composeCommentsPrompt(fresh));
6064
+ if (unhandled.length > 0) sections.push(composeCommentsPrompt(unhandled));
5768
6065
  if (checksChanged) sections.push(composeChecksPrompt(checks));
5769
6066
  if (sections.length === 0) return { memory };
5770
6067
  const nextSignature = checksChanged ? signature : memory.handledChecksSignature;
@@ -5777,6 +6074,47 @@ window.__ModuleLoader__.load({
5777
6074
  };
5778
6075
  }
5779
6076
  //#endregion
6077
+ //#region src/client/boot-check.ts
6078
+ /** Boot-time assertions against the running Host.
6079
+ *
6080
+ * The Desktop build ships no type declarations and several of its client
6081
+ * packages are unpublished, so `tsc` validates this plugin against whatever
6082
+ * @deepseek-ai/* versions happen to be installed — never against the Host it
6083
+ * will actually run inside. Every Desktop 2.0 breakage found so far was
6084
+ * invisible at compile time AND at runtime: a missing service leaves its
6085
+ * guarded registration silently unregistered, and a renamed custom property
6086
+ * just freezes a style on its fallback. These checks make that drift say so. */
6087
+ /** Host-owned custom properties the styles read, and where each is scoped.
6088
+ *
6089
+ * `var(--x, fallback)` cannot distinguish "the Host stopped publishing this"
6090
+ * from "the Host says this", so anything listed here needs an explicit probe.
6091
+ * The Host scopes these to a subtree rather than `:root` — custom properties
6092
+ * inherit, so only an element inside that subtree can see one, and probing
6093
+ * document.documentElement reports every one of them missing. Each entry
6094
+ * therefore names the mounted element that must be measured instead.
6095
+ *
6096
+ * Plugin-owned properties (--dsh-claude-*) are set by this package and are
6097
+ * deliberately absent from this list. */
6098
+ const CLAUDE_SCOPED_CSS_VARIABLES = ["--dsh-composer-card-max-width"];
6099
+ /** Marks the bar the scoped-property probe measures. */
6100
+ const CLAUDE_COMPOSER_BAR_ATTRIBUTE = "data-dsh-claude-composer-bar";
6101
+ /** One line per service the Host no longer provides; empty when all resolve. */
6102
+ function claudeBootCheckFindings(input) {
6103
+ const findings = [];
6104
+ for (const name of input.services) if (input.resolve(name) === void 0) findings.push(`service "${name}" is declared in inject but the Host does not provide it`);
6105
+ return findings;
6106
+ }
6107
+ /** One line per scoped custom property the measured element cannot see.
6108
+ *
6109
+ * @param read - computed value of one property ON THE MOUNTED BAR, not on the
6110
+ * document root: these properties are published to a subtree, so reading them
6111
+ * anywhere above it returns '' whether or not the Host still defines them. */
6112
+ function claudeScopedCssFindings(read, names = CLAUDE_SCOPED_CSS_VARIABLES) {
6113
+ const findings = [];
6114
+ for (const name of names) if (read(name).trim() === "") findings.push(`CSS custom property "${name}" is not visible to the composer bar; styles reading it are stuck on their fallback`);
6115
+ return findings;
6116
+ }
6117
+ //#endregion
5780
6118
  //#region src/client/ClaudeRepositoryStatus.tsx
5781
6119
  /** Icon-only status: the tone carries the state, the full label lives in the tooltip and accessible name. */
5782
6120
  function StatusGlyph({ label, tone, children }) {
@@ -5900,15 +6238,6 @@ window.__ModuleLoader__.load({
5900
6238
  function repositoryName$1(remote) {
5901
6239
  return remote?.split("/").at(-1);
5902
6240
  }
5903
- function relativeAge(value) {
5904
- if (value === void 0) return void 0;
5905
- const elapsedHours = Math.max(0, Math.floor((Date.now() - Date.parse(value)) / 36e5));
5906
- if (!Number.isFinite(elapsedHours)) return void 0;
5907
- if (elapsedHours < 1) return "<1h";
5908
- if (elapsedHours < 24) return `${elapsedHours}h`;
5909
- const days = Math.floor(elapsedHours / 24);
5910
- return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
5911
- }
5912
6241
  function PullRequestHoverCard({ repository, t }) {
5913
6242
  const pullRequest = repository.pullRequest;
5914
6243
  if (pullRequest === void 0) return null;
@@ -6046,7 +6375,7 @@ window.__ModuleLoader__.load({
6046
6375
  if (!enabled || !open || running || number === void 0 || submitPrompt === void 0) return;
6047
6376
  let cancelled = false;
6048
6377
  const tick = async () => {
6049
- const [comments, failing] = await Promise.all([loadPullRequestComments(sessionId, number).catch(() => []), checks === "failing" ? loadFailingChecks(sessionId, number).catch(() => []) : Promise.resolve([])]);
6378
+ const [comments, failing] = await Promise.all([loadPullRequestThreads(sessionId, number).catch(() => []), checks === "failing" ? loadFailingChecks(sessionId, number).catch(() => []) : Promise.resolve([])]);
6050
6379
  if (cancelled) return;
6051
6380
  const plan = planAutoFix(autoFixMemory(sessionId), comments, failing);
6052
6381
  if (plan.prompt !== void 0 && submitPrompt(plan.prompt, "idle")) rememberAutoFix(sessionId, plan.memory);
@@ -6216,7 +6545,7 @@ window.__ModuleLoader__.load({
6216
6545
  }) : null]
6217
6546
  });
6218
6547
  }
6219
- function UpdateBranchControl({ sessionId, repository, t, submitPrompt }) {
6548
+ function UpdateBranchControl({ sessionId, repository, t, report, submitPrompt }) {
6220
6549
  const [dialog, setDialog] = useState();
6221
6550
  const [method, setMethod] = useState("rebase");
6222
6551
  const controller = useRef();
@@ -6224,11 +6553,6 @@ window.__ModuleLoader__.load({
6224
6553
  const base = pullRequest?.baseBranch;
6225
6554
  const behind = repository.baseBehind ?? 0;
6226
6555
  useEffect(() => () => controller.current?.abort(), []);
6227
- useEffect(() => {
6228
- if (dialog?.pushed === void 0 || dialog.error !== void 0) return;
6229
- const timer = setTimeout(() => setDialog(void 0), 1200);
6230
- return () => clearTimeout(timer);
6231
- }, [dialog?.error, dialog?.pushed]);
6232
6556
  if (pullRequest === void 0 || pullRequest.state !== "open" || base === void 0 || repository.detached === true || repository.dirty === true || behind <= 0) return null;
6233
6557
  const openDialog = () => {
6234
6558
  controller.current?.abort();
@@ -6273,11 +6597,16 @@ window.__ModuleLoader__.load({
6273
6597
  baseBranch: base,
6274
6598
  mergeMethod: method
6275
6599
  }).then((result) => {
6276
- setDialog({
6277
- ...pending,
6278
- submitting: false,
6279
- ...result.conflicts !== void 0 && result.conflicts.length > 0 ? { conflicts: result.conflicts } : { pushed: result.commit }
6280
- });
6600
+ if (result.conflicts !== void 0 && result.conflicts.length > 0) {
6601
+ setDialog({
6602
+ ...pending,
6603
+ submitting: false,
6604
+ conflicts: result.conflicts
6605
+ });
6606
+ return;
6607
+ }
6608
+ report(t("diffUpdateBranchCompleted", { commit: result.commit.slice(0, 8) }));
6609
+ setDialog(void 0);
6281
6610
  }, (reason) => {
6282
6611
  setDialog({
6283
6612
  ...pending,
@@ -6286,7 +6615,7 @@ window.__ModuleLoader__.load({
6286
6615
  });
6287
6616
  });
6288
6617
  };
6289
- const settled = dialog?.pushed !== void 0 || dialog?.conflicts !== void 0;
6618
+ const settled = dialog?.conflicts !== void 0;
6290
6619
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6291
6620
  /* @__PURE__ */ jsxs("button", {
6292
6621
  type: "button",
@@ -6365,10 +6694,6 @@ window.__ModuleLoader__.load({
6365
6694
  onChange: () => setMethod(option)
6366
6695
  }), t(`diffUpdateBranch_${option}`, { base })]
6367
6696
  }, option)),
6368
- dialog.pushed === void 0 ? null : /* @__PURE__ */ jsx("p", {
6369
- style: diffModalSuccess,
6370
- children: t("diffUpdateBranchCompleted", { commit: dialog.pushed.slice(0, 8) })
6371
- }),
6372
6697
  dialog.conflicts === void 0 ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [
6373
6698
  /* @__PURE__ */ jsx("p", {
6374
6699
  style: diffModalStatus,
@@ -6401,15 +6726,10 @@ window.__ModuleLoader__.load({
6401
6726
  /** After a merge: remove the worktree (or switch a plain checkout back to
6402
6727
  * base), delete the merged branch, and drop the DSH workspace along with the
6403
6728
  * sessions it held. */
6404
- function CleanupControl({ repository, t, deleteWorkspace }) {
6729
+ function CleanupControl({ repository, t, report, deleteWorkspace }) {
6405
6730
  const [dialog, setDialog] = useState();
6406
6731
  const pullRequest = repository.pullRequest;
6407
6732
  const base = pullRequest?.baseBranch;
6408
- useEffect(() => {
6409
- if (dialog?.done === void 0) return;
6410
- const timer = setTimeout(() => setDialog(void 0), 1500);
6411
- return () => clearTimeout(timer);
6412
- }, [dialog?.done]);
6413
6733
  if (pullRequest?.state !== "merged" || base === void 0 || repository.root === void 0 || repository.detached === true) return null;
6414
6734
  const root = repository.root;
6415
6735
  const closeDialog = () => {
@@ -6418,11 +6738,9 @@ window.__ModuleLoader__.load({
6418
6738
  const confirm = () => {
6419
6739
  setDialog({ submitting: true });
6420
6740
  cleanupMergedRepository(root, base).then(async (result) => {
6741
+ report(t("cleanupCompleted", { branch: result.branch }));
6742
+ setDialog(void 0);
6421
6743
  if (result.mode === "worktree" && deleteWorkspace !== void 0) await deleteWorkspace();
6422
- setDialog({
6423
- submitting: false,
6424
- done: result.branch
6425
- });
6426
6744
  }, (reason) => {
6427
6745
  setDialog({
6428
6746
  submitting: false,
@@ -6462,8 +6780,8 @@ window.__ModuleLoader__.load({
6462
6780
  },
6463
6781
  disabled: dialog?.submitting === true,
6464
6782
  onClick: closeDialog,
6465
- children: dialog?.done === void 0 ? t("diffCancel") : t("diffDone")
6466
- }), dialog?.done === void 0 ? /* @__PURE__ */ jsx("button", {
6783
+ children: t("diffCancel")
6784
+ }), /* @__PURE__ */ jsx("button", {
6467
6785
  type: "button",
6468
6786
  style: {
6469
6787
  ...primaryButton,
@@ -6472,35 +6790,28 @@ window.__ModuleLoader__.load({
6472
6790
  disabled: dialog?.submitting === true,
6473
6791
  onClick: confirm,
6474
6792
  children: dialog?.submitting === true ? t("diffSubmitting") : t("diffConfirm")
6475
- }) : null]
6793
+ })]
6476
6794
  }),
6477
6795
  children: dialog === void 0 ? null : /* @__PURE__ */ jsxs("div", {
6478
6796
  style: diffModalBody,
6479
- children: [
6480
- /* @__PURE__ */ jsxs("div", {
6481
- style: diffModalMeta,
6482
- children: [/* @__PURE__ */ jsxs("strong", {
6483
- style: diffModalMetaText,
6484
- children: [
6485
- repository.branch ?? t("repositoryUnknownBranch"),
6486
- " → ",
6487
- base
6488
- ]
6489
- }), /* @__PURE__ */ jsx("span", {
6490
- style: diffModalFileState,
6491
- children: repository.worktree === true ? t("repositoryWorktree") : t("repositoryLocal")
6492
- })]
6493
- }),
6494
- dialog.done === void 0 ? null : /* @__PURE__ */ jsx("p", {
6495
- style: diffModalSuccess,
6496
- children: t("cleanupCompleted", { branch: dialog.done })
6497
- }),
6498
- dialog.error === void 0 ? null : /* @__PURE__ */ jsx("p", {
6499
- role: "alert",
6500
- style: diffModalError,
6501
- children: dialog.error
6502
- })
6503
- ]
6797
+ children: [/* @__PURE__ */ jsxs("div", {
6798
+ style: diffModalMeta,
6799
+ children: [/* @__PURE__ */ jsxs("strong", {
6800
+ style: diffModalMetaText,
6801
+ children: [
6802
+ repository.branch ?? t("repositoryUnknownBranch"),
6803
+ " ",
6804
+ base
6805
+ ]
6806
+ }), /* @__PURE__ */ jsx("span", {
6807
+ style: diffModalFileState,
6808
+ children: repository.worktree === true ? t("repositoryWorktree") : t("repositoryLocal")
6809
+ })]
6810
+ }), dialog.error === void 0 ? null : /* @__PURE__ */ jsx("p", {
6811
+ role: "alert",
6812
+ style: diffModalError,
6813
+ children: dialog.error
6814
+ })]
6504
6815
  })
6505
6816
  })
6506
6817
  ] });
@@ -6510,17 +6821,12 @@ window.__ModuleLoader__.load({
6510
6821
  "squash",
6511
6822
  "rebase"
6512
6823
  ];
6513
- function MergePullRequestControl({ sessionId, repository, t }) {
6824
+ function MergePullRequestControl({ sessionId, repository, t, report }) {
6514
6825
  const [menuOpen, setMenuOpen] = useState(false);
6515
6826
  const [dialog, setDialog] = useState();
6516
6827
  const controller = useRef();
6517
6828
  const pullRequest = repository.pullRequest;
6518
6829
  useEffect(() => () => controller.current?.abort(), []);
6519
- useEffect(() => {
6520
- if (dialog?.merged !== true || dialog.error !== void 0) return;
6521
- const timer = setTimeout(() => setDialog(void 0), 1200);
6522
- return () => clearTimeout(timer);
6523
- }, [dialog?.error, dialog?.merged]);
6524
6830
  if (pullRequest === void 0 || pullRequest.state !== "open" || pullRequest.draft || repository.detached === true) return null;
6525
6831
  const openMerge = (method) => {
6526
6832
  controller.current?.abort();
@@ -6568,11 +6874,8 @@ window.__ModuleLoader__.load({
6568
6874
  includeUnstaged: false,
6569
6875
  mergeMethod: dialog.method
6570
6876
  }).then(() => {
6571
- setDialog({
6572
- ...pending,
6573
- submitting: false,
6574
- merged: true
6575
- });
6877
+ report(t("diffMergeCompleted", { number: pullRequest.number }));
6878
+ setDialog(void 0);
6576
6879
  }, (error) => {
6577
6880
  setDialog({
6578
6881
  ...pending,
@@ -6581,15 +6884,13 @@ window.__ModuleLoader__.load({
6581
6884
  });
6582
6885
  });
6583
6886
  };
6584
- const items = MERGE_METHODS.map((method) => ({
6585
- id: method,
6586
- label: t(`diffMerge_${method}`)
6587
- }));
6588
- const merged = dialog?.merged === true;
6589
6887
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6590
6888
  /* @__PURE__ */ jsx(Menu, {
6591
6889
  open: menuOpen,
6592
- items,
6890
+ items: MERGE_METHODS.map((method) => ({
6891
+ id: method,
6892
+ label: t(`diffMerge_${method}`)
6893
+ })),
6593
6894
  onSelect: (id) => openMerge(id),
6594
6895
  onClose: () => setMenuOpen(false),
6595
6896
  align: "end",
@@ -6626,8 +6927,8 @@ window.__ModuleLoader__.load({
6626
6927
  },
6627
6928
  disabled: dialog?.submitting === true,
6628
6929
  onClick: closeDialog,
6629
- children: merged ? t("diffDone") : t("diffCancel")
6630
- }), !merged ? /* @__PURE__ */ jsx("button", {
6930
+ children: t("diffCancel")
6931
+ }), /* @__PURE__ */ jsx("button", {
6631
6932
  type: "button",
6632
6933
  style: {
6633
6934
  ...primaryButton,
@@ -6636,7 +6937,7 @@ window.__ModuleLoader__.load({
6636
6937
  disabled: dialog?.loading === true || dialog?.submitting === true || dialog?.fingerprint === void 0,
6637
6938
  onClick: confirmMerge,
6638
6939
  children: dialog?.submitting === true ? t("diffSubmitting") : t("diffConfirm")
6639
- }) : null]
6940
+ })]
6640
6941
  }),
6641
6942
  children: dialog === void 0 ? null : /* @__PURE__ */ jsxs("div", {
6642
6943
  style: diffModalBody,
@@ -6660,10 +6961,6 @@ window.__ModuleLoader__.load({
6660
6961
  style: diffModalStatus,
6661
6962
  children: pullRequest.title
6662
6963
  }),
6663
- merged ? /* @__PURE__ */ jsx("p", {
6664
- style: diffModalSuccess,
6665
- children: t("diffMergeCompleted", { number: pullRequest.number })
6666
- }) : null,
6667
6964
  dialog.error === void 0 ? null : /* @__PURE__ */ jsx("p", {
6668
6965
  role: "alert",
6669
6966
  style: diffModalError,
@@ -6679,6 +6976,7 @@ window.__ModuleLoader__.load({
6679
6976
  const running = useSessions((value) => value.byId[sessionId]?.running === true);
6680
6977
  const projection = useClaudeProjection((value) => value);
6681
6978
  const repository = projection.repository;
6979
+ const { toast, report } = useActionToast();
6682
6980
  if (blank || !projection.owned || repository === void 0) return null;
6683
6981
  const branch = repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch");
6684
6982
  const pullRequest = repository.pullRequest;
@@ -6689,6 +6987,7 @@ window.__ModuleLoader__.load({
6689
6987
  const hasDiff = repository.diff !== void 0 && (repository.diff.additions > 0 || repository.diff.deletions > 0);
6690
6988
  if (repository.status !== "ready") return /* @__PURE__ */ jsx("div", {
6691
6989
  style: repositoryBarFrame,
6990
+ [CLAUDE_COMPOSER_BAR_ATTRIBUTE]: "",
6692
6991
  children: /* @__PURE__ */ jsxs("div", {
6693
6992
  style: repositoryBar,
6694
6993
  children: [/* @__PURE__ */ jsx("span", {
@@ -6700,9 +6999,10 @@ window.__ModuleLoader__.load({
6700
6999
  })]
6701
7000
  })
6702
7001
  });
6703
- return /* @__PURE__ */ jsx("div", {
7002
+ return /* @__PURE__ */ jsxs("div", {
6704
7003
  style: repositoryBarFrame,
6705
- children: /* @__PURE__ */ jsxs("div", {
7004
+ [CLAUDE_COMPOSER_BAR_ATTRIBUTE]: "",
7005
+ children: [toast, /* @__PURE__ */ jsxs("div", {
6706
7006
  style: {
6707
7007
  ...repositoryBar,
6708
7008
  ...merged ? repositoryBarMerged : {}
@@ -6778,6 +7078,7 @@ window.__ModuleLoader__.load({
6778
7078
  }), /* @__PURE__ */ jsx(CleanupControl, {
6779
7079
  repository,
6780
7080
  t,
7081
+ report,
6781
7082
  ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
6782
7083
  })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
6783
7084
  pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
@@ -6806,17 +7107,19 @@ window.__ModuleLoader__.load({
6806
7107
  sessionId,
6807
7108
  repository,
6808
7109
  t,
7110
+ report,
6809
7111
  ...submitPrompt === void 0 ? {} : { submitPrompt }
6810
7112
  }),
6811
7113
  /* @__PURE__ */ jsx(MergePullRequestControl, {
6812
7114
  sessionId,
6813
7115
  repository,
6814
- t
7116
+ t,
7117
+ report
6815
7118
  })
6816
7119
  ] })]
6817
7120
  })
6818
7121
  ]
6819
- })
7122
+ })]
6820
7123
  });
6821
7124
  }
6822
7125
  //#endregion
@@ -11134,24 +11437,424 @@ window.__ModuleLoader__.load({
11134
11437
  ADD_ATTR: ["target", "rel"]
11135
11438
  });
11136
11439
  }
11137
- /** One review comment body, ready for `dangerouslySetInnerHTML`. */
11138
- function renderCommentBody(body, purify$2 = purify) {
11139
- return sanitizeCommentHtml(commentBodyHtml(body), purify$2);
11440
+ /** One review comment body, ready for `dangerouslySetInnerHTML`. */
11441
+ function renderCommentBody(body, purify$2 = purify) {
11442
+ return sanitizeCommentHtml(commentBodyHtml(body), purify$2);
11443
+ }
11444
+ /** Open sanitized links in the browser instead of inside the app shell, and
11445
+ * keep comment checkboxes inert. Installed once, on import, so no render
11446
+ * path can reach the sanitizer before its hooks. */
11447
+ function installCommentSanitizerHooks(purify$3 = purify) {
11448
+ if (typeof purify$3.addHook !== "function" || purify$3.isSupported !== true) return;
11449
+ purify$3.addHook("afterSanitizeAttributes", (node) => {
11450
+ if (node.nodeName === "A") {
11451
+ node.setAttribute("target", "_blank");
11452
+ node.setAttribute("rel", "noopener noreferrer");
11453
+ }
11454
+ if (node.nodeName === "INPUT") node.setAttribute("disabled", "");
11455
+ });
11456
+ }
11457
+ installCommentSanitizerHooks();
11458
+ //#endregion
11459
+ //#region src/client/mention.ts
11460
+ /** `@` completion for the review reply composer.
11461
+ *
11462
+ * GitHub resolves `@login` server-side, so completion is convenience only:
11463
+ * every function here works on the draft text, never on the network. */
11464
+ /** GitHub logins are alphanumeric with dashes; app accounts add a `[bot]`
11465
+ * suffix, which the user reaches by continuing to type rather than by us
11466
+ * matching it here. */
11467
+ const HANDLE = /(?:^|[^\w@/-])@([\w-]*)$/u;
11468
+ const MAX_HANDLE_CHARS = 39;
11469
+ /** The handle the caret is currently inside, when it is inside one. */
11470
+ function mentionQueryAt(text, caret) {
11471
+ const before = text.slice(0, caret);
11472
+ const match = HANDLE.exec(before);
11473
+ const query = match?.[1];
11474
+ if (match === void 0 || match === null || query === void 0 || query.length > MAX_HANDLE_CHARS) return void 0;
11475
+ return {
11476
+ query,
11477
+ start: caret - query.length - 1
11478
+ };
11479
+ }
11480
+ /** Swap the typed handle for the chosen login, leaving a separating space. */
11481
+ function applyMention(text, caret, mention, login) {
11482
+ const completed = `@${login} `;
11483
+ return {
11484
+ text: `${text.slice(0, mention.start)}${completed}${text.slice(caret)}`,
11485
+ caret: mention.start + completed.length
11486
+ };
11487
+ }
11488
+ //#endregion
11489
+ //#region src/client/menu-navigation.ts
11490
+ /** Wrapping keyboard navigation shared by the plugin's popup lists. */
11491
+ function menuNavigationIndex(current, count, key) {
11492
+ if (count <= 0) return 0;
11493
+ if (key === "Home") return 0;
11494
+ if (key === "End") return count - 1;
11495
+ return (current + (key === "ArrowDown" ? 1 : -1) + count) % count;
11496
+ }
11497
+ //#endregion
11498
+ //#region src/client/MentionTextarea.tsx
11499
+ /** How long the draft must sit still before the repository is asked who `@al`
11500
+ * might be; one request per keystroke would rate-limit the token. */
11501
+ const SUGGEST_DEBOUNCE_MS = 200;
11502
+ /** A comment composer that completes `@handles` from the repository's
11503
+ * mentionable users. GitHub notifies on the text alone, so the popup is a
11504
+ * convenience: typing a login by hand works exactly as well. */
11505
+ function MentionTextarea({ value, placeholder, suggestLabel, maxLength = 2e3, autoFocus = false, disabled = false, suggest, onChange, onSubmit }) {
11506
+ const areaRef = useRef(null);
11507
+ const [mention, setMention] = useState();
11508
+ const [users, setUsers] = useState([]);
11509
+ const [activeIndex, setActiveIndex] = useState(0);
11510
+ const [caretTarget, setCaretTarget] = useState();
11511
+ const open = mention !== void 0 && users.length > 0;
11512
+ useEffect(() => {
11513
+ if (mention === void 0) {
11514
+ setUsers([]);
11515
+ return;
11516
+ }
11517
+ let cancelled = false;
11518
+ const timer = setTimeout(() => {
11519
+ suggest(mention.query).then((found) => {
11520
+ if (cancelled) return;
11521
+ setUsers(found);
11522
+ setActiveIndex(0);
11523
+ }, () => {
11524
+ if (!cancelled) setUsers([]);
11525
+ });
11526
+ }, SUGGEST_DEBOUNCE_MS);
11527
+ return () => {
11528
+ cancelled = true;
11529
+ clearTimeout(timer);
11530
+ };
11531
+ }, [mention, suggest]);
11532
+ useLayoutEffect(() => {
11533
+ if (caretTarget === void 0 || areaRef.current === null) return;
11534
+ areaRef.current.setSelectionRange(caretTarget, caretTarget);
11535
+ setCaretTarget(void 0);
11536
+ }, [caretTarget, value]);
11537
+ const track = (area) => {
11538
+ setMention(mentionQueryAt(area.value, area.selectionStart ?? area.value.length));
11539
+ };
11540
+ const complete = (login) => {
11541
+ const area = areaRef.current;
11542
+ if (area === null || mention === void 0) return;
11543
+ const applied = applyMention(area.value, area.selectionStart ?? area.value.length, mention, login);
11544
+ setMention(void 0);
11545
+ setUsers([]);
11546
+ setCaretTarget(applied.caret);
11547
+ onChange(applied.text);
11548
+ };
11549
+ const handleKeyDown = (event) => {
11550
+ if (open) {
11551
+ if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Home" || event.key === "End") {
11552
+ event.preventDefault();
11553
+ setActiveIndex((current) => menuNavigationIndex(current, users.length, event.key));
11554
+ return;
11555
+ }
11556
+ if (event.key === "Enter" || event.key === "Tab") {
11557
+ const login = users[activeIndex]?.login;
11558
+ if (login !== void 0) {
11559
+ event.preventDefault();
11560
+ complete(login);
11561
+ return;
11562
+ }
11563
+ }
11564
+ if (event.key === "Escape") {
11565
+ event.preventDefault();
11566
+ setMention(void 0);
11567
+ setUsers([]);
11568
+ return;
11569
+ }
11570
+ }
11571
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey) && onSubmit !== void 0) {
11572
+ event.preventDefault();
11573
+ onSubmit();
11574
+ }
11575
+ };
11576
+ return /* @__PURE__ */ jsxs("span", {
11577
+ style: mentionField,
11578
+ children: [/* @__PURE__ */ jsx("textarea", {
11579
+ ref: areaRef,
11580
+ className: diffCommentTextareaClass,
11581
+ style: diffCommentTextarea,
11582
+ value,
11583
+ maxLength,
11584
+ placeholder,
11585
+ disabled,
11586
+ autoFocus,
11587
+ "aria-autocomplete": "list",
11588
+ onChange: (event) => {
11589
+ onChange(event.currentTarget.value);
11590
+ track(event.currentTarget);
11591
+ },
11592
+ onKeyUp: (event) => {
11593
+ track(event.currentTarget);
11594
+ },
11595
+ onClick: (event) => {
11596
+ track(event.currentTarget);
11597
+ },
11598
+ onBlur: () => {
11599
+ setMention(void 0);
11600
+ },
11601
+ onKeyDown: handleKeyDown
11602
+ }), !open ? null : /* @__PURE__ */ jsx("span", {
11603
+ role: "listbox",
11604
+ "aria-label": suggestLabel,
11605
+ style: mentionList,
11606
+ children: users.map((user, index) => /* @__PURE__ */ jsxs("button", {
11607
+ type: "button",
11608
+ role: "option",
11609
+ "aria-selected": index === activeIndex,
11610
+ style: {
11611
+ ...mentionItem,
11612
+ ...index === activeIndex ? mentionItemActive : {}
11613
+ },
11614
+ onMouseEnter: () => {
11615
+ setActiveIndex(index);
11616
+ },
11617
+ onMouseDown: (event) => {
11618
+ event.preventDefault();
11619
+ },
11620
+ onClick: () => {
11621
+ complete(user.login);
11622
+ },
11623
+ children: [user.avatarUrl === void 0 ? null : /* @__PURE__ */ jsx("img", {
11624
+ src: user.avatarUrl,
11625
+ alt: "",
11626
+ width: 16,
11627
+ height: 16,
11628
+ loading: "lazy",
11629
+ referrerPolicy: "no-referrer",
11630
+ style: mentionAvatar
11631
+ }), user.login]
11632
+ }, user.login))
11633
+ })]
11634
+ });
11635
+ }
11636
+ //#endregion
11637
+ //#region src/client/ReviewThreadCard.tsx
11638
+ const BOT_SUFFIX = "[bot]";
11639
+ /** GitHub prints an app account as its name plus a Bot pill rather than as the
11640
+ * raw `name[bot]` login. GraphQL already drops that suffix, so the flag from
11641
+ * the API decides and the suffix is only stripped for display. */
11642
+ function identity(author) {
11643
+ return author.endsWith(BOT_SUFFIX) ? author.slice(0, -5) : author;
11644
+ }
11645
+ function CommentBody({ comment, t, now, first }) {
11646
+ const name = identity(comment.author);
11647
+ const age = relativeAge(comment.createdAt, now);
11648
+ return /* @__PURE__ */ jsxs("div", {
11649
+ style: {
11650
+ ...diffGhComment,
11651
+ ...first ? {} : diffGhCommentNext
11652
+ },
11653
+ children: [/* @__PURE__ */ jsxs("div", {
11654
+ style: diffCommentCardMeta,
11655
+ children: [/* @__PURE__ */ jsxs("span", {
11656
+ style: diffGhCommentIdentity,
11657
+ children: [
11658
+ comment.avatarUrl === void 0 ? null : /* @__PURE__ */ jsx("img", {
11659
+ src: comment.avatarUrl,
11660
+ alt: "",
11661
+ width: 16,
11662
+ height: 16,
11663
+ loading: "lazy",
11664
+ referrerPolicy: "no-referrer",
11665
+ style: diffGhCommentAvatar,
11666
+ onError: (event) => {
11667
+ event.currentTarget.style.display = "none";
11668
+ }
11669
+ }),
11670
+ /* @__PURE__ */ jsxs("span", {
11671
+ style: diffGhCommentAuthor,
11672
+ children: ["@", name]
11673
+ }),
11674
+ comment.bot !== true ? null : /* @__PURE__ */ jsx("span", {
11675
+ style: diffThreadBadge,
11676
+ children: t("reviewThreadBot")
11677
+ }),
11678
+ age === void 0 ? null : /* @__PURE__ */ jsx("span", {
11679
+ style: diffGhCommentAge,
11680
+ children: t("reviewThreadAgo", { age })
11681
+ })
11682
+ ]
11683
+ }), /* @__PURE__ */ jsx(Tooltip, {
11684
+ label: t("reviewThreadOpenOnGitHub"),
11685
+ side: "bottom",
11686
+ delayMs: 250,
11687
+ children: /* @__PURE__ */ jsx("a", {
11688
+ href: comment.url,
11689
+ target: "_blank",
11690
+ rel: "noopener noreferrer",
11691
+ style: diffGhCommentLink,
11692
+ "aria-label": t("reviewThreadOpenOnGitHub"),
11693
+ children: /* @__PURE__ */ jsx(IconRightUpOutline14, {})
11694
+ })
11695
+ })]
11696
+ }), /* @__PURE__ */ jsx("div", {
11697
+ style: diffCommentCardText,
11698
+ className: "dshClaudeDiffCommentBody",
11699
+ dangerouslySetInnerHTML: { __html: renderCommentBody(comment.body) }
11700
+ })]
11701
+ });
11140
11702
  }
11141
- /** Open sanitized links in the browser instead of inside the app shell, and
11142
- * keep comment checkboxes inert. Installed once, on import, so no render
11143
- * path can reach the sanitizer before its hooks. */
11144
- function installCommentSanitizerHooks(purify$3 = purify) {
11145
- if (typeof purify$3.addHook !== "function" || purify$3.isSupported !== true) return;
11146
- purify$3.addHook("afterSanitizeAttributes", (node) => {
11147
- if (node.nodeName === "A") {
11148
- node.setAttribute("target", "_blank");
11149
- node.setAttribute("rel", "noopener noreferrer");
11703
+ /** One GitHub review conversation, anchored on its diff line: every comment in
11704
+ * the thread, plus the two things a reviewer expects to do with it — answer it
11705
+ * and close it. A resolved thread collapses, because the diff is about what
11706
+ * still needs attention. */
11707
+ function ReviewThreadCard({ thread, t, now, anchorKey, active = false, suggest, onReply, onResolvedChange }) {
11708
+ const [expanded, setExpanded] = useState(false);
11709
+ const [replying, setReplying] = useState(false);
11710
+ const [draft, setDraft] = useState("");
11711
+ const [busy, setBusy] = useState(false);
11712
+ const [error, setError] = useState();
11713
+ const first = thread.comments[0];
11714
+ if (first === void 0) return null;
11715
+ const collapsed = thread.resolved && !expanded;
11716
+ const send = () => {
11717
+ const body = draft.trim();
11718
+ if (body.length === 0 || busy) return;
11719
+ setBusy(true);
11720
+ setError(void 0);
11721
+ onReply(body).then(() => {
11722
+ setDraft("");
11723
+ setReplying(false);
11724
+ }, (reason) => {
11725
+ setError(reason instanceof Error ? reason.message : t("reviewThreadReplyFailed"));
11726
+ }).finally(() => {
11727
+ setBusy(false);
11728
+ });
11729
+ };
11730
+ const toggleResolved = () => {
11731
+ if (busy) return;
11732
+ setBusy(true);
11733
+ setError(void 0);
11734
+ onResolvedChange(!thread.resolved).catch((reason) => {
11735
+ setError(reason instanceof Error ? reason.message : t("reviewThreadResolveFailed"));
11736
+ }).finally(() => {
11737
+ setBusy(false);
11738
+ });
11739
+ };
11740
+ const frame = {
11741
+ ...anchorKey === void 0 ? {} : { "data-review-target": anchorKey },
11742
+ ...active ? { "data-review-active": "true" } : {},
11743
+ style: {
11744
+ ...diffCommentBlock,
11745
+ ...active ? diffCommentBlockActive : {}
11150
11746
  }
11151
- if (node.nodeName === "INPUT") node.setAttribute("disabled", "");
11747
+ };
11748
+ if (collapsed) return /* @__PURE__ */ jsx("div", {
11749
+ ...frame,
11750
+ children: /* @__PURE__ */ jsxs("button", {
11751
+ type: "button",
11752
+ "aria-expanded": false,
11753
+ style: diffThreadSummary,
11754
+ onClick: () => {
11755
+ setExpanded(true);
11756
+ },
11757
+ children: [
11758
+ /* @__PURE__ */ jsx("span", {
11759
+ style: diffThreadBadge,
11760
+ children: t("reviewThreadResolved")
11761
+ }),
11762
+ /* @__PURE__ */ jsxs("span", {
11763
+ style: diffGhCommentAuthor,
11764
+ children: ["@", identity(first.author)]
11765
+ }),
11766
+ /* @__PURE__ */ jsx("span", {
11767
+ style: diffThreadExcerpt,
11768
+ children: first.body.split("\n")[0]
11769
+ })
11770
+ ]
11771
+ })
11772
+ });
11773
+ return /* @__PURE__ */ jsxs("div", {
11774
+ ...frame,
11775
+ children: [
11776
+ !thread.resolved && !thread.outdated ? null : /* @__PURE__ */ jsxs("div", {
11777
+ style: diffThreadBadges,
11778
+ children: [thread.resolved ? /* @__PURE__ */ jsx("span", {
11779
+ style: diffThreadBadge,
11780
+ children: t("reviewThreadResolved")
11781
+ }) : null, thread.outdated ? /* @__PURE__ */ jsx("span", {
11782
+ style: diffThreadBadge,
11783
+ children: t("reviewThreadOutdated")
11784
+ }) : null]
11785
+ }),
11786
+ thread.comments.map((comment, index) => /* @__PURE__ */ jsx(CommentBody, {
11787
+ comment,
11788
+ t,
11789
+ now,
11790
+ first: index === 0
11791
+ }, comment.id)),
11792
+ error === void 0 ? null : /* @__PURE__ */ jsx("p", {
11793
+ style: diffCommentError,
11794
+ children: error
11795
+ }),
11796
+ replying ? /* @__PURE__ */ jsxs("div", {
11797
+ "data-review-reply": "",
11798
+ style: diffThreadComposer,
11799
+ children: [
11800
+ /* @__PURE__ */ jsx("span", {
11801
+ style: diffThreadComposerCaption,
11802
+ children: t("reviewThreadReplyTo", { author: identity(thread.comments[thread.comments.length - 1]?.author ?? first.author) })
11803
+ }),
11804
+ /* @__PURE__ */ jsx(MentionTextarea, {
11805
+ value: draft,
11806
+ placeholder: t("reviewThreadReplyPlaceholder"),
11807
+ suggestLabel: t("reviewThreadMention"),
11808
+ disabled: busy,
11809
+ autoFocus: true,
11810
+ suggest,
11811
+ onChange: setDraft,
11812
+ onSubmit: send
11813
+ }),
11814
+ /* @__PURE__ */ jsxs("div", {
11815
+ style: diffCommentActions,
11816
+ children: [/* @__PURE__ */ jsx("button", {
11817
+ type: "button",
11818
+ style: diffCommentActionButton,
11819
+ disabled: busy,
11820
+ onClick: () => {
11821
+ setReplying(false);
11822
+ setError(void 0);
11823
+ },
11824
+ children: t("reviewCommentCancel")
11825
+ }), /* @__PURE__ */ jsx("button", {
11826
+ type: "button",
11827
+ style: {
11828
+ ...diffCommentActionButton,
11829
+ ...diffCommentSubmitButton
11830
+ },
11831
+ disabled: busy || draft.trim().length === 0,
11832
+ onClick: send,
11833
+ children: t("reviewThreadSend")
11834
+ })]
11835
+ })
11836
+ ]
11837
+ }) : /* @__PURE__ */ jsxs("div", {
11838
+ style: diffCommentActions,
11839
+ children: [/* @__PURE__ */ jsx("button", {
11840
+ type: "button",
11841
+ style: diffCommentActionButton,
11842
+ disabled: busy,
11843
+ onClick: () => {
11844
+ setReplying(true);
11845
+ },
11846
+ children: t("reviewThreadReply")
11847
+ }), /* @__PURE__ */ jsx("button", {
11848
+ type: "button",
11849
+ style: diffCommentActionButton,
11850
+ disabled: busy,
11851
+ onClick: toggleResolved,
11852
+ children: thread.resolved ? t("reviewThreadUnresolve") : t("reviewThreadResolve")
11853
+ })]
11854
+ })
11855
+ ]
11152
11856
  });
11153
11857
  }
11154
- installCommentSanitizerHooks();
11155
11858
  //#endregion
11156
11859
  //#region src/client/ClaudeDiffPanel.tsx
11157
11860
  function pathFromHeader(line) {
@@ -11304,6 +12007,28 @@ window.__ModuleLoader__.load({
11304
12007
  }
11305
12008
  return out;
11306
12009
  }
12010
+ /** Everything in this diff still waiting on the reader, in reading order:
12011
+ * files as the panel lists them, lines as the file reads. Resolved threads are
12012
+ * collapsed by design, and a comment on a file this diff does not render has
12013
+ * nowhere to scroll to, so neither is a stop. */
12014
+ function reviewTargets(files, comments, threads) {
12015
+ const targets = [];
12016
+ for (const file of files) {
12017
+ const inFile = [...threads.filter((thread) => thread.path === file.path && !thread.resolved && thread.line !== void 0).map((thread) => ({
12018
+ key: `thread:${thread.id}`,
12019
+ path: thread.path,
12020
+ line: thread.line ?? 0,
12021
+ side: thread.side
12022
+ })), ...comments.filter((comment) => comment.path === file.path).map((comment) => ({
12023
+ key: `comment:${comment.id}`,
12024
+ path: comment.path,
12025
+ line: comment.line,
12026
+ side: comment.side
12027
+ }))];
12028
+ targets.push(...inFile.sort((left, right) => left.line - right.line || left.side.localeCompare(right.side)));
12029
+ }
12030
+ return targets;
12031
+ }
11307
12032
  /**
11308
12033
  * Anchor for a drag from row `from` to row `to`: every commentable row in
11309
12034
  * between on the same side as the first row, collapsed to its first/last line.
@@ -11430,8 +12155,7 @@ window.__ModuleLoader__.load({
11430
12155
  children: /* @__PURE__ */ jsx("path", { d: "M14 9.5a2 2 0 0 1-2 2H6l-3.5 2.5V4a2 2 0 0 1 2-2h7.5a2 2 0 0 1 2 2z" })
11431
12156
  });
11432
12157
  }
11433
- function DiffFileSection({ file, root, initiallyOpen, t, comments, ghComments, editorAnchor, editorNode, onOpenEditor, onRemoveComment }) {
11434
- const [open, setOpen] = useState(initiallyOpen);
12158
+ function DiffFileSection({ file, root, open, t, comments, ghThreads, editorAnchor, editorNode, now, activeTargetKey, suggestMention, onOpenEditor, onOpenChange, onRemoveComment, onReplyToThread, onThreadResolvedChange }) {
11435
12159
  const [revealed, setRevealed] = useState(() => /* @__PURE__ */ new Map());
11436
12160
  const [total, setTotal] = useState();
11437
12161
  const [expanding, setExpanding] = useState(false);
@@ -11443,7 +12167,7 @@ window.__ModuleLoader__.load({
11443
12167
  ]);
11444
12168
  const anchors = useMemo(() => rows.map(commentAnchorForLine), [rows]);
11445
12169
  const name = file.path.slice(file.path.lastIndexOf("/") + 1);
11446
- const commentCount = comments.length + ghComments.length;
12170
+ const commentCount = comments.length + ghThreads.reduce((total, thread) => total + thread.comments.length, 0);
11447
12171
  const expand = (gap, direction) => {
11448
12172
  if (root === void 0 || expanding) return;
11449
12173
  const span = gap.count ?? 20;
@@ -11486,14 +12210,16 @@ window.__ModuleLoader__.load({
11486
12210
  type: "button",
11487
12211
  style: diffFileHeader,
11488
12212
  "aria-expanded": open,
11489
- onClick: () => setOpen((value) => !value),
12213
+ onClick: () => onOpenChange(!open),
11490
12214
  children: [
11491
12215
  /* @__PURE__ */ jsx("span", {
12216
+ "data-diff-file-chevron": "",
11492
12217
  style: {
11493
- ...chevron,
12218
+ ...diffFileChevron,
11494
12219
  ...open ? chevronOpen : {}
11495
12220
  },
11496
- children: ""
12221
+ "aria-hidden": "true",
12222
+ children: /* @__PURE__ */ jsx(IconChevronRightOutline14, { size: 14 })
11497
12223
  }),
11498
12224
  /* @__PURE__ */ jsx("span", {
11499
12225
  style: diffFilePath,
@@ -11536,7 +12262,7 @@ window.__ModuleLoader__.load({
11536
12262
  }, `gap:${entry.gap.newStart}`);
11537
12263
  const anchor = anchors[index];
11538
12264
  const lineComments = anchor === void 0 ? [] : comments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
11539
- const ghLineComments = anchor === void 0 ? [] : ghComments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
12265
+ const lineThreads = anchor === void 0 ? [] : ghThreads.filter((thread) => thread.line === anchor.line && thread.side === anchor.side);
11540
12266
  const editorOpen = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.line === anchor.line && editorAnchor.side === anchor.side;
11541
12267
  const inDrag = dragLow !== void 0 && dragHigh !== void 0 && index >= dragLow && index <= dragHigh && anchor?.side === dragSide;
11542
12268
  const inEditorRange = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.side === anchor.side && anchor.line <= editorAnchor.line && anchor.line >= (editorAnchor.startLine ?? editorAnchor.line);
@@ -11556,7 +12282,12 @@ window.__ModuleLoader__.load({
11556
12282
  })
11557
12283
  }),
11558
12284
  lineComments.map((comment) => /* @__PURE__ */ jsxs("div", {
11559
- style: diffCommentBlock,
12285
+ "data-review-target": `comment:${comment.id}`,
12286
+ "data-review-active": activeTargetKey === `comment:${comment.id}` ? "true" : void 0,
12287
+ style: {
12288
+ ...diffCommentBlock,
12289
+ ...activeTargetKey === `comment:${comment.id}` ? diffCommentBlockActive : {}
12290
+ },
11560
12291
  children: [/* @__PURE__ */ jsxs("div", {
11561
12292
  style: diffCommentCardMeta,
11562
12293
  children: [/* @__PURE__ */ jsx("span", { children: commentLineLabel(comment, t) }), /* @__PURE__ */ jsx("button", {
@@ -11571,41 +12302,16 @@ window.__ModuleLoader__.load({
11571
12302
  children: comment.text
11572
12303
  })]
11573
12304
  }, comment.id)),
11574
- ghLineComments.map((comment) => /* @__PURE__ */ jsxs("div", {
11575
- style: diffCommentBlock,
11576
- children: [/* @__PURE__ */ jsxs("div", {
11577
- style: diffCommentCardMeta,
11578
- children: [/* @__PURE__ */ jsxs("span", {
11579
- style: diffGhCommentIdentity,
11580
- children: [comment.avatarUrl === void 0 ? null : /* @__PURE__ */ jsx("img", {
11581
- src: comment.avatarUrl,
11582
- alt: "",
11583
- width: 16,
11584
- height: 16,
11585
- loading: "lazy",
11586
- referrerPolicy: "no-referrer",
11587
- style: diffGhCommentAvatar,
11588
- onError: (event) => {
11589
- event.currentTarget.style.display = "none";
11590
- }
11591
- }), /* @__PURE__ */ jsxs("span", {
11592
- style: diffGhCommentAuthor,
11593
- children: ["@", comment.author]
11594
- })]
11595
- }), /* @__PURE__ */ jsx("a", {
11596
- href: comment.url,
11597
- target: "_blank",
11598
- rel: "noopener noreferrer",
11599
- style: diffGhCommentLink,
11600
- "aria-label": comment.url,
11601
- children: "↗"
11602
- })]
11603
- }), /* @__PURE__ */ jsx("div", {
11604
- style: diffCommentCardText,
11605
- className: "dshClaudeDiffCommentBody",
11606
- dangerouslySetInnerHTML: { __html: renderCommentBody(comment.body) }
11607
- })]
11608
- }, `gh-${comment.id}`)),
12305
+ lineThreads.map((thread) => /* @__PURE__ */ jsx(ReviewThreadCard, {
12306
+ thread,
12307
+ anchorKey: `thread:${thread.id}`,
12308
+ active: activeTargetKey === `thread:${thread.id}`,
12309
+ t,
12310
+ suggest: suggestMention,
12311
+ now,
12312
+ onReply: (body) => onReplyToThread(thread, body),
12313
+ onResolvedChange: (resolved) => onThreadResolvedChange(thread, resolved)
12314
+ }, thread.id)),
11609
12315
  editorOpen ? editorNode : null
11610
12316
  ] }, `${index}:${entry.line}`);
11611
12317
  })
@@ -11646,6 +12352,7 @@ window.__ModuleLoader__.load({
11646
12352
  const diff = repository?.diff;
11647
12353
  const files = useMemo(() => parseUnifiedDiff(diff?.patch ?? ""), [diff?.patch]);
11648
12354
  const [menuOpen, setMenuOpen] = useState(false);
12355
+ const { toast, report } = useActionToast();
11649
12356
  const [dialog, setDialog] = useState();
11650
12357
  const [message, setMessage] = useState("");
11651
12358
  const [includeUnstaged, setIncludeUnstaged] = useState(true);
@@ -11660,21 +12367,55 @@ window.__ModuleLoader__.load({
11660
12367
  const [localComments, setLocalComments] = useState([]);
11661
12368
  const [removedCommentIds, setRemovedCommentIds] = useState(() => /* @__PURE__ */ new Set());
11662
12369
  const actionController = useRef();
11663
- const [ghComments, setGhComments] = useState([]);
12370
+ const [ghThreads, setGhThreads] = useState([]);
12371
+ const [threadsLoadedAt, setThreadsLoadedAt] = useState(() => Date.now());
11664
12372
  const pullNumber = repository?.pullRequest?.state === "open" ? repository.pullRequest.number : void 0;
11665
12373
  useEffect(() => {
11666
- setGhComments([]);
12374
+ setGhThreads([]);
11667
12375
  if (pullNumber === void 0) return;
11668
12376
  const controller = new AbortController();
11669
- loadPullRequestComments(sessionId, pullNumber, controller.signal).then(setGhComments, () => void 0);
12377
+ loadPullRequestThreads(sessionId, pullNumber, controller.signal).then((threads) => {
12378
+ setGhThreads(threads);
12379
+ setThreadsLoadedAt(Date.now());
12380
+ }, () => void 0);
11670
12381
  return () => {
11671
12382
  controller.abort();
11672
12383
  };
11673
12384
  }, [pullNumber, sessionId]);
12385
+ const openThreadCount = ghThreads.filter((thread) => !thread.resolved).length;
12386
+ const bodyRef = useRef(null);
12387
+ const [targetIndex, setTargetIndex] = useState(0);
12388
+ const [activeTargetKey, setActiveTargetKey] = useState();
12389
+ const [pendingScrollKey, setPendingScrollKey] = useState();
12390
+ const [openFiles, setOpenFiles] = useState(() => /* @__PURE__ */ new Map());
12391
+ const fileOpen = useCallback((path, index) => openFiles.get(path) ?? index === 0, [openFiles]);
12392
+ const setFileOpen = useCallback((path, open) => {
12393
+ setOpenFiles((current) => new Map(current).set(path, open));
12394
+ }, []);
12395
+ const suggestMention = useCallback(async (query) => pullNumber === void 0 ? [] : loadMentionableUsers(sessionId, pullNumber, query).catch(() => []), [pullNumber, sessionId]);
12396
+ const replyToThread = useCallback(async (thread, body) => {
12397
+ if (pullNumber === void 0) return;
12398
+ const anchor = thread.comments[0];
12399
+ if (anchor === void 0) return;
12400
+ const posted = await replyToReviewThread(sessionId, pullNumber, anchor.id, body);
12401
+ setGhThreads((list) => list.map((item) => item.id === thread.id ? {
12402
+ ...item,
12403
+ comments: [...item.comments, posted]
12404
+ } : item));
12405
+ }, [pullNumber, sessionId]);
12406
+ const changeThreadResolved = useCallback(async (thread, resolved) => {
12407
+ if (pullNumber === void 0) return;
12408
+ const state = await setReviewThreadResolved(sessionId, pullNumber, thread.id, resolved);
12409
+ setGhThreads((list) => list.map((item) => item.id === thread.id ? {
12410
+ ...item,
12411
+ resolved: state
12412
+ } : item));
12413
+ }, [pullNumber, sessionId]);
11674
12414
  const diffViewportObserver = useRef();
11675
12415
  const diffBodyRef = useCallback((element) => {
11676
12416
  diffViewportObserver.current?.disconnect();
11677
12417
  diffViewportObserver.current = void 0;
12418
+ bodyRef.current = element;
11678
12419
  if (element === null || typeof ResizeObserver === "undefined") return;
11679
12420
  const update = () => element.style.setProperty("--dsh-claude-diff-viewport", `${element.clientWidth}px`);
11680
12421
  update();
@@ -11689,16 +12430,6 @@ window.__ModuleLoader__.load({
11689
12430
  actionController.current = void 0;
11690
12431
  setDialog(void 0);
11691
12432
  }, [dialog?.submitting]);
11692
- useEffect(() => {
11693
- if (dialog?.commit === void 0 || dialog.error !== void 0 || dialog.pullRequestUrl !== void 0) return;
11694
- const timer = setTimeout(closeDialog, 1200);
11695
- return () => clearTimeout(timer);
11696
- }, [
11697
- closeDialog,
11698
- dialog?.commit,
11699
- dialog?.error,
11700
- dialog?.pullRequestUrl
11701
- ]);
11702
12433
  const openAction = useCallback((action) => {
11703
12434
  actionController.current?.abort();
11704
12435
  setMenuOpen(false);
@@ -11732,14 +12463,12 @@ window.__ModuleLoader__.load({
11732
12463
  submitting: false
11733
12464
  });
11734
12465
  const generated = await generateCommitMessage(sessionId, preview.fingerprint, controller.signal);
11735
- setMessage(generated);
11736
- setPrTitle(generated);
11737
- setPrBody(`Summary: ${generated}\n\nChanges:\n- ${generated}`);
11738
- setDialog({
11739
- action,
11740
- preview,
11741
- loading: false,
11742
- submitting: false
12466
+ setMessage((current) => current.trim() === "" ? generated : current);
12467
+ setPrTitle((current) => current.trim() === "" ? generated : current);
12468
+ setPrBody((current) => current.trim() === "" ? `Summary: ${generated}\n\nChanges:\n- ${generated}` : current);
12469
+ setDialog((current) => current === void 0 ? current : {
12470
+ ...current,
12471
+ loading: false
11743
12472
  });
11744
12473
  }).catch((error) => {
11745
12474
  if (!controller.signal.aborted) setDialog({
@@ -11770,12 +12499,8 @@ window.__ModuleLoader__.load({
11770
12499
  draft
11771
12500
  } : {}
11772
12501
  });
11773
- setDialog({
11774
- ...dialog,
11775
- submitting: false,
11776
- commit: result.commit,
11777
- ...result.pullRequestUrl === void 0 ? {} : { pullRequestUrl: result.pullRequestUrl }
11778
- });
12502
+ report(result.pullRequestUrl === void 0 ? t(dialog.action === "push" ? "diffPushCompleted" : "diffCommitCompleted", { commit: result.commit.slice(0, 8) }) : t("diffPrCompleted"));
12503
+ setDialog(void 0);
11779
12504
  } catch (error) {
11780
12505
  const completedCommit = typeof error === "object" && error !== null && "commit" in error && typeof error.commit === "string" ? error.commit : void 0;
11781
12506
  setDialog({
@@ -11793,6 +12518,7 @@ window.__ModuleLoader__.load({
11793
12518
  message,
11794
12519
  prBody,
11795
12520
  prTitle,
12521
+ report,
11796
12522
  sessionId,
11797
12523
  t
11798
12524
  ]);
@@ -11865,6 +12591,47 @@ window.__ModuleLoader__.load({
11865
12591
  projection.reviewComments,
11866
12592
  removedCommentIds
11867
12593
  ]);
12594
+ const targets = useMemo(() => reviewTargets(files, reviewComments, ghThreads), [
12595
+ files,
12596
+ ghThreads,
12597
+ reviewComments
12598
+ ]);
12599
+ useEffect(() => {
12600
+ setTargetIndex((index) => index < targets.length ? index : 0);
12601
+ }, [targets.length]);
12602
+ const goToComment = useCallback((direction) => {
12603
+ setTargetIndex((current) => {
12604
+ if (targets.length === 0) return current;
12605
+ const next = menuNavigationIndex(current, targets.length, direction);
12606
+ const target = targets[next];
12607
+ if (target !== void 0) {
12608
+ setActiveTargetKey(target.key);
12609
+ setPendingScrollKey(target.key);
12610
+ setFileOpen(target.path, true);
12611
+ }
12612
+ return next;
12613
+ });
12614
+ }, [setFileOpen, targets]);
12615
+ useEffect(() => {
12616
+ if (pendingScrollKey === void 0) return;
12617
+ bodyRef.current?.querySelector(`[data-review-target="${pendingScrollKey}"]`)?.scrollIntoView({ block: "center" });
12618
+ setPendingScrollKey(void 0);
12619
+ }, [pendingScrollKey]);
12620
+ useEffect(() => {
12621
+ if (targets.length === 0) return;
12622
+ const shortcut = (event) => {
12623
+ if (event.ctrlKey || event.metaKey || event.altKey) return;
12624
+ const origin = event.target;
12625
+ if (origin instanceof HTMLElement && (origin.isContentEditable || origin.tagName === "INPUT" || origin.tagName === "TEXTAREA" || origin.tagName === "SELECT")) return;
12626
+ if (event.key !== "n" && event.key !== "p") return;
12627
+ event.preventDefault();
12628
+ goToComment(event.key === "n" ? "ArrowDown" : "ArrowUp");
12629
+ };
12630
+ document.addEventListener("keydown", shortcut);
12631
+ return () => {
12632
+ document.removeEventListener("keydown", shortcut);
12633
+ };
12634
+ }, [goToComment, targets.length]);
11868
12635
  useEffect(() => () => actionController.current?.abort(), []);
11869
12636
  useEffect(() => {
11870
12637
  if (!projection.owned || repository?.status !== "ready" || diff === void 0) closeDetails();
@@ -11900,7 +12667,7 @@ window.__ModuleLoader__.load({
11900
12667
  disabled: !availability["create-pr"]
11901
12668
  }
11902
12669
  ];
11903
- const completed = dialog?.commit !== void 0 && dialog.error === void 0;
12670
+ const allFilesOpen = files.every((file, index) => fileOpen(file.path, index));
11904
12671
  const commentEditorNode = commentEditor === void 0 ? null : /* @__PURE__ */ jsxs("div", {
11905
12672
  style: diffCommentBlock,
11906
12673
  children: [
@@ -11942,6 +12709,7 @@ window.__ModuleLoader__.load({
11942
12709
  ]
11943
12710
  });
11944
12711
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
12712
+ toast,
11945
12713
  /* @__PURE__ */ jsxs("style", {
11946
12714
  "data-dsh-claude-repository-modal-styles": true,
11947
12715
  children: [
@@ -12014,12 +12782,46 @@ window.__ModuleLoader__.load({
12014
12782
  })
12015
12783
  })]
12016
12784
  }),
12017
- ghComments.length > 0 && submitPrompt !== void 0 ? /* @__PURE__ */ jsxs("button", {
12785
+ targets.length === 0 ? null : /* @__PURE__ */ jsxs("div", {
12786
+ style: diffCommentNav,
12787
+ children: [
12788
+ /* @__PURE__ */ jsx("button", {
12789
+ type: "button",
12790
+ className: panelIconButtonClass,
12791
+ "aria-label": t("diffCommentPrevious"),
12792
+ title: t("diffCommentPosition", {
12793
+ index: targetIndex + 1,
12794
+ total: targets.length
12795
+ }),
12796
+ onClick: () => goToComment("ArrowUp"),
12797
+ children: /* @__PURE__ */ jsx(IconChevronUpOutline14, {})
12798
+ }),
12799
+ /* @__PURE__ */ jsx("span", {
12800
+ style: diffCommentNavCount,
12801
+ children: t("diffCommentCounter", {
12802
+ index: targetIndex + 1,
12803
+ total: targets.length
12804
+ })
12805
+ }),
12806
+ /* @__PURE__ */ jsx("button", {
12807
+ type: "button",
12808
+ className: panelIconButtonClass,
12809
+ "aria-label": t("diffCommentNext"),
12810
+ title: t("diffCommentPosition", {
12811
+ index: targetIndex + 1,
12812
+ total: targets.length
12813
+ }),
12814
+ onClick: () => goToComment("ArrowDown"),
12815
+ children: /* @__PURE__ */ jsx(IconChevronDownOutline14, {})
12816
+ })
12817
+ ]
12818
+ }),
12819
+ openThreadCount > 0 && submitPrompt !== void 0 ? /* @__PURE__ */ jsxs("button", {
12018
12820
  type: "button",
12019
12821
  style: diffPrCommentsButton,
12020
12822
  title: t("prCommentsSend"),
12021
- "aria-label": t("prCommentsButton", { count: ghComments.length }),
12022
- onClick: () => submitPrompt(composeCommentsPrompt(ghComments)),
12823
+ "aria-label": t("prCommentsButton", { count: openThreadCount }),
12824
+ onClick: () => submitPrompt(composeCommentsPrompt(ghThreads)),
12023
12825
  children: [/* @__PURE__ */ jsx("svg", {
12024
12826
  width: "14",
12025
12827
  height: "14",
@@ -12031,7 +12833,7 @@ window.__ModuleLoader__.load({
12031
12833
  strokeLinejoin: "round",
12032
12834
  "aria-hidden": "true",
12033
12835
  children: /* @__PURE__ */ jsx("path", { d: "M2.75 2.75h10.5a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1H8.2l-3.2 2.9v-2.9H2.75a1 1 0 0 1-1-1v-6.5a1 1 0 0 1 1-1Z" })
12034
- }), ghComments.length]
12836
+ }), openThreadCount]
12035
12837
  }) : null,
12036
12838
  /* @__PURE__ */ jsx("button", {
12037
12839
  type: "button",
@@ -12061,6 +12863,15 @@ window.__ModuleLoader__.load({
12061
12863
  /* @__PURE__ */ jsxs("span", {
12062
12864
  style: diffDelete,
12063
12865
  children: ["−", diff.deletions]
12866
+ }),
12867
+ files.length === 0 ? null : /* @__PURE__ */ jsx("button", {
12868
+ type: "button",
12869
+ style: diffSummaryAction,
12870
+ "aria-label": allFilesOpen ? t("diffCollapseAll") : t("diffExpandAll"),
12871
+ onClick: () => {
12872
+ setOpenFiles(new Map(files.map((file) => [file.path, !allFilesOpen])));
12873
+ },
12874
+ children: allFilesOpen ? t("diffCollapseAll") : t("diffExpandAll")
12064
12875
  })
12065
12876
  ]
12066
12877
  }),
@@ -12076,10 +12887,18 @@ window.__ModuleLoader__.load({
12076
12887
  }) : files.map((file, index) => /* @__PURE__ */ jsx(DiffFileSection, {
12077
12888
  file,
12078
12889
  root: repository.root,
12079
- initiallyOpen: index === 0,
12890
+ open: fileOpen(file.path, index),
12891
+ onOpenChange: (open) => {
12892
+ setFileOpen(file.path, open);
12893
+ },
12080
12894
  t,
12081
12895
  comments: reviewComments.filter((comment) => comment.path === file.path),
12082
- ghComments: ghComments.filter((comment) => comment.path === file.path),
12896
+ ghThreads: ghThreads.filter((thread) => thread.path === file.path),
12897
+ suggestMention,
12898
+ now: threadsLoadedAt,
12899
+ activeTargetKey,
12900
+ onReplyToThread: replyToThread,
12901
+ onThreadResolvedChange: changeThreadResolved,
12083
12902
  editorAnchor: commentEditor !== void 0 && commentEditor.path === file.path ? commentEditor : void 0,
12084
12903
  editorNode: commentEditorNode,
12085
12904
  onOpenEditor: (anchor) => openCommentEditor(file.path, anchor),
@@ -12106,17 +12925,17 @@ window.__ModuleLoader__.load({
12106
12925
  },
12107
12926
  disabled: dialog?.submitting === true,
12108
12927
  onClick: closeDialog,
12109
- children: completed ? t("diffDone") : t("diffCancel")
12110
- }), !completed ? /* @__PURE__ */ jsx("button", {
12928
+ children: t("diffCancel")
12929
+ }), /* @__PURE__ */ jsx("button", {
12111
12930
  type: "button",
12112
12931
  style: {
12113
12932
  ...primaryButton,
12114
12933
  ...diffModalButton
12115
12934
  },
12116
- disabled: dialog?.loading === true || dialog?.submitting === true || dialog?.preview === void 0 || dialog.action !== "push" && message.trim() === "",
12935
+ disabled: dialog?.submitting === true || dialog?.preview === void 0 || dialog.action !== "push" && message.trim() === "",
12117
12936
  onClick: () => void confirm(),
12118
12937
  children: dialog?.submitting === true ? t("diffSubmitting") : t("diffConfirm")
12119
- }) : null]
12938
+ })]
12120
12939
  }),
12121
12940
  children: [
12122
12941
  dialog?.loading === true ? /* @__PURE__ */ jsx("p", {
@@ -12227,17 +13046,7 @@ window.__ModuleLoader__.load({
12227
13046
  onChange: (event) => setDraft(event.currentTarget.checked)
12228
13047
  }), t("diffPrDraft")]
12229
13048
  })
12230
- ] }) : null,
12231
- dialog.commit !== void 0 ? /* @__PURE__ */ jsx("p", {
12232
- style: diffModalSuccess,
12233
- children: dialog.action === "push" ? t("diffPushCompleted", { commit: dialog.commit.slice(0, 8) }) : t("diffCommitCompleted", { commit: dialog.commit.slice(0, 8) })
12234
- }) : null,
12235
- dialog.pullRequestUrl !== void 0 ? /* @__PURE__ */ jsx("a", {
12236
- href: dialog.pullRequestUrl,
12237
- target: "_blank",
12238
- rel: "noopener noreferrer",
12239
- children: t("diffOpenPr")
12240
- }) : null
13049
+ ] }) : null
12241
13050
  ]
12242
13051
  }) : null,
12243
13052
  dialog?.error !== void 0 ? /* @__PURE__ */ jsxs("p", {
@@ -12553,8 +13362,18 @@ window.__ModuleLoader__.load({
12553
13362
  ] });
12554
13363
  }
12555
13364
  function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
12556
- const snapshot = useSyncExternalStore(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot);
12557
- const workspaceStore = workspaces ?? NO_WORKSPACES;
13365
+ const sessionStore = useMemo(() => ({
13366
+ subscribe: (listener) => sessions.subscribe(listener),
13367
+ getSnapshot: () => sessions.getSnapshot()
13368
+ }), [sessions]);
13369
+ const snapshot = useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
13370
+ const workspaceStore = useMemo(() => {
13371
+ const source = workspaces ?? NO_WORKSPACES;
13372
+ return {
13373
+ subscribe: (listener) => source.subscribe(listener),
13374
+ getSnapshot: () => source.getSnapshot()
13375
+ };
13376
+ }, [workspaces]);
12558
13377
  const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
12559
13378
  const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
12560
13379
  const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
@@ -12915,6 +13734,7 @@ window.__ModuleLoader__.load({
12915
13734
  /** Selection toolbar over assistant replies: copy, or ask a follow-up whose
12916
13735
  * answer streams into a popup rendered with the Host Markdown renderer. */
12917
13736
  function ClaudeSelectionAsk({ t, currentSessionId, ownsSession, insertIntoChat }) {
13737
+ const markdownLabels = useClaudeMarkdownLabels(t);
12918
13738
  const [selection, setSelection] = useState();
12919
13739
  const [open, setOpen] = useState(false);
12920
13740
  const [question, setQuestion] = useState("");
@@ -13196,8 +14016,9 @@ window.__ModuleLoader__.load({
13196
14016
  style: askStatus,
13197
14017
  children: started ? t("askThinking") : t("askStarting")
13198
14018
  }) : null,
13199
- blocks.map((block, index) => block.kind === "text" ? /* @__PURE__ */ jsx(MarkdownText, {
14019
+ blocks.map((block, index) => block.kind === "text" ? /* @__PURE__ */ jsx(ClaudeMarkdown, {
13200
14020
  text: block.text,
14021
+ labels: markdownLabels,
13201
14022
  streaming: phase === "answering" && index === blocks.length - 1
13202
14023
  }, `text-${index}`) : /* @__PURE__ */ jsxs("div", {
13203
14024
  style: askToolRow,
@@ -13263,6 +14084,77 @@ window.__ModuleLoader__.load({
13263
14084
  });
13264
14085
  }
13265
14086
  //#endregion
14087
+ //#region src/client/composer-style-probe.ts
14088
+ /** Watch for the plugin's composer bar and check it once for style drift.
14089
+ *
14090
+ * The Host publishes the composer layout properties onto the composer subtree,
14091
+ * not onto `:root`. Custom properties inherit, so only an element inside that
14092
+ * subtree can read one — probing the document root reports every one of them
14093
+ * missing whether or not the Host still defines it, which is exactly the
14094
+ * false alarm this replaced.
14095
+ *
14096
+ * The bar exists only while a Session is on screen, so this waits rather than
14097
+ * sampling once at boot, and stops after the first successful read: the answer
14098
+ * cannot change without a Host reload.
14099
+ *
14100
+ * @param report - receives one line per property the bar cannot see.
14101
+ * @returns disposer; safe to call more than once.
14102
+ */
14103
+ function watchClaudeComposerBar(report) {
14104
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {};
14105
+ let observer;
14106
+ const stop = () => {
14107
+ observer?.disconnect();
14108
+ observer = void 0;
14109
+ };
14110
+ const probe = () => {
14111
+ const bar = document.querySelector(`[${CLAUDE_COMPOSER_BAR_ATTRIBUTE}]`);
14112
+ if (bar === null) return false;
14113
+ const style = getComputedStyle(bar);
14114
+ for (const finding of claudeScopedCssFindings((name) => style.getPropertyValue(name))) report(finding);
14115
+ return true;
14116
+ };
14117
+ if (probe()) return stop;
14118
+ observer = new MutationObserver(() => {
14119
+ if (probe()) stop();
14120
+ });
14121
+ observer.observe(document.body, {
14122
+ childList: true,
14123
+ subtree: true
14124
+ });
14125
+ return stop;
14126
+ }
14127
+ function postToHost(report) {
14128
+ fetch(CLAUDE_CLIENT_DIAGNOSTICS_PATH, {
14129
+ method: "POST",
14130
+ credentials: "same-origin",
14131
+ headers: { "content-type": "application/json" },
14132
+ body: JSON.stringify(report)
14133
+ }).catch(() => {});
14134
+ }
14135
+ /** Renderer-side findings reach the Host log through here.
14136
+ *
14137
+ * Nothing the renderer throws — a crashed Slot entry included — reaches the
14138
+ * Host log on its own, and the shipped Desktop build opens no DevTools, so a
14139
+ * plugin whose entries all died looks exactly like a healthy one from the
14140
+ * outside. Every Desktop 2.0 breakage in this package was found only by
14141
+ * attaching a debugger to the renderer by hand. */
14142
+ function createClaudeDiagnosticsReporter(post = postToHost) {
14143
+ const sent = /* @__PURE__ */ new Set();
14144
+ return { report(kind, detail) {
14145
+ if (sent.size >= 40) return;
14146
+ const key = `${kind} ${detail}`;
14147
+ if (sent.has(key)) return;
14148
+ sent.add(key);
14149
+ try {
14150
+ post({
14151
+ kind,
14152
+ detail
14153
+ });
14154
+ } catch {}
14155
+ } };
14156
+ }
14157
+ //#endregion
13266
14158
  //#region src/client/rewind-dom.ts
13267
14159
  /** Seats for the rewind control inside the Host's own message action rows.
13268
14160
  *
@@ -13346,7 +14238,7 @@ window.__ModuleLoader__.load({
13346
14238
  //#endregion
13347
14239
  //#region src/client/ClaudeRewind.tsx
13348
14240
  const EMPTY_RANGES = [];
13349
- const EMPTY_CHAT = {
14241
+ const EMPTY_CHAT_VIEW = {
13350
14242
  chat: {
13351
14243
  order: [],
13352
14244
  nodes: { get: () => void 0 }
@@ -13354,7 +14246,7 @@ window.__ModuleLoader__.load({
13354
14246
  running: false
13355
14247
  };
13356
14248
  const NO_SUBSCRIBE = () => () => {};
13357
- const EMPTY_CHAT_SNAPSHOT = () => EMPTY_CHAT;
14249
+ const EMPTY_CHAT_SNAPSHOT = () => EMPTY_CHAT_VIEW;
13358
14250
  const EMPTY_PROJECTION_SNAPSHOT = () => EMPTY_CLAUDE_PROJECTION;
13359
14251
  /** Plain text of one user message node, matching what the copy action yields. */
13360
14252
  function rewindMessageText(data) {
@@ -13673,12 +14565,6 @@ window.__ModuleLoader__.load({
13673
14565
  function toggleTicketSelection(tickets, ticket) {
13674
14566
  return tickets.some((item) => item.key === ticket.key) ? tickets.filter((item) => item.key !== ticket.key) : [...tickets, ticket];
13675
14567
  }
13676
- function branchMenuNavigationIndex(current, count, key) {
13677
- if (count <= 0) return 0;
13678
- if (key === "Home") return 0;
13679
- if (key === "End") return count - 1;
13680
- return (current + (key === "ArrowDown" ? 1 : -1) + count) % count;
13681
- }
13682
14568
  const BRANCH_LOAD_RETRIES = 2;
13683
14569
  const BRANCH_LOAD_RETRY_MS = 1e3;
13684
14570
  const WORKTREE_PROGRESS_STAGES = [
@@ -13842,7 +14728,7 @@ window.__ModuleLoader__.load({
13842
14728
  if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Home" || event.key === "End") {
13843
14729
  event.preventDefault();
13844
14730
  const key = event.key;
13845
- setActiveIndex((current) => branchMenuNavigationIndex(current, filtered.length, key));
14731
+ setActiveIndex((current) => menuNavigationIndex(current, filtered.length, key));
13846
14732
  } else if (event.key === "Enter") {
13847
14733
  event.preventDefault();
13848
14734
  chooseActive();
@@ -14803,9 +15689,9 @@ window.__ModuleLoader__.load({
14803
15689
  if (this.#inflight !== void 0) return;
14804
15690
  this.#inflight = (async () => {
14805
15691
  try {
14806
- const response = await this.#api.list({});
14807
- if (!response.result.ok) return;
14808
- this.#rows = response.result.value.presets;
15692
+ const response = await this.#api.list();
15693
+ if (!response.ok) return;
15694
+ this.#rows = response.value.presets;
14809
15695
  for (const listener of this.#listeners) listener();
14810
15696
  } catch {} finally {
14811
15697
  this.#inflight = void 0;
@@ -15248,6 +16134,9 @@ window.__ModuleLoader__.load({
15248
16134
  toolInput: "输入",
15249
16135
  toolOutput: "输出",
15250
16136
  toolError: "错误",
16137
+ markdownCopy: "复制",
16138
+ markdownCopied: "已复制",
16139
+ markdownFootnotes: "脚注",
15251
16140
  tokens: "{count} 个 token",
15252
16141
  doctor: "运行诊断",
15253
16142
  refreshing: "诊断中…",
@@ -15420,6 +16309,12 @@ window.__ModuleLoader__.load({
15420
16309
  diffTruncated: "Diff 超出安全显示上限。请在终端中查看完整内容。",
15421
16310
  diffEmpty: "没有可显示的 tracked 文件改动",
15422
16311
  diffMaximize: "最大化 Diff 面板",
16312
+ diffCommentPrevious: "上一条评论",
16313
+ diffCommentNext: "下一条评论",
16314
+ diffCommentPosition: "第 {index} 条,共 {total} 条",
16315
+ diffCommentCounter: "{index}/{total}",
16316
+ diffExpandAll: "展开全部文件",
16317
+ diffCollapseAll: "收起全部文件",
15423
16318
  diffRestore: "还原 Diff 面板",
15424
16319
  diffCommit: "Commit",
15425
16320
  diffCommitMenu: "打开提交操作菜单",
@@ -15430,7 +16325,7 @@ window.__ModuleLoader__.load({
15430
16325
  diffPushDescription: "将本地未推送的 commit 推送到远端分支。",
15431
16326
  diffPushAhead: "{count} 个未推送的 commit",
15432
16327
  diffPushCompleted: "已推送 commit {commit}",
15433
- diffGeneratingMessage: "正在读取仓库并生成 commit message",
16328
+ diffGeneratingMessage: "正在生成建议的 commit message,只会填进你没写的字段。",
15434
16329
  diffActionFailed: "仓库操作失败。",
15435
16330
  diffIncludeUnstaged: "包含 unstaged 和 untracked 改动",
15436
16331
  diffCommitMessage: "Commit message",
@@ -15448,8 +16343,8 @@ window.__ModuleLoader__.load({
15448
16343
  diffSubmitting: "执行中…",
15449
16344
  diffDone: "完成",
15450
16345
  diffCommitCompleted: "已创建 commit {commit}",
16346
+ diffPrCompleted: "已创建 PR,可在仓库栏打开",
15451
16347
  diffCommitPreserved: "本地 commit {commit} 已保留。",
15452
- diffOpenPr: "打开 Pull Request",
15453
16348
  diffMergePr: "Merge",
15454
16349
  repositoryMergeMenu: "打开 PR 合并选项",
15455
16350
  diffMerge_merge: "合并提交 (merge)",
@@ -15544,6 +16439,20 @@ window.__ModuleLoader__.load({
15544
16439
  reviewCommentSubmit: "注释",
15545
16440
  reviewCommentCancel: "取消",
15546
16441
  reviewCommentRemove: "删除评论",
16442
+ reviewThreadReply: "回复",
16443
+ reviewThreadBot: "Bot",
16444
+ reviewThreadAgo: "{age}前",
16445
+ reviewThreadReplyTo: "回复 @{author}",
16446
+ reviewThreadOpenOnGitHub: "在 GitHub 上打开",
16447
+ reviewThreadSend: "发送回复",
16448
+ reviewThreadResolve: "标记为已解决",
16449
+ reviewThreadUnresolve: "重新打开",
16450
+ reviewThreadResolved: "已解决",
16451
+ reviewThreadOutdated: "已过时",
16452
+ reviewThreadReplyPlaceholder: "回复这条评论,输入 @ 可提及成员",
16453
+ reviewThreadMention: "提及仓库成员",
16454
+ reviewThreadReplyFailed: "回复没有发送成功。",
16455
+ reviewThreadResolveFailed: "线程状态没有更新成功。",
15547
16456
  reviewCommentFailed: "评论操作失败。",
15548
16457
  reviewCommentsClear: "清除全部评论",
15549
16458
  reviewCommentsSend: "发送评论",
@@ -15585,6 +16494,9 @@ window.__ModuleLoader__.load({
15585
16494
  toolInput: "Input",
15586
16495
  toolOutput: "Output",
15587
16496
  toolError: "Error",
16497
+ markdownCopy: "Copy",
16498
+ markdownCopied: "Copied",
16499
+ markdownFootnotes: "Footnotes",
15588
16500
  tokens: "{count} tokens",
15589
16501
  doctor: "Run Doctor",
15590
16502
  refreshing: "Running Doctor…",
@@ -15757,6 +16669,12 @@ window.__ModuleLoader__.load({
15757
16669
  diffTruncated: "The diff exceeds the safe display limit. Use the terminal to inspect the complete content.",
15758
16670
  diffEmpty: "No tracked file changes to display",
15759
16671
  diffMaximize: "Maximize diff panel",
16672
+ diffCommentPrevious: "Previous comment",
16673
+ diffCommentNext: "Next comment",
16674
+ diffCommentPosition: "Comment {index} of {total}",
16675
+ diffCommentCounter: "{index}/{total}",
16676
+ diffExpandAll: "Expand all files",
16677
+ diffCollapseAll: "Collapse all files",
15760
16678
  diffRestore: "Restore diff panel",
15761
16679
  diffCommit: "Commit",
15762
16680
  diffCommitMenu: "Open commit actions",
@@ -15767,7 +16685,7 @@ window.__ModuleLoader__.load({
15767
16685
  diffPushDescription: "Push local commits to the remote branch.",
15768
16686
  diffPushAhead: "{count} unpushed commit(s)",
15769
16687
  diffPushCompleted: "Pushed commit {commit}",
15770
- diffGeneratingMessage: "Reading the repository and generating a commit message…",
16688
+ diffGeneratingMessage: "Suggesting a commit message. It only fills the fields you leave empty.",
15771
16689
  diffActionFailed: "Repository action failed.",
15772
16690
  diffIncludeUnstaged: "Include unstaged and untracked changes",
15773
16691
  diffCommitMessage: "Commit message",
@@ -15785,8 +16703,8 @@ window.__ModuleLoader__.load({
15785
16703
  diffSubmitting: "Working…",
15786
16704
  diffDone: "Done",
15787
16705
  diffCommitCompleted: "Created commit {commit}",
16706
+ diffPrCompleted: "Created the pull request — open it from the repository bar.",
15788
16707
  diffCommitPreserved: "Local commit {commit} was preserved.",
15789
- diffOpenPr: "Open pull request",
15790
16708
  diffMergePr: "Merge",
15791
16709
  repositoryMergeMenu: "Open merge options",
15792
16710
  diffMerge_merge: "Create a merge commit",
@@ -15881,6 +16799,20 @@ window.__ModuleLoader__.load({
15881
16799
  reviewCommentSubmit: "Comment",
15882
16800
  reviewCommentCancel: "Cancel",
15883
16801
  reviewCommentRemove: "Remove comment",
16802
+ reviewThreadReply: "Reply",
16803
+ reviewThreadBot: "Bot",
16804
+ reviewThreadAgo: "{age} ago",
16805
+ reviewThreadReplyTo: "Reply to @{author}",
16806
+ reviewThreadOpenOnGitHub: "Open on GitHub",
16807
+ reviewThreadSend: "Send reply",
16808
+ reviewThreadResolve: "Resolve",
16809
+ reviewThreadUnresolve: "Unresolve",
16810
+ reviewThreadResolved: "Resolved",
16811
+ reviewThreadOutdated: "Outdated",
16812
+ reviewThreadReplyPlaceholder: "Reply to this comment; type @ to mention someone",
16813
+ reviewThreadMention: "Mention a repository member",
16814
+ reviewThreadReplyFailed: "The reply could not be posted.",
16815
+ reviewThreadResolveFailed: "The thread could not be updated.",
15884
16816
  reviewCommentFailed: "The comment could not be saved.",
15885
16817
  reviewCommentsClear: "Clear all comments",
15886
16818
  reviewCommentsSend: "Send comments",
@@ -15926,8 +16858,11 @@ window.__ModuleLoader__.load({
15926
16858
  const inject = [
15927
16859
  "slots",
15928
16860
  "locale",
15929
- "conversationEvents",
16861
+ "remote",
16862
+ "remote.agentPresets",
15930
16863
  "sessions",
16864
+ "uiSession",
16865
+ "uiConversation",
15931
16866
  "workspaces",
15932
16867
  "inputTriggers",
15933
16868
  "conversation",
@@ -15935,6 +16870,14 @@ window.__ModuleLoader__.load({
15935
16870
  ];
15936
16871
  function apply(ctx) {
15937
16872
  const namespace = "settings.claude-code";
16873
+ const diagnostics = createClaudeDiagnosticsReporter();
16874
+ for (const finding of claudeBootCheckFindings({
16875
+ services: inject,
16876
+ resolve: (name) => ctx.get(name)
16877
+ })) diagnostics.report("boot-check", finding);
16878
+ ctx.effect(() => watchClaudeComposerBar((finding) => {
16879
+ diagnostics.report("boot-check", finding);
16880
+ }), "dsh-claude: composer style drift probe");
15938
16881
  ctx.effect(() => ctx.locale.register(namespace, {
15939
16882
  zh,
15940
16883
  en
@@ -15947,6 +16890,7 @@ window.__ModuleLoader__.load({
15947
16890
  const workspaces = ctx.get("workspaces");
15948
16891
  const conversation = ctx.get("conversation");
15949
16892
  const connection = ctx.get("connection");
16893
+ const remote = ctx.get("remote");
15950
16894
  /** Composer submit hook shared by the repository feedback affordances.
15951
16895
  * 'append' keeps any user draft and adds the prompt below it; 'idle'
15952
16896
  * submits only when the composer is empty and reports false otherwise. */
@@ -15964,14 +16908,18 @@ window.__ModuleLoader__.load({
15964
16908
  return true;
15965
16909
  };
15966
16910
  };
15967
- if (sessions !== void 0) ctx.effect(() => sessions.provide({
16911
+ const uiSession = ctx.get("uiSession");
16912
+ if (uiSession !== void 0) ctx.effect(() => uiSession.provide({
15968
16913
  hooks: ["claudeProjection"],
15969
16914
  resolve: (binding) => ({ hooks: { claudeProjection: projections.source(binding.sessionId) } })
15970
16915
  }), "dsh-claude: sidecar projection provider");
15971
16916
  ctx.effect(() => () => projections.dispose(), "dsh-claude: sidecar projection lifecycle");
15972
- ctx.effect(() => ctx.conversationEvents.register(claudeTurnDefinition), "dsh-claude: Claude turn marker");
15973
- ctx.effect(() => ctx.conversationEvents.register(claudeActivityStepDefinition), "dsh-claude: Claude activity flow node");
15974
- ctx.effect(() => ctx.conversationEvents.register(claudeActiveTasksDefinition), "dsh-claude: active Claude tasks node");
16917
+ const uiConversation = ctx.get("uiConversation");
16918
+ if (uiConversation !== void 0) {
16919
+ ctx.effect(() => uiConversation.events.register(claudeTurnDefinition), "dsh-claude: Claude turn marker");
16920
+ ctx.effect(() => uiConversation.events.register(claudeActivityStepDefinition), "dsh-claude: Claude activity flow node");
16921
+ ctx.effect(() => uiConversation.events.register(claudeActiveTasksDefinition), "dsh-claude: active Claude tasks node");
16922
+ }
15975
16923
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
15976
16924
  name: "conversation.chat.node",
15977
16925
  key: "claude-activity-step",
@@ -16002,9 +16950,13 @@ window.__ModuleLoader__.load({
16002
16950
  diffOpen.close();
16003
16951
  layout?.closeDetails();
16004
16952
  };
16005
- ctx.effect(() => ctx.slots.onEntryError((key, entry) => {
16953
+ ctx.effect(() => ctx.slots.onEntryError((key, entry, error) => {
16954
+ const id = entry.options.id === void 0 ? "" : ` id="${String(entry.options.id)}"`;
16955
+ const message = error instanceof Error ? `${error.message}
16956
+ ${error.stack ?? ""}` : String(error);
16957
+ diagnostics.report("slot-entry-crashed", `slot "${key}"${id}: ${message}`);
16006
16958
  if (key === "shell.overlay" && entry.options.id === "claude-diff-overlay") restoreDiff();
16007
- }), "dsh-claude: diff overlay recovery");
16959
+ }), "dsh-claude: Slot entry failure reporting");
16008
16960
  const openTasksPanel = (sessionId, turn) => {
16009
16961
  closePluginDetails();
16010
16962
  try {
@@ -16141,8 +17093,8 @@ window.__ModuleLoader__.load({
16141
17093
  openTasks: (turn) => openTasksPanel(sessionId, turn)
16142
17094
  })
16143
17095
  }, ClaudeActivityTail));
16144
- if (connection !== void 0) {
16145
- const roster = new AgentPresetRoster(connection.api.agentPresets);
17096
+ if (remote !== void 0) {
17097
+ const roster = new AgentPresetRoster(remote.agentPresets);
16146
17098
  const hostT = ctx.locale.bind("settings.agentPreset");
16147
17099
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
16148
17100
  name: "conversation.session.header.actions",
@@ -16236,12 +17188,59 @@ window.__ModuleLoader__.load({
16236
17188
  input.setDraft(current.trim() === "" ? text : `${current}\n\n${text}`);
16237
17189
  } }
16238
17190
  })));
17191
+ /** Compose the chat view the rewind control reads.
17192
+ *
17193
+ * Desktop 2.0 split what used to be one Session snapshot: the Controller's
17194
+ * `binding.session` kept `running`, while the assembled Chat nodes moved to
17195
+ * the Conversation binding's 'chat' target. Reading the old combined shape
17196
+ * threw on `snapshot.chat.order` and took the whole overlay entry down. */
17197
+ const chatSourceCache = /* @__PURE__ */ new Map();
17198
+ const claudeChatSource = (sessionId) => {
17199
+ const cached = chatSourceCache.get(sessionId);
17200
+ if (cached !== void 0) return cached;
17201
+ if (sessions === void 0 || uiConversation === void 0) return void 0;
17202
+ const session = sessions.binding(sessionId)?.session;
17203
+ if (session === void 0) return void 0;
17204
+ let chatTarget;
17205
+ try {
17206
+ chatTarget = uiConversation.binding(sessionId).target("chat");
17207
+ } catch {
17208
+ return;
17209
+ }
17210
+ let lastChat;
17211
+ let lastRunning;
17212
+ let view = EMPTY_CHAT_VIEW;
17213
+ const source = {
17214
+ subscribe(listener) {
17215
+ const dropChat = chatTarget.subscribe(listener);
17216
+ const dropSession = session.subscribe(listener);
17217
+ return () => {
17218
+ dropChat();
17219
+ dropSession();
17220
+ };
17221
+ },
17222
+ getSnapshot() {
17223
+ const chat = chatTarget.getSnapshot();
17224
+ const running = session.getSnapshot().running === true;
17225
+ if (chat === lastChat && running === lastRunning) return view;
17226
+ lastChat = chat;
17227
+ lastRunning = running;
17228
+ view = chat === void 0 ? EMPTY_CHAT_VIEW : {
17229
+ chat,
17230
+ running
17231
+ };
17232
+ return view;
17233
+ }
17234
+ };
17235
+ chatSourceCache.set(sessionId, source);
17236
+ return source;
17237
+ };
16239
17238
  if (sessions !== void 0) {
16240
17239
  const rewind = {
16241
17240
  t,
16242
17241
  currentSessionId: () => sessions.list.getSnapshot().current,
16243
17242
  subscribeSessions: (listener) => sessions.list.subscribe(listener),
16244
- chatOf: (sessionId) => sessions.binding(sessionId)?.session,
17243
+ chatOf: (sessionId) => claudeChatSource(sessionId),
16245
17244
  projectionOf: (sessionId) => projections.source(sessionId),
16246
17245
  ...conversation === void 0 ? {} : { setDraft: (sessionId, text) => {
16247
17246
  const scope = sessions.scope(sessionId);
@@ -16273,7 +17272,7 @@ window.__ModuleLoader__.load({
16273
17272
  };
16274
17273
  }
16275
17274
  }, ClaudeQueueDock));
16276
- if (sessions !== void 0 && workspaces !== void 0 && conversation !== void 0 && connection !== void 0) {
17275
+ if (sessions !== void 0 && workspaces !== void 0 && conversation !== void 0 && connection !== void 0 && remote !== void 0) {
16277
17276
  /** Attach a prepared worktree to its session without blocking the flow. */
16278
17277
  const bindLease = (leaseId, targetSessionId) => {
16279
17278
  if (leaseId === void 0) return;
@@ -16310,12 +17309,9 @@ window.__ModuleLoader__.load({
16310
17309
  const targetSessionId = await workspaces.connectWorkspace(workspace.workspaceId);
16311
17310
  const targetScope = sessions.scope(targetSessionId);
16312
17311
  if (targetScope === void 0) throw new Error(t("repositorySessionUnavailable"));
16313
- const presetResponse = await connection.api.agentPresets.select({
16314
- sessionId: targetSessionId,
16315
- agentPreset: "claude"
16316
- });
16317
- if (!presetResponse.result.ok) throw new Error(presetResponse.result.error.message);
16318
- sessions.noteAgentPreset(targetSessionId, presetResponse.result.value.agentPreset);
17312
+ const presetResponse = await remote.agentPresets.select(targetSessionId, "claude");
17313
+ if (!presetResponse.ok) throw new Error(presetResponse.error.message);
17314
+ sessions.noteAgentPreset(targetSessionId, presetResponse.value);
16319
17315
  const targetInput = conversation.input.for(targetScope);
16320
17316
  onProgress("transferring-draft");
16321
17317
  if (imageIds.length > 0 && !targetInput.addImages(imageIds)) throw new Error(t("repositoryDraftTransferFailed"));
@@ -16354,12 +17350,9 @@ window.__ModuleLoader__.load({
16354
17350
  const targetSessionId = await workspaces.connectWorkspace(workspace.workspaceId);
16355
17351
  const targetScope = sessions.scope(targetSessionId);
16356
17352
  if (targetScope === void 0) throw new Error(t("repositorySessionUnavailable"));
16357
- const presetResponse = await connection.api.agentPresets.select({
16358
- sessionId: targetSessionId,
16359
- agentPreset: "claude"
16360
- });
16361
- if (!presetResponse.result.ok) throw new Error(presetResponse.result.error.message);
16362
- sessions.noteAgentPreset(targetSessionId, presetResponse.result.value.agentPreset);
17353
+ const presetResponse = await remote.agentPresets.select(targetSessionId, "claude");
17354
+ if (!presetResponse.ok) throw new Error(presetResponse.error.message);
17355
+ sessions.noteAgentPreset(targetSessionId, presetResponse.value);
16363
17356
  const targetInput = conversation.input.for(targetScope);
16364
17357
  report("transferring-draft");
16365
17358
  targetInput.setDraft(rawDraft.trim() === "" ? ticketPrompt(ticket) : `${rawDraft.trimEnd()}\n\n${ticketContext(ticket)}`);