@norman-else/dsh-claude 0.1.25 → 0.1.26

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
@@ -20,7 +20,9 @@ window.__ModuleLoader__.load({
20
20
  const CLAUDE_REVIEW_COMMENT_PATH = "/plugins/dsh-claude/review-comments";
21
21
  const CLAUDE_REPOSITORY_FEEDBACK_PATH = "/plugins/dsh-claude/repository/feedback";
22
22
  const CLAUDE_REPOSITORY_STATUS_PATH = "/plugins/dsh-claude/repository/status";
23
+ const CLAUDE_REPOSITORY_FILE_PATH = "/plugins/dsh-claude/repository/file";
23
24
  const CLAUDE_JIRA_PATH = "/plugins/dsh-claude/jira";
25
+ const CLAUDE_ASK_PATH = "/plugins/dsh-claude/ask";
24
26
  //#endregion
25
27
  //#region src/client/task-projection.ts
26
28
  /** Tasks UI is reserved for detached work and genuine Claude subagents. */
@@ -751,16 +753,33 @@ window.__ModuleLoader__.load({
751
753
  const tasksPanel = {
752
754
  display: "flex",
753
755
  flexDirection: "column",
754
- width: "calc(100% - 16px)",
755
- height: "calc(100% - 16px)",
756
756
  minWidth: 0,
757
- margin: 8,
758
757
  overflow: "hidden",
759
758
  border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
760
759
  borderRadius: 12,
761
760
  background: "var(--dsw-alias-bg-layer-1)",
762
761
  boxShadow: "0 4px 16px color-mix(in srgb, #000 12%, transparent)"
763
762
  };
763
+ const detailsCardClass = "dshClaudeDetailsCard";
764
+ /**
765
+ * Geometry shared by every details-column card. Lives in a stylesheet rather
766
+ * than inline so the DSH Desktop macOS shell can be special-cased: its
767
+ * "advanced" frame floats a 32px opaque caption/drag row over the top of the
768
+ * conversation and details columns, 12px of which lands inside the column and
769
+ * used to cover the card's top border and corners.
770
+ */
771
+ const detailsCardCss = `
772
+ .${detailsCardClass} {
773
+ box-sizing: border-box;
774
+ width: calc(100% - 16px);
775
+ height: calc(100% - 16px);
776
+ margin: 8px;
777
+ }
778
+ .dshDesktopFrame[data-desktop-mode="advanced"][data-desktop-platform="darwin"] .dshDesktopDetailsSurface .${detailsCardClass} {
779
+ height: calc(100% - 28px);
780
+ margin-top: 20px;
781
+ }
782
+ `;
764
783
  const tasksHeader = {
765
784
  boxSizing: "border-box",
766
785
  height: 58,
@@ -1131,7 +1150,7 @@ window.__ModuleLoader__.load({
1131
1150
  background: "transparent",
1132
1151
  color: "inherit",
1133
1152
  font: "inherit",
1134
- fontSize: 12,
1153
+ fontSize: 13,
1135
1154
  lineHeight: "18px",
1136
1155
  cursor: "pointer"
1137
1156
  };
@@ -1178,7 +1197,7 @@ window.__ModuleLoader__.load({
1178
1197
  background: "transparent",
1179
1198
  color: "var(--dsw-alias-label-primary)",
1180
1199
  font: "inherit",
1181
- fontSize: 12,
1200
+ fontSize: 13,
1182
1201
  lineHeight: "18px"
1183
1202
  };
1184
1203
  const heroBranchList = {
@@ -1190,8 +1209,8 @@ window.__ModuleLoader__.load({
1190
1209
  };
1191
1210
  const heroBranchItem = {
1192
1211
  width: "100%",
1193
- height: 34,
1194
- minHeight: 34,
1212
+ height: 36,
1213
+ minHeight: 36,
1195
1214
  display: "flex",
1196
1215
  alignItems: "center",
1197
1216
  gap: 8,
@@ -1201,7 +1220,7 @@ window.__ModuleLoader__.load({
1201
1220
  background: "transparent",
1202
1221
  color: "var(--dsw-alias-label-primary)",
1203
1222
  font: "inherit",
1204
- fontSize: 12,
1223
+ fontSize: 13,
1205
1224
  lineHeight: "18px",
1206
1225
  textAlign: "left",
1207
1226
  cursor: "pointer"
@@ -1215,11 +1234,11 @@ window.__ModuleLoader__.load({
1215
1234
  whiteSpace: "nowrap"
1216
1235
  };
1217
1236
  const heroBranchEmpty = {
1218
- height: 34,
1237
+ height: 36,
1219
1238
  display: "grid",
1220
1239
  placeItems: "center",
1221
1240
  color: "var(--dsw-alias-label-tertiary)",
1222
- fontSize: 12
1241
+ fontSize: 13
1223
1242
  };
1224
1243
  const heroRepositoryDivider = {
1225
1244
  width: 1,
@@ -1239,7 +1258,7 @@ window.__ModuleLoader__.load({
1239
1258
  background: "transparent",
1240
1259
  color: "var(--dsw-alias-label-secondary)",
1241
1260
  font: "inherit",
1242
- fontSize: 12,
1261
+ fontSize: 13,
1243
1262
  lineHeight: "18px",
1244
1263
  whiteSpace: "nowrap",
1245
1264
  cursor: "pointer",
@@ -1332,6 +1351,10 @@ window.__ModuleLoader__.load({
1332
1351
  lineHeight: "18px",
1333
1352
  overflowWrap: "anywhere"
1334
1353
  };
1354
+ const heroWorktreeProgressContext = {
1355
+ color: "var(--dsw-alias-label-secondary)",
1356
+ fontWeight: 500
1357
+ };
1335
1358
  const heroWorktreeProgressDismiss = {
1336
1359
  width: 24,
1337
1360
  height: 24,
@@ -1691,6 +1714,14 @@ window.__ModuleLoader__.load({
1691
1714
  borderRadius: 999,
1692
1715
  background: "currentColor"
1693
1716
  };
1717
+ const repositoryGlyph = {
1718
+ width: 16,
1719
+ height: 16,
1720
+ flex: "none",
1721
+ display: "grid",
1722
+ placeItems: "center",
1723
+ color: "var(--dsw-alias-label-tertiary)"
1724
+ };
1694
1725
  const repositoryItemSuccess = { color: "var(--dsw-alias-state-success-primary)" };
1695
1726
  const repositoryItemWarning = { color: "var(--dsw-alias-state-warning-primary, #d69e2e)" };
1696
1727
  const repositoryItemError = { color: "var(--dsw-alias-state-error-primary)" };
@@ -1909,19 +1940,77 @@ window.__ModuleLoader__.load({
1909
1940
  fontWeight: 650,
1910
1941
  cursor: "pointer"
1911
1942
  };
1943
+ /** Ticket summaries need room; the branch menu's 280px truncates them badly. */
1944
+ const heroTicketMenu = { width: 420 };
1912
1945
  const heroTicketKey = {
1913
1946
  flex: "none",
1914
- color: "var(--dsw-alias-label-tertiary)",
1915
- fontSize: 11,
1947
+ color: "var(--dsw-alias-label-secondary)",
1948
+ fontSize: 12,
1916
1949
  fontWeight: 650,
1917
1950
  letterSpacing: .2
1918
1951
  };
1952
+ const heroTicketChips = {
1953
+ display: "flex",
1954
+ flexWrap: "wrap",
1955
+ alignItems: "center",
1956
+ gap: 6,
1957
+ padding: "2px 2px 6px",
1958
+ borderBottom: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 12%, transparent))"
1959
+ };
1960
+ const heroTicketChip = {
1961
+ display: "inline-flex",
1962
+ alignItems: "center",
1963
+ gap: 4,
1964
+ padding: "2px 4px 2px 8px",
1965
+ border: "1px solid color-mix(in srgb, var(--dsw-static-blue-450) 40%, transparent)",
1966
+ borderRadius: 999,
1967
+ background: "color-mix(in srgb, var(--dsw-static-blue-450) 12%, transparent)",
1968
+ color: "var(--dsw-alias-label-primary)",
1969
+ fontSize: 12,
1970
+ fontWeight: 600,
1971
+ letterSpacing: .2
1972
+ };
1973
+ const heroTicketChipRemove = {
1974
+ width: 16,
1975
+ height: 16,
1976
+ display: "grid",
1977
+ placeItems: "center",
1978
+ padding: 0,
1979
+ border: "none",
1980
+ borderRadius: 999,
1981
+ background: "transparent",
1982
+ color: "var(--dsw-alias-label-tertiary)",
1983
+ fontSize: 12,
1984
+ lineHeight: 1,
1985
+ cursor: "pointer"
1986
+ };
1987
+ const heroTicketChipsClear = {
1988
+ padding: "2px 4px",
1989
+ border: "none",
1990
+ background: "transparent",
1991
+ color: "var(--dsw-alias-label-tertiary)",
1992
+ fontSize: 12,
1993
+ cursor: "pointer"
1994
+ };
1995
+ const heroTicketCheckbox = {
1996
+ width: 15,
1997
+ height: 15,
1998
+ boxSizing: "border-box",
1999
+ display: "grid",
2000
+ placeItems: "center",
2001
+ flex: "none",
2002
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2003
+ borderRadius: 4,
2004
+ background: "var(--dsw-alias-bg-layer-2)",
2005
+ color: "var(--dsw-alias-label-on-accent, #fff)",
2006
+ transition: "border-color 120ms ease, background 120ms ease"
2007
+ };
1919
2008
  const heroTicketStatus = {
1920
2009
  flex: "none",
1921
- maxWidth: 90,
2010
+ maxWidth: 110,
1922
2011
  overflow: "hidden",
1923
2012
  color: "var(--dsw-alias-label-tertiary)",
1924
- fontSize: 11,
2013
+ fontSize: 12,
1925
2014
  textOverflow: "ellipsis",
1926
2015
  whiteSpace: "nowrap"
1927
2016
  };
@@ -2008,6 +2097,197 @@ window.__ModuleLoader__.load({
2008
2097
  fontSize: 13,
2009
2098
  textAlign: "center"
2010
2099
  };
2100
+ const askSurfaceBorder = "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))";
2101
+ const askSurfaceShadow = "var(--dsw-shadow-lv3, 0 8px 24px color-mix(in srgb, #000 24%, transparent))";
2102
+ /** Painted through CSS.highlights while the follow-up popup is open. */
2103
+ const askHighlightCss = `
2104
+ ::highlight(dsh-claude-ask) {
2105
+ background-color: color-mix(in srgb, var(--dsw-static-blue-450) 30%, transparent);
2106
+ color: inherit;
2107
+ }
2108
+ `;
2109
+ const askToolbar = {
2110
+ position: "fixed",
2111
+ zIndex: 2e3,
2112
+ pointerEvents: "auto",
2113
+ display: "inline-flex",
2114
+ alignItems: "center",
2115
+ gap: 2,
2116
+ padding: 3,
2117
+ border: askSurfaceBorder,
2118
+ borderRadius: 9,
2119
+ background: "var(--dsw-alias-bg-layer-1)",
2120
+ color: "var(--dsw-alias-label-primary)",
2121
+ boxShadow: askSurfaceShadow
2122
+ };
2123
+ const askPopup = {
2124
+ position: "fixed",
2125
+ zIndex: 2e3,
2126
+ pointerEvents: "auto",
2127
+ boxSizing: "border-box",
2128
+ display: "flex",
2129
+ flexDirection: "column",
2130
+ gap: 10,
2131
+ padding: 12,
2132
+ border: askSurfaceBorder,
2133
+ borderRadius: 12,
2134
+ background: "var(--dsw-alias-bg-layer-1)",
2135
+ color: "var(--dsw-alias-label-primary)",
2136
+ boxShadow: askSurfaceShadow,
2137
+ fontSize: 13,
2138
+ lineHeight: "20px"
2139
+ };
2140
+ const askQuote = {
2141
+ flex: "none",
2142
+ display: "-webkit-box",
2143
+ WebkitLineClamp: 3,
2144
+ WebkitBoxOrient: "vertical",
2145
+ margin: 0,
2146
+ padding: "6px 10px",
2147
+ overflow: "hidden",
2148
+ borderLeft: "3px solid color-mix(in srgb, var(--dsw-static-blue-450) 60%, transparent)",
2149
+ borderRadius: 6,
2150
+ background: "var(--dsw-alias-bg-layer-2)",
2151
+ color: "var(--dsw-alias-label-secondary)",
2152
+ fontSize: 12,
2153
+ lineHeight: "18px",
2154
+ whiteSpace: "pre-wrap",
2155
+ wordBreak: "break-word"
2156
+ };
2157
+ const askQuestion = {
2158
+ flex: "none",
2159
+ margin: 0,
2160
+ color: "var(--dsw-alias-label-primary)",
2161
+ fontWeight: 600,
2162
+ whiteSpace: "pre-wrap",
2163
+ wordBreak: "break-word"
2164
+ };
2165
+ const askTextarea = {
2166
+ width: "100%",
2167
+ minHeight: 64,
2168
+ boxSizing: "border-box",
2169
+ padding: "8px 10px",
2170
+ border: askSurfaceBorder,
2171
+ borderRadius: 8,
2172
+ outline: "none",
2173
+ background: "var(--dsw-alias-bg-layer-2)",
2174
+ color: "var(--dsw-alias-label-primary)",
2175
+ font: "inherit",
2176
+ fontSize: 13,
2177
+ lineHeight: "20px",
2178
+ resize: "vertical"
2179
+ };
2180
+ const askActions = {
2181
+ flex: "none",
2182
+ display: "flex",
2183
+ alignItems: "center",
2184
+ justifyContent: "flex-end",
2185
+ gap: 6
2186
+ };
2187
+ const askAnswer = {
2188
+ flex: "1 1 auto",
2189
+ minHeight: 0,
2190
+ overflowY: "auto",
2191
+ padding: "2px 2px 0",
2192
+ wordBreak: "break-word"
2193
+ };
2194
+ const askButton = {
2195
+ minHeight: 26,
2196
+ padding: "3px 10px",
2197
+ border: askSurfaceBorder,
2198
+ borderRadius: 7,
2199
+ outline: "none",
2200
+ background: "var(--dsw-alias-bg-layer-2)",
2201
+ color: "var(--dsw-alias-label-primary)",
2202
+ font: "inherit",
2203
+ fontSize: 12,
2204
+ lineHeight: "18px",
2205
+ fontWeight: 600,
2206
+ cursor: "pointer"
2207
+ };
2208
+ const askPrimaryButton = {
2209
+ border: "1px solid transparent",
2210
+ background: "var(--dsw-static-blue-450)",
2211
+ color: "var(--dsw-alias-label-on-accent, #fff)"
2212
+ };
2213
+ const askThinkingRow = {
2214
+ width: "100%",
2215
+ display: "flex",
2216
+ alignItems: "baseline",
2217
+ gap: 6,
2218
+ margin: "0 0 8px",
2219
+ padding: 0,
2220
+ border: "none",
2221
+ outline: "none",
2222
+ background: "transparent",
2223
+ color: "var(--dsw-alias-label-tertiary)",
2224
+ font: "inherit",
2225
+ fontSize: 12,
2226
+ lineHeight: "18px",
2227
+ textAlign: "left",
2228
+ cursor: "pointer"
2229
+ };
2230
+ const askThinkingLabel = {
2231
+ flex: "none",
2232
+ fontWeight: 600
2233
+ };
2234
+ const askThinkingPreview = {
2235
+ minWidth: 0,
2236
+ flex: 1,
2237
+ overflow: "hidden",
2238
+ textOverflow: "ellipsis",
2239
+ whiteSpace: "nowrap"
2240
+ };
2241
+ const askThinkingFull = {
2242
+ minWidth: 0,
2243
+ flex: 1,
2244
+ maxHeight: 200,
2245
+ overflowY: "auto",
2246
+ whiteSpace: "pre-wrap",
2247
+ wordBreak: "break-word"
2248
+ };
2249
+ const askToolRow = {
2250
+ display: "flex",
2251
+ alignItems: "baseline",
2252
+ gap: 6,
2253
+ margin: "2px 0 6px",
2254
+ minWidth: 0,
2255
+ color: "var(--dsw-alias-label-tertiary)",
2256
+ fontSize: 12,
2257
+ lineHeight: "18px"
2258
+ };
2259
+ const askToolGlyph = {
2260
+ flex: "none",
2261
+ width: 14,
2262
+ textAlign: "center",
2263
+ color: "var(--dsw-static-blue-450)"
2264
+ };
2265
+ const askToolGlyphDone = { color: "var(--dsw-alias-state-success-primary)" };
2266
+ const askToolGlyphError = { color: "var(--dsw-alias-state-error-primary)" };
2267
+ const askToolName = {
2268
+ flex: "none",
2269
+ color: "var(--dsw-alias-label-secondary)",
2270
+ fontWeight: 600
2271
+ };
2272
+ const askToolSummary = {
2273
+ minWidth: 0,
2274
+ flex: 1,
2275
+ overflow: "hidden",
2276
+ fontFamily: "var(--dsw-font-family-code, ui-monospace, monospace)",
2277
+ fontSize: 11,
2278
+ textOverflow: "ellipsis",
2279
+ whiteSpace: "nowrap"
2280
+ };
2281
+ const askStatus = {
2282
+ marginRight: "auto",
2283
+ color: "var(--dsw-alias-label-tertiary)",
2284
+ fontSize: 12
2285
+ };
2286
+ const askError = {
2287
+ margin: 0,
2288
+ color: "var(--dsw-alias-state-error-primary)",
2289
+ fontSize: 12
2290
+ };
2011
2291
  const repositoryMergeTrigger = {
2012
2292
  flex: "none",
2013
2293
  display: "inline-flex",
@@ -2231,10 +2511,7 @@ window.__ModuleLoader__.load({
2231
2511
  const diffPanel = {
2232
2512
  display: "flex",
2233
2513
  flexDirection: "column",
2234
- width: "calc(100% - 16px)",
2235
- height: "calc(100% - 16px)",
2236
2514
  minWidth: 0,
2237
- margin: 8,
2238
2515
  overflow: "hidden",
2239
2516
  border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2240
2517
  borderRadius: 12,
@@ -2349,26 +2626,41 @@ window.__ModuleLoader__.load({
2349
2626
  background: "var(--dsw-alias-bg-layer-1)",
2350
2627
  color: "var(--dsw-alias-label-primary)",
2351
2628
  font: "inherit",
2352
- fontSize: 12,
2629
+ fontSize: 13,
2630
+ lineHeight: "20px",
2353
2631
  textAlign: "left",
2354
2632
  cursor: "pointer"
2355
2633
  };
2356
2634
  const diffFilePath = {
2357
2635
  minWidth: 0,
2358
2636
  flex: 1,
2637
+ display: "flex",
2638
+ alignItems: "baseline",
2639
+ overflow: "hidden"
2640
+ };
2641
+ /** Directory part: truncated from the left so the nearest folders stay visible. */
2642
+ const diffFileDir = {
2643
+ minWidth: 0,
2359
2644
  overflow: "hidden",
2360
2645
  textOverflow: "ellipsis",
2646
+ whiteSpace: "nowrap",
2647
+ direction: "rtl",
2648
+ textAlign: "left",
2649
+ color: "var(--dsw-alias-label-tertiary)"
2650
+ };
2651
+ const diffFileName = {
2652
+ flex: "none",
2653
+ fontWeight: 650,
2361
2654
  whiteSpace: "nowrap"
2362
2655
  };
2363
2656
  const diffFileStats = {
2364
2657
  flex: "none",
2365
2658
  display: "inline-flex",
2366
2659
  gap: 4,
2367
- fontSize: 12,
2660
+ fontSize: 13,
2368
2661
  fontWeight: 650
2369
2662
  };
2370
2663
  const diffCode = {
2371
- minWidth: "max-content",
2372
2664
  padding: "4px 0 8px",
2373
2665
  background: "var(--dsw-alias-bg-base)",
2374
2666
  fontFamily: "var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Consolas, monospace)",
@@ -2378,7 +2670,12 @@ window.__ModuleLoader__.load({
2378
2670
  const diffLine = {
2379
2671
  minHeight: 17,
2380
2672
  display: "flex",
2381
- whiteSpace: "pre"
2673
+ whiteSpace: "pre-wrap",
2674
+ overflowWrap: "anywhere"
2675
+ };
2676
+ const diffLineText = {
2677
+ flex: 1,
2678
+ minWidth: 0
2382
2679
  };
2383
2680
  const diffLineNumber = {
2384
2681
  width: 38,
@@ -2408,6 +2705,40 @@ window.__ModuleLoader__.load({
2408
2705
  color: "var(--dsw-alias-label-tertiary)",
2409
2706
  fontStyle: "italic"
2410
2707
  };
2708
+ const diffLineSelected = {
2709
+ background: "color-mix(in srgb, var(--dsw-static-blue-450) 14%, transparent)",
2710
+ boxShadow: "inset 2px 0 0 var(--dsw-static-blue-450)"
2711
+ };
2712
+ /** Sits where the comment button, line number and marker would be so the label aligns with code. */
2713
+ const diffGapControls = {
2714
+ width: 84,
2715
+ flex: "none",
2716
+ boxSizing: "border-box",
2717
+ display: "inline-flex",
2718
+ alignItems: "center",
2719
+ justifyContent: "flex-end",
2720
+ gap: 2,
2721
+ paddingRight: 6
2722
+ };
2723
+ const diffGapButton = {
2724
+ width: 20,
2725
+ height: 18,
2726
+ display: "grid",
2727
+ placeItems: "center",
2728
+ padding: 0,
2729
+ border: "none",
2730
+ borderRadius: 4,
2731
+ background: "transparent",
2732
+ color: "var(--dsw-alias-label-secondary)",
2733
+ font: "inherit",
2734
+ cursor: "pointer"
2735
+ };
2736
+ const diffCommentRange = {
2737
+ marginBottom: 6,
2738
+ color: "var(--dsw-alias-label-secondary)",
2739
+ fontSize: 12,
2740
+ fontWeight: 600
2741
+ };
2411
2742
  const diffLineContext = { color: "var(--dsw-alias-label-secondary)" };
2412
2743
  const diffNotice = {
2413
2744
  margin: 10,
@@ -2781,11 +3112,12 @@ window.__ModuleLoader__.load({
2781
3112
  const clearFinished = () => setDismissedSettledIds((previous) => /* @__PURE__ */ new Set([...previous, ...tasks.filter((task) => task.status !== "running").map((task) => task.taskId)]));
2782
3113
  if (!projection.owned) return null;
2783
3114
  return /* @__PURE__ */ jsxs("div", {
3115
+ className: detailsCardClass,
2784
3116
  style: tasksPanel,
2785
3117
  children: [
2786
- /* @__PURE__ */ jsx("style", {
3118
+ /* @__PURE__ */ jsxs("style", {
2787
3119
  "data-dsh-claude-panel-icon-styles": true,
2788
- children: panelIconButtonCss
3120
+ children: [detailsCardCss, panelIconButtonCss]
2789
3121
  }),
2790
3122
  /* @__PURE__ */ jsxs("div", {
2791
3123
  style: tasksHeader,
@@ -3018,7 +3350,7 @@ window.__ModuleLoader__.load({
3018
3350
  const MAX_REVIEW_COMMENTS = 50;
3019
3351
  const MAX_REVIEW_COMMENT_CHARS = 2e3;
3020
3352
  const MAX_TRANSCRIPT_CHARS = 64e3;
3021
- function record$6(value) {
3353
+ function record$7(value) {
3022
3354
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3023
3355
  }
3024
3356
  function nonNegativeInteger(value) {
@@ -3028,18 +3360,18 @@ window.__ModuleLoader__.load({
3028
3360
  return value === void 0 || typeof value === "string" && value.length <= MAX_REPOSITORY_TEXT_CHARS;
3029
3361
  }
3030
3362
  function validateRepository(value) {
3031
- const repository = record$6(value);
3363
+ const repository = record$7(value);
3032
3364
  if (repository === void 0 || ![
3033
3365
  "ready",
3034
3366
  "not-repository",
3035
3367
  "unavailable"
3036
3368
  ].includes(String(repository.status)) || typeof repository.cwd !== "string" || repository.cwd.length > MAX_REPOSITORY_TEXT_CHARS || !optionalBoundedString(repository.root) || !optionalBoundedString(repository.branch) || !optionalBoundedString(repository.remote) || repository.detached !== void 0 && typeof repository.detached !== "boolean" || repository.worktree !== void 0 && typeof repository.worktree !== "boolean" || repository.dirty !== void 0 && typeof repository.dirty !== "boolean" || repository.upstream !== void 0 && typeof repository.upstream !== "boolean" || repository.ahead !== void 0 && !nonNegativeInteger(repository.ahead) || repository.behind !== void 0 && !nonNegativeInteger(repository.behind)) return false;
3037
3369
  if (repository.diff !== void 0) {
3038
- const diff = record$6(repository.diff);
3370
+ const diff = record$7(repository.diff);
3039
3371
  if (diff === void 0 || !nonNegativeInteger(diff.additions) || !nonNegativeInteger(diff.deletions) || !nonNegativeInteger(diff.files) || typeof diff.truncated !== "boolean" || diff.patch !== void 0 && (typeof diff.patch !== "string" || diff.patch.length > MAX_DIFF_CHARS)) return false;
3040
3372
  }
3041
3373
  if (repository.pullRequest === void 0) return true;
3042
- const pullRequest = record$6(repository.pullRequest);
3374
+ const pullRequest = record$7(repository.pullRequest);
3043
3375
  if (pullRequest === void 0 || !Number.isSafeInteger(pullRequest.number) || Number(pullRequest.number) <= 0 || typeof pullRequest.title !== "string" || pullRequest.title.length > MAX_REPOSITORY_TEXT_CHARS || typeof pullRequest.url !== "string" || pullRequest.url.length > MAX_REPOSITORY_TEXT_CHARS || ![
3044
3376
  "open",
3045
3377
  "closed",
@@ -3064,24 +3396,24 @@ window.__ModuleLoader__.load({
3064
3396
  }
3065
3397
  /** Validate the public route envelope before publishing it to UI components. */
3066
3398
  function parseClaudeClientProjection(value) {
3067
- const input = record$6(value);
3399
+ const input = record$7(value);
3068
3400
  if (input === void 0 || input.schemaVersion !== 1 || !nonNegativeInteger(input.revision) || typeof input.owned !== "boolean" || !Array.isArray(input.commands) || input.commands.length > MAX_COMMANDS || !Array.isArray(input.activities) || input.activities.length > MAX_ACTIVITIES) throw new Error("invalid Claude sidecar projection");
3069
3401
  for (const item of input.commands) {
3070
- const command = record$6(item);
3402
+ const command = record$7(item);
3071
3403
  if (command === void 0 || typeof command.publicName !== "string" || typeof command.claudeName !== "string" || typeof command.description !== "string" || command.hint !== void 0 && typeof command.hint !== "string" || typeof command.prefixed !== "boolean") throw new Error("invalid Claude command projection");
3072
3404
  }
3073
3405
  for (const item of input.activities) {
3074
- const activity = record$6(item);
3406
+ const activity = record$7(item);
3075
3407
  if (activity === void 0 || !nonNegativeInteger(activity.turn) || !nonNegativeInteger(activity.step) || !nonNegativeInteger(activity.ordinal) || typeof activity.kind !== "string") throw new Error("invalid Claude sidecar activity");
3076
3408
  }
3077
- if (input.contextUsage !== void 0 && record$6(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
3078
- const tasks = input.tasks === void 0 ? void 0 : record$6(input.tasks);
3409
+ if (input.contextUsage !== void 0 && record$7(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
3410
+ const tasks = input.tasks === void 0 ? void 0 : record$7(input.tasks);
3079
3411
  if (tasks !== void 0 && !Array.isArray(tasks.tasks)) throw new Error("invalid Claude tasks projection");
3080
3412
  if (input.repository !== void 0 && !validateRepository(input.repository)) throw new Error("invalid Claude repository projection");
3081
3413
  if (input.reviewComments !== void 0) {
3082
3414
  if (!Array.isArray(input.reviewComments) || input.reviewComments.length > MAX_REVIEW_COMMENTS) throw new Error("invalid Claude review comment projection");
3083
3415
  for (const item of input.reviewComments) {
3084
- const comment = record$6(item);
3416
+ const comment = record$7(item);
3085
3417
  if (comment === void 0 || typeof comment.id !== "string" || comment.id.length === 0 || comment.id.length > 128 || typeof comment.path !== "string" || comment.path.length === 0 || comment.path.length > MAX_REPOSITORY_TEXT_CHARS || !nonNegativeInteger(comment.line) || comment.side !== "old" && comment.side !== "new" || typeof comment.text !== "string" || comment.text.length > MAX_REVIEW_COMMENT_CHARS) throw new Error("invalid Claude review comment projection");
3086
3418
  }
3087
3419
  }
@@ -3279,7 +3611,7 @@ window.__ModuleLoader__.load({
3279
3611
  } catch {
3280
3612
  return;
3281
3613
  }
3282
- const event = record$6(value);
3614
+ const event = record$7(value);
3283
3615
  if (event === void 0 || typeof event.type !== "string") return;
3284
3616
  try {
3285
3617
  switch (event.type) {
@@ -3538,7 +3870,7 @@ window.__ModuleLoader__.load({
3538
3870
  return value;
3539
3871
  }
3540
3872
  }
3541
- function record$5(value) {
3873
+ function record$6(value) {
3542
3874
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3543
3875
  }
3544
3876
  function text(value) {
@@ -3552,7 +3884,7 @@ window.__ModuleLoader__.load({
3552
3884
  if (typeof value === "string") return value;
3553
3885
  if (typeof value === "number" || typeof value === "boolean") return String(value);
3554
3886
  if (Array.isArray(value)) return value.map(displayValue).join(", ");
3555
- return record$5(value) === void 0 ? String(value) : Object.entries(value).map(([key, item]) => `${key}: ${displayValue(item)}`).join("\n");
3887
+ return record$6(value) === void 0 ? String(value) : Object.entries(value).map(([key, item]) => `${key}: ${displayValue(item)}`).join("\n");
3556
3888
  }
3557
3889
  function Section({ title, children }) {
3558
3890
  return /* @__PURE__ */ jsxs("section", {
@@ -3622,15 +3954,15 @@ window.__ModuleLoader__.load({
3622
3954
  function ToolPresentation({ tool, t }) {
3623
3955
  const inputValue = parsedValue(tool.input);
3624
3956
  const outputValue = parsedValue(tool.output);
3625
- const input = record$5(inputValue);
3626
- const output = record$5(outputValue);
3957
+ const input = record$6(inputValue);
3958
+ const output = record$6(outputValue);
3627
3959
  const outputTitle = tool.isError === true ? t("toolError") : t("toolOutput");
3628
3960
  if (tool.diffs !== void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DiffBlock, { diffs: [...tool.diffs] }), /* @__PURE__ */ jsx(TextDetail, {
3629
3961
  title: outputTitle,
3630
3962
  value: typeof outputValue === "string" ? outputValue : void 0
3631
3963
  })] });
3632
3964
  if (tool.toolName === "Read") {
3633
- const file = record$5(output?.file);
3965
+ const file = record$6(output?.file);
3634
3966
  const path = text(file?.filePath) ?? text(input?.file_path);
3635
3967
  const content = text(file?.content) ?? (typeof outputValue === "string" ? outputValue : void 0);
3636
3968
  const offset = numberValue(input?.offset) ?? 1;
@@ -3828,7 +4160,7 @@ window.__ModuleLoader__.load({
3828
4160
  if (code !== void 0) this.code = code;
3829
4161
  }
3830
4162
  };
3831
- function record$4(value) {
4163
+ function record$5(value) {
3832
4164
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3833
4165
  }
3834
4166
  async function call(path, init) {
@@ -3836,7 +4168,7 @@ window.__ModuleLoader__.load({
3836
4168
  credentials: "same-origin",
3837
4169
  ...init
3838
4170
  });
3839
- const body = record$4(await response.json());
4171
+ const body = record$5(await response.json());
3840
4172
  if (!response.ok) throw new JiraClientError(typeof body?.message === "string" ? body.message : "Jira is unavailable.", typeof body?.error === "string" ? body.error : void 0);
3841
4173
  if (body === void 0) throw new JiraClientError("Invalid Jira response.");
3842
4174
  return body;
@@ -3877,7 +4209,7 @@ window.__ModuleLoader__.load({
3877
4209
  if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
3878
4210
  const tickets = [];
3879
4211
  for (const item of body.tickets) {
3880
- const ticket = record$4(item);
4212
+ const ticket = record$5(item);
3881
4213
  if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
3882
4214
  tickets.push(ticket);
3883
4215
  }
@@ -4580,33 +4912,33 @@ window.__ModuleLoader__.load({
4580
4912
  if (commit !== void 0) this.commit = commit;
4581
4913
  }
4582
4914
  };
4583
- function record$3(value) {
4915
+ function record$4(value) {
4584
4916
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4585
4917
  }
4586
4918
  async function response$1(pending) {
4587
4919
  const result = await pending;
4588
4920
  const body = await result.json();
4589
4921
  if (!result.ok) {
4590
- const error = record$3(body);
4922
+ const error = record$4(body);
4591
4923
  throw new RepositoryActionClientError(typeof error?.message === "string" ? error.message : "Repository action failed.", typeof error?.error === "string" ? error.error : void 0, typeof error?.commit === "string" ? error.commit : void 0);
4592
4924
  }
4593
4925
  return body;
4594
4926
  }
4595
4927
  function preview(value) {
4596
- const input = record$3(value);
4928
+ const input = record$4(value);
4597
4929
  if (input === void 0 || typeof input.root !== "string" || typeof input.branch !== "string" || typeof input.head !== "string" || typeof input.fingerprint !== "string" || !Array.isArray(input.files) || typeof input.patch !== "string" || typeof input.truncated !== "boolean" || typeof input.hasStaged !== "boolean" || typeof input.hasUnstaged !== "boolean" || typeof input.hasUntracked !== "boolean" || input.upstream !== void 0 && typeof input.upstream !== "string" || !Array.isArray(input.unpushedCommits) || typeof input.unpushedTruncated !== "boolean") throw new Error("Invalid repository action preview.");
4598
4930
  for (const file of input.files) {
4599
- const item = record$3(file);
4931
+ const item = record$4(file);
4600
4932
  if (item === void 0 || typeof item.path !== "string" || typeof item.staged !== "boolean" || typeof item.unstaged !== "boolean" || typeof item.untracked !== "boolean") throw new Error("Invalid repository action file.");
4601
4933
  }
4602
4934
  for (const commit of input.unpushedCommits) {
4603
- const item = record$3(commit);
4935
+ const item = record$4(commit);
4604
4936
  if (item === void 0 || typeof item.hash !== "string" || typeof item.subject !== "string") throw new Error("Invalid repository action commit.");
4605
4937
  }
4606
4938
  return input;
4607
4939
  }
4608
4940
  function result(value) {
4609
- const input = record$3(value);
4941
+ const input = record$4(value);
4610
4942
  if (input === void 0 || typeof input.commit !== "string" || typeof input.pushed !== "boolean" || input.pullRequestUrl !== void 0 && typeof input.pullRequestUrl !== "string" || input.conflicts !== void 0 && (!Array.isArray(input.conflicts) || input.conflicts.some((item) => typeof item !== "string"))) throw new Error("Invalid repository action result.");
4611
4943
  return input;
4612
4944
  }
@@ -4622,7 +4954,7 @@ window.__ModuleLoader__.load({
4622
4954
  })));
4623
4955
  }
4624
4956
  async function generateCommitMessage(sessionId, fingerprint, signal) {
4625
- const value = record$3(await response$1(fetch(endpoint("/message", sessionId), {
4957
+ const value = record$4(await response$1(fetch(endpoint("/message", sessionId), {
4626
4958
  method: "POST",
4627
4959
  credentials: "same-origin",
4628
4960
  headers: {
@@ -4655,16 +4987,16 @@ window.__ModuleLoader__.load({
4655
4987
  "saving-worktree",
4656
4988
  "switching-branch"
4657
4989
  ]);
4658
- function record$2(value) {
4990
+ function record$3(value) {
4659
4991
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4660
4992
  }
4661
4993
  function setupResult(value) {
4662
- const item = record$2(value);
4994
+ const item = record$3(value);
4663
4995
  if (item === void 0 || item.mode !== "checkout" && item.mode !== "worktree" || typeof item.root !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || item.leaseId !== void 0 && typeof item.leaseId !== "string") return void 0;
4664
4996
  return item;
4665
4997
  }
4666
4998
  function parseRepositorySetupEvent(line, onProgress) {
4667
- const event = record$2(JSON.parse(line));
4999
+ const event = record$3(JSON.parse(line));
4668
5000
  if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
4669
5001
  onProgress(event.stage);
4670
5002
  return;
@@ -4766,6 +5098,21 @@ window.__ModuleLoader__.load({
4766
5098
  if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
4767
5099
  return body;
4768
5100
  }
5101
+ /** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
5102
+ async function loadRepositoryFileLines(cwd, path, from, to, signal) {
5103
+ const query = `cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(path)}&from=${from}&to=${to}`;
5104
+ const body = await response(fetch(`${CLAUDE_REPOSITORY_FILE_PATH}?${query}`, {
5105
+ method: "GET",
5106
+ credentials: "same-origin",
5107
+ headers: { accept: "application/json" },
5108
+ ...signal === void 0 ? {} : { signal }
5109
+ }));
5110
+ if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
5111
+ return {
5112
+ lines: body.lines.map(String),
5113
+ total: body.total
5114
+ };
5115
+ }
4769
5116
  async function loadRepositoryStatusFor(cwd, signal) {
4770
5117
  const body = await response(fetch(`${CLAUDE_REPOSITORY_STATUS_PATH}?cwd=${encodeURIComponent(cwd)}`, {
4771
5118
  method: "GET",
@@ -4778,7 +5125,7 @@ window.__ModuleLoader__.load({
4778
5125
  }
4779
5126
  //#endregion
4780
5127
  //#region src/client/pr-feedback-api.ts
4781
- function record$1(value) {
5128
+ function record$2(value) {
4782
5129
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4783
5130
  }
4784
5131
  async function loadJson(path, sessionId, pullNumber, signal) {
@@ -4788,7 +5135,7 @@ window.__ModuleLoader__.load({
4788
5135
  headers: { accept: "application/json" },
4789
5136
  ...signal === void 0 ? {} : { signal }
4790
5137
  });
4791
- const body = record$1(await response.json());
5138
+ const body = record$2(await response.json());
4792
5139
  if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
4793
5140
  if (body === void 0) throw new Error("Invalid pull request feedback response.");
4794
5141
  return body;
@@ -4798,7 +5145,7 @@ window.__ModuleLoader__.load({
4798
5145
  if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
4799
5146
  const comments = [];
4800
5147
  for (const item of body.comments) {
4801
- const input = record$1(item);
5148
+ const input = record$2(item);
4802
5149
  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.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number") continue;
4803
5150
  comments.push(input);
4804
5151
  }
@@ -4809,7 +5156,7 @@ window.__ModuleLoader__.load({
4809
5156
  if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
4810
5157
  const checks = [];
4811
5158
  for (const item of body.checks) {
4812
- const input = record$1(item);
5159
+ const input = record$2(item);
4813
5160
  if (input === void 0 || typeof input.name !== "string" || input.link !== void 0 && typeof input.link !== "string" || input.description !== void 0 && typeof input.description !== "string" || input.log !== void 0 && typeof input.log !== "string") continue;
4814
5161
  checks.push(input);
4815
5162
  }
@@ -4908,6 +5255,63 @@ window.__ModuleLoader__.load({
4908
5255
  })]
4909
5256
  });
4910
5257
  }
5258
+ /** Icon-only status: the tone carries the state, the full label lives in the tooltip and accessible name. */
5259
+ function StatusGlyph({ label, tone, children }) {
5260
+ const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : {};
5261
+ return /* @__PURE__ */ jsx(Tooltip, {
5262
+ label,
5263
+ side: "top",
5264
+ delayMs: 250,
5265
+ children: /* @__PURE__ */ jsx("span", {
5266
+ role: "img",
5267
+ "aria-label": label,
5268
+ style: {
5269
+ ...repositoryGlyph,
5270
+ ...toneStyle
5271
+ },
5272
+ children
5273
+ })
5274
+ });
5275
+ }
5276
+ function ChecksGlyph({ state }) {
5277
+ return /* @__PURE__ */ jsxs("svg", {
5278
+ width: "14",
5279
+ height: "14",
5280
+ viewBox: "0 0 14 14",
5281
+ fill: "none",
5282
+ stroke: "currentColor",
5283
+ strokeWidth: "1.5",
5284
+ strokeLinecap: "round",
5285
+ strokeLinejoin: "round",
5286
+ "aria-hidden": "true",
5287
+ children: [/* @__PURE__ */ jsx("circle", {
5288
+ cx: "7",
5289
+ cy: "7",
5290
+ r: "5.5"
5291
+ }), state === "passing" ? /* @__PURE__ */ jsx("path", { d: "M4.5 7.2l1.8 1.8 3.2-3.6" }) : state === "failing" ? /* @__PURE__ */ jsx("path", { d: "M5 5l4 4M9 5l-4 4" }) : /* @__PURE__ */ jsx("path", {
5292
+ d: "M4.6 7h.01M7 7h.01M9.4 7h.01",
5293
+ strokeWidth: "2"
5294
+ })]
5295
+ });
5296
+ }
5297
+ function ReviewGlyph() {
5298
+ return /* @__PURE__ */ jsxs("svg", {
5299
+ width: "14",
5300
+ height: "14",
5301
+ viewBox: "0 0 14 14",
5302
+ fill: "none",
5303
+ stroke: "currentColor",
5304
+ strokeWidth: "1.5",
5305
+ strokeLinecap: "round",
5306
+ strokeLinejoin: "round",
5307
+ "aria-hidden": "true",
5308
+ children: [/* @__PURE__ */ jsx("path", { d: "M1.5 7s2-3.5 5.5-3.5S12.5 7 12.5 7s-2 3.5-5.5 3.5S1.5 7 1.5 7Z" }), /* @__PURE__ */ jsx("circle", {
5309
+ cx: "7",
5310
+ cy: "7",
5311
+ r: "1.8"
5312
+ })]
5313
+ });
5314
+ }
4911
5315
  function PullRequestIcon({ size = 16, merged = false }) {
4912
5316
  if (merged) return /* @__PURE__ */ jsxs("svg", {
4913
5317
  width: size,
@@ -5233,9 +5637,10 @@ window.__ModuleLoader__.load({
5233
5637
  "aria-expanded": open,
5234
5638
  "aria-label": t("repositoryChecksOpen"),
5235
5639
  onClick: toggle,
5236
- children: /* @__PURE__ */ jsx(StatusItem, {
5640
+ children: /* @__PURE__ */ jsx(StatusGlyph, {
5237
5641
  label: t("repositoryChecks_failing"),
5238
- tone: "error"
5642
+ tone: "error",
5643
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: "failing" })
5239
5644
  })
5240
5645
  }), open ? /* @__PURE__ */ jsxs("span", {
5241
5646
  role: "dialog",
@@ -5857,13 +6262,15 @@ window.__ModuleLoader__.load({
5857
6262
  pullNumber: pullRequest.number,
5858
6263
  t,
5859
6264
  ...submitPrompt === void 0 ? {} : { submitPrompt }
5860
- }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusItem, {
6265
+ }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
5861
6266
  label: t(`repositoryChecks_${pullRequest.checks}`),
5862
- tone: pullRequest.checks === "passing" ? "success" : "warning"
6267
+ tone: pullRequest.checks === "passing" ? "success" : "warning",
6268
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
5863
6269
  }),
5864
- pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusItem, {
6270
+ pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
5865
6271
  label: t(`repositoryReview_${pullRequest.review}`),
5866
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
6272
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
6273
+ children: /* @__PURE__ */ jsx(ReviewGlyph, {})
5867
6274
  }),
5868
6275
  /* @__PURE__ */ jsx(AutoFixControl, {
5869
6276
  sessionId,
@@ -5892,7 +6299,7 @@ window.__ModuleLoader__.load({
5892
6299
  }
5893
6300
  //#endregion
5894
6301
  //#region src/client/review-comment-api.ts
5895
- function record(value) {
6302
+ function record$1(value) {
5896
6303
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5897
6304
  }
5898
6305
  async function post(path, sessionId, body) {
@@ -5907,13 +6314,13 @@ window.__ModuleLoader__.load({
5907
6314
  });
5908
6315
  const value = await response.json();
5909
6316
  if (!response.ok) {
5910
- const error = record(value);
6317
+ const error = record$1(value);
5911
6318
  throw new Error(typeof error?.message === "string" ? error.message : "Review comment request failed.");
5912
6319
  }
5913
6320
  return value;
5914
6321
  }
5915
6322
  async function addReviewComment(sessionId, comment) {
5916
- const created = record(record(await post("", sessionId, comment))?.comment);
6323
+ const created = record$1(record$1(await post("", sessionId, comment))?.comment);
5917
6324
  if (created === void 0 || typeof created.id !== "string") throw new Error("Invalid review comment response.");
5918
6325
  return created;
5919
6326
  }
@@ -5926,7 +6333,15 @@ window.__ModuleLoader__.load({
5926
6333
  //#endregion
5927
6334
  //#region src/client/ClaudeReviewComments.tsx
5928
6335
  function reviewCommentChipLabel(comment) {
5929
- return `${comment.path.split("/").at(-1) ?? comment.path}:${comment.line}`;
6336
+ return `${comment.path.split("/").at(-1) ?? comment.path}:${comment.startLine === void 0 ? comment.line : `${comment.startLine}-${comment.line}`}`;
6337
+ }
6338
+ /** "Line 12" / "Lines 10–12", with the old-side variant when the comment sits on removed code. */
6339
+ function commentLineLabel(comment, t) {
6340
+ if (comment.startLine !== void 0) return t(comment.side === "old" ? "reviewCommentOldRange" : "reviewCommentNewRange", {
6341
+ start: comment.startLine,
6342
+ end: comment.line
6343
+ });
6344
+ return t(comment.side === "old" ? "reviewCommentOldSide" : "reviewCommentNewSide", { line: comment.line });
5930
6345
  }
5931
6346
  function CommentIcon() {
5932
6347
  return /* @__PURE__ */ jsx("svg", {
@@ -5992,7 +6407,7 @@ window.__ModuleLoader__.load({
5992
6407
  children: [
5993
6408
  comment.path,
5994
6409
  " · ",
5995
- comment.side === "old" ? t("reviewCommentOldSide", { line: comment.line }) : t("reviewCommentNewSide", { line: comment.line })
6410
+ commentLineLabel(comment, t)
5996
6411
  ]
5997
6412
  }), /* @__PURE__ */ jsx("p", {
5998
6413
  style: reviewCommentHoverText,
@@ -6101,17 +6516,41 @@ window.__ModuleLoader__.load({
6101
6516
  let oldLine = 0;
6102
6517
  let newLine = 0;
6103
6518
  let previousOldEnd;
6519
+ let previousNewEnd = 0;
6520
+ let expandable = false;
6104
6521
  for (const line of lines) {
6105
6522
  const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/u.exec(line);
6106
6523
  if (hunk !== null) {
6107
6524
  const nextOld = Number(hunk[1]);
6108
- if (previousOldEnd !== void 0 && nextOld > previousOldEnd) numbered.push({
6109
- line: `${nextOld - previousOldEnd} unmodified lines`,
6110
- kind: "collapsed"
6525
+ const nextNew = Number(hunk[3]);
6526
+ const oldCount = Number(hunk[2] ?? 1);
6527
+ const newCount = Number(hunk[4] ?? 1);
6528
+ if (previousOldEnd === void 0) {
6529
+ expandable = oldCount > 0 && newCount > 0;
6530
+ if (expandable && nextOld > 1) numbered.push({
6531
+ line: "",
6532
+ kind: "collapsed",
6533
+ gap: {
6534
+ oldStart: 1,
6535
+ newStart: 1,
6536
+ count: nextOld - 1,
6537
+ position: "top"
6538
+ }
6539
+ });
6540
+ } else if (nextOld > previousOldEnd) numbered.push({
6541
+ line: "",
6542
+ kind: "collapsed",
6543
+ gap: {
6544
+ oldStart: previousOldEnd,
6545
+ newStart: previousNewEnd,
6546
+ count: nextOld - previousOldEnd,
6547
+ position: "middle"
6548
+ }
6111
6549
  });
6112
6550
  oldLine = nextOld;
6113
- newLine = Number(hunk[3]);
6114
- previousOldEnd = nextOld + Number(hunk[2] ?? 1);
6551
+ newLine = nextNew;
6552
+ previousOldEnd = nextOld + oldCount;
6553
+ previousNewEnd = nextNew + newCount;
6115
6554
  numbered.push({
6116
6555
  line,
6117
6556
  kind: "hunk"
@@ -6143,8 +6582,76 @@ window.__ModuleLoader__.load({
6143
6582
  newLine += 1;
6144
6583
  }
6145
6584
  }
6585
+ if (expandable && previousOldEnd !== void 0) numbered.push({
6586
+ line: "",
6587
+ kind: "collapsed",
6588
+ gap: {
6589
+ oldStart: previousOldEnd,
6590
+ newStart: previousNewEnd,
6591
+ position: "bottom"
6592
+ }
6593
+ });
6146
6594
  return numbered;
6147
6595
  }
6596
+ /**
6597
+ * Splice revealed working-tree lines (keyed by new-side line number) into the
6598
+ * collapsed gaps. Expansion only ever grows a gap's edges, so each gap is a
6599
+ * top run + remaining gap + bottom run; `total` (file line count) turns the
6600
+ * open-ended tail gap into a bounded one.
6601
+ */
6602
+ function expandDiffRows(rows, revealed, total) {
6603
+ const out = [];
6604
+ for (const row of rows) {
6605
+ const gap = row.gap;
6606
+ if (row.kind !== "collapsed" || gap === void 0) {
6607
+ out.push(row);
6608
+ continue;
6609
+ }
6610
+ const count = gap.count ?? (total === void 0 ? void 0 : Math.max(0, total - gap.newStart + 1));
6611
+ const context = (offset) => ({
6612
+ line: ` ${revealed.get(gap.newStart + offset) ?? ""}`,
6613
+ kind: "context",
6614
+ oldLine: gap.oldStart + offset,
6615
+ newLine: gap.newStart + offset
6616
+ });
6617
+ let top = 0;
6618
+ while ((count === void 0 || top < count) && revealed.has(gap.newStart + top)) top += 1;
6619
+ let bottom = 0;
6620
+ if (count !== void 0) while (bottom < count - top && revealed.has(gap.newStart + count - 1 - bottom)) bottom += 1;
6621
+ for (let offset = 0; offset < top; offset += 1) out.push(context(offset));
6622
+ const remaining = count === void 0 ? void 0 : count - top - bottom;
6623
+ if (remaining === void 0 || remaining > 0) out.push({
6624
+ ...row,
6625
+ gap: {
6626
+ ...gap,
6627
+ oldStart: gap.oldStart + top,
6628
+ newStart: gap.newStart + top,
6629
+ ...remaining === void 0 ? {} : { count: remaining }
6630
+ }
6631
+ });
6632
+ if (count !== void 0) for (let offset = count - bottom; offset < count; offset += 1) out.push(context(offset));
6633
+ }
6634
+ return out;
6635
+ }
6636
+ /**
6637
+ * Anchor for a drag from row `from` to row `to`: every commentable row in
6638
+ * between on the same side as the first row, collapsed to its first/last line.
6639
+ */
6640
+ function rangeCommentAnchor(anchors, from, to) {
6641
+ const side = anchors[from]?.side;
6642
+ if (side === void 0) return void 0;
6643
+ const lines = anchors.slice(Math.min(from, to), Math.max(from, to) + 1).flatMap((anchor) => anchor?.side === side ? [anchor.line] : []);
6644
+ const line = Math.max(...lines);
6645
+ const startLine = Math.min(...lines);
6646
+ return startLine < line ? {
6647
+ line,
6648
+ side,
6649
+ startLine
6650
+ } : {
6651
+ line,
6652
+ side
6653
+ };
6654
+ }
6148
6655
  /** Which working-tree line a comment on this rendered diff row refers to. */
6149
6656
  function commentAnchorForLine(entry) {
6150
6657
  if (entry.kind === "add" || entry.kind === "context") return entry.newLine === void 0 ? void 0 : {
@@ -6156,22 +6663,32 @@ window.__ModuleLoader__.load({
6156
6663
  side: "old"
6157
6664
  };
6158
6665
  }
6159
- function DiffLine({ entry, addLabel, onComment }) {
6160
- const style = entry.kind === "add" ? diffLineAdd : entry.kind === "delete" ? diffLineDelete : entry.kind === "hunk" || entry.kind === "collapsed" ? diffLineHunk : diffLineContext;
6666
+ function DiffLine({ entry, addLabel, selected, onComment, onDragStart, onDragEnter }) {
6667
+ const style = entry.kind === "add" ? diffLineAdd : entry.kind === "delete" ? diffLineDelete : entry.kind === "hunk" ? diffLineHunk : diffLineContext;
6161
6668
  const lineNumber = entry.oldLine === void 0 && entry.newLine === void 0 ? "" : entry.oldLine === void 0 ? String(entry.newLine) : entry.newLine === void 0 ? String(entry.oldLine) : String(entry.newLine);
6162
6669
  return /* @__PURE__ */ jsxs("div", {
6163
6670
  className: diffLineRowClass,
6164
6671
  style: {
6165
6672
  ...diffLine,
6166
- ...style
6673
+ ...style,
6674
+ ...selected ? diffLineSelected : {}
6167
6675
  },
6676
+ onPointerEnter: onDragEnter,
6168
6677
  children: [
6169
6678
  /* @__PURE__ */ jsx("button", {
6170
6679
  type: "button",
6171
6680
  className: diffCommentButtonClass,
6172
6681
  disabled: onComment === void 0,
6173
6682
  "aria-label": addLabel,
6174
- onClick: onComment,
6683
+ onPointerDown: (event) => {
6684
+ if (event.button !== 0 || onDragStart === void 0) return;
6685
+ event.preventDefault();
6686
+ if (event.currentTarget.hasPointerCapture?.(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
6687
+ onDragStart();
6688
+ },
6689
+ onClick: (event) => {
6690
+ if (event.detail === 0) onComment?.();
6691
+ },
6175
6692
  children: "+"
6176
6693
  }),
6177
6694
  /* @__PURE__ */ jsx("span", {
@@ -6180,14 +6697,105 @@ window.__ModuleLoader__.load({
6180
6697
  }),
6181
6698
  /* @__PURE__ */ jsx("span", {
6182
6699
  style: diffLineMarker,
6183
- children: entry.kind === "add" ? "+" : entry.kind === "delete" ? "−" : entry.kind === "collapsed" ? "⌄" : " "
6700
+ children: entry.kind === "add" ? "+" : entry.kind === "delete" ? "−" : " "
6184
6701
  }),
6185
- /* @__PURE__ */ jsx("span", { children: entry.kind === "collapsed" ? entry.line : entry.line.slice(entry.kind === "hunk" ? 0 : 1) })
6702
+ /* @__PURE__ */ jsx("span", {
6703
+ style: diffLineText,
6704
+ children: entry.line.slice(entry.kind === "hunk" ? 0 : 1)
6705
+ })
6186
6706
  ]
6187
6707
  });
6188
6708
  }
6189
- function DiffFileSection({ file, initiallyOpen, t, comments, ghComments, editorAnchor, editorNode, onOpenEditor, onRemoveComment }) {
6709
+ function ChevronGlyph({ direction }) {
6710
+ return /* @__PURE__ */ jsx("svg", {
6711
+ width: "14",
6712
+ height: "14",
6713
+ viewBox: "0 0 14 14",
6714
+ fill: "none",
6715
+ stroke: "currentColor",
6716
+ strokeWidth: "1.5",
6717
+ strokeLinecap: "round",
6718
+ strokeLinejoin: "round",
6719
+ "aria-hidden": "true",
6720
+ children: /* @__PURE__ */ jsx("path", { d: direction === "up" ? "M3 8.5l4-4 4 4" : "M3 5.5l4 4 4-4" })
6721
+ });
6722
+ }
6723
+ /** "N unmodified lines" separator with GitHub-style expanders: ↑ above the first hunk, ↑↓ between hunks, ↓ after the last. */
6724
+ function DiffGapRow({ gap, t, busy, onExpand }) {
6725
+ const button = (direction) => /* @__PURE__ */ jsx("button", {
6726
+ type: "button",
6727
+ style: diffGapButton,
6728
+ disabled: busy || onExpand === void 0,
6729
+ "aria-label": t(direction === "up" ? "diffExpandUp" : "diffExpandDown"),
6730
+ title: t(direction === "up" ? "diffExpandUp" : "diffExpandDown"),
6731
+ onClick: () => onExpand?.(gap, direction),
6732
+ children: /* @__PURE__ */ jsx(ChevronGlyph, { direction })
6733
+ });
6734
+ return /* @__PURE__ */ jsxs("div", {
6735
+ style: {
6736
+ ...diffLine,
6737
+ ...diffLineHunk
6738
+ },
6739
+ children: [/* @__PURE__ */ jsxs("span", {
6740
+ style: diffGapControls,
6741
+ children: [gap.position === "bottom" ? null : button("up"), gap.position === "top" ? null : button("down")]
6742
+ }), /* @__PURE__ */ jsx("span", {
6743
+ style: diffLineText,
6744
+ children: gap.count === void 0 ? t("diffUnmodifiedTail") : t("diffUnmodifiedLines", { count: gap.count })
6745
+ })]
6746
+ });
6747
+ }
6748
+ function DiffFileSection({ file, root, initiallyOpen, t, comments, ghComments, editorAnchor, editorNode, onOpenEditor, onRemoveComment }) {
6190
6749
  const [open, setOpen] = useState(initiallyOpen);
6750
+ const [revealed, setRevealed] = useState(() => /* @__PURE__ */ new Map());
6751
+ const [total, setTotal] = useState();
6752
+ const [expanding, setExpanding] = useState(false);
6753
+ const [drag, setDrag] = useState();
6754
+ const rows = useMemo(() => expandDiffRows(numberDiffLines(file.lines), revealed, total), [
6755
+ file.lines,
6756
+ revealed,
6757
+ total
6758
+ ]);
6759
+ const anchors = useMemo(() => rows.map(commentAnchorForLine), [rows]);
6760
+ const slash = file.path.lastIndexOf("/");
6761
+ const directory = slash < 0 ? "" : file.path.slice(0, slash + 1);
6762
+ const name = file.path.slice(slash + 1);
6763
+ const expand = (gap, direction) => {
6764
+ if (root === void 0 || expanding) return;
6765
+ const span = gap.count ?? 20;
6766
+ const size = Math.min(20, span);
6767
+ const from = direction === "down" ? gap.newStart : gap.newStart + span - size;
6768
+ setExpanding(true);
6769
+ loadRepositoryFileLines(root, file.path, from, from + size - 1).then((result) => {
6770
+ setRevealed((previous) => {
6771
+ const next = new Map(previous);
6772
+ result.lines.forEach((text, offset) => next.set(from + offset, text));
6773
+ return next;
6774
+ });
6775
+ setTotal(result.total);
6776
+ }, () => void 0).finally(() => setExpanding(false));
6777
+ };
6778
+ useEffect(() => {
6779
+ if (drag === void 0) return;
6780
+ const finish = () => {
6781
+ setDrag(void 0);
6782
+ const anchor = rangeCommentAnchor(anchors, drag.start, drag.end);
6783
+ if (anchor !== void 0) onOpenEditor(anchor);
6784
+ };
6785
+ window.addEventListener("pointerup", finish);
6786
+ window.addEventListener("pointercancel", finish);
6787
+ return () => {
6788
+ window.removeEventListener("pointerup", finish);
6789
+ window.removeEventListener("pointercancel", finish);
6790
+ };
6791
+ }, [
6792
+ anchors,
6793
+ drag,
6794
+ onOpenEditor
6795
+ ]);
6796
+ const dragLow = drag === void 0 ? void 0 : Math.min(drag.start, drag.end);
6797
+ const dragHigh = drag === void 0 ? void 0 : Math.max(drag.start, drag.end);
6798
+ const dragSide = drag === void 0 ? void 0 : anchors[drag.start]?.side;
6191
6799
  return /* @__PURE__ */ jsxs("section", {
6192
6800
  style: diffFile,
6193
6801
  children: [/* @__PURE__ */ jsxs("button", {
@@ -6203,9 +6811,16 @@ window.__ModuleLoader__.load({
6203
6811
  },
6204
6812
  children: "›"
6205
6813
  }),
6206
- /* @__PURE__ */ jsx("span", {
6814
+ /* @__PURE__ */ jsxs("span", {
6207
6815
  style: diffFilePath,
6208
- children: file.path
6816
+ title: file.path,
6817
+ children: [directory === "" ? null : /* @__PURE__ */ jsx("span", {
6818
+ style: diffFileDir,
6819
+ children: `\u202A${directory}\u202C`
6820
+ }), /* @__PURE__ */ jsx("span", {
6821
+ style: diffFileName,
6822
+ children: name
6823
+ })]
6209
6824
  }),
6210
6825
  /* @__PURE__ */ jsxs("span", {
6211
6826
  style: diffFileStats,
@@ -6220,22 +6835,39 @@ window.__ModuleLoader__.load({
6220
6835
  ]
6221
6836
  }), open ? /* @__PURE__ */ jsx("div", {
6222
6837
  style: diffCode,
6223
- children: numberDiffLines(file.lines).map((entry, index) => {
6224
- const anchor = commentAnchorForLine(entry);
6838
+ children: rows.map((entry, index) => {
6839
+ if (entry.kind === "collapsed" && entry.gap !== void 0) return /* @__PURE__ */ jsx(DiffGapRow, {
6840
+ gap: entry.gap,
6841
+ t,
6842
+ busy: expanding,
6843
+ onExpand: root === void 0 ? void 0 : expand
6844
+ }, `gap:${entry.gap.newStart}`);
6845
+ const anchor = anchors[index];
6225
6846
  const lineComments = anchor === void 0 ? [] : comments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
6226
6847
  const ghLineComments = anchor === void 0 ? [] : ghComments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
6227
6848
  const editorOpen = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.line === anchor.line && editorAnchor.side === anchor.side;
6849
+ const inDrag = dragLow !== void 0 && dragHigh !== void 0 && index >= dragLow && index <= dragHigh && anchor?.side === dragSide;
6850
+ const inEditorRange = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.side === anchor.side && anchor.line <= editorAnchor.line && anchor.line >= (editorAnchor.startLine ?? editorAnchor.line);
6228
6851
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6229
6852
  /* @__PURE__ */ jsx(DiffLine, {
6230
6853
  entry,
6231
6854
  addLabel: t("reviewCommentAdd"),
6232
- onComment: anchor === void 0 ? void 0 : () => onOpenEditor(anchor)
6855
+ selected: inDrag || inEditorRange,
6856
+ onComment: anchor === void 0 ? void 0 : () => onOpenEditor(anchor),
6857
+ onDragStart: anchor === void 0 ? void 0 : () => setDrag({
6858
+ start: index,
6859
+ end: index
6860
+ }),
6861
+ onDragEnter: drag === void 0 ? void 0 : () => setDrag((current) => current === void 0 ? current : {
6862
+ ...current,
6863
+ end: index
6864
+ })
6233
6865
  }),
6234
6866
  lineComments.map((comment) => /* @__PURE__ */ jsxs("div", {
6235
6867
  style: diffCommentBlock,
6236
6868
  children: [/* @__PURE__ */ jsxs("div", {
6237
6869
  style: diffCommentCardMeta,
6238
- children: [/* @__PURE__ */ jsx("span", { children: comment.side === "old" ? t("reviewCommentOldSide", { line: comment.line }) : t("reviewCommentNewSide", { line: comment.line }) }), /* @__PURE__ */ jsx("button", {
6870
+ children: [/* @__PURE__ */ jsx("span", { children: commentLineLabel(comment, t) }), /* @__PURE__ */ jsx("button", {
6239
6871
  type: "button",
6240
6872
  style: reviewCommentChipRemove,
6241
6873
  "aria-label": t("reviewCommentRemove"),
@@ -6478,6 +7110,7 @@ window.__ModuleLoader__.load({
6478
7110
  const created = await addReviewComment(sessionId, {
6479
7111
  path: commentEditor.path,
6480
7112
  line: commentEditor.line,
7113
+ ...commentEditor.startLine === void 0 ? {} : { startLine: commentEditor.startLine },
6481
7114
  side: commentEditor.side,
6482
7115
  text: commentDraft.trim()
6483
7116
  });
@@ -6564,6 +7197,10 @@ window.__ModuleLoader__.load({
6564
7197
  const commentEditorNode = commentEditor === void 0 ? null : /* @__PURE__ */ jsxs("div", {
6565
7198
  style: diffCommentBlock,
6566
7199
  children: [
7200
+ /* @__PURE__ */ jsx("div", {
7201
+ style: diffCommentRange,
7202
+ children: commentLineLabel(commentEditor, t)
7203
+ }),
6567
7204
  /* @__PURE__ */ jsx("textarea", {
6568
7205
  className: diffCommentTextareaClass,
6569
7206
  style: diffCommentTextarea,
@@ -6601,12 +7238,14 @@ window.__ModuleLoader__.load({
6601
7238
  /* @__PURE__ */ jsxs("style", {
6602
7239
  "data-dsh-claude-repository-modal-styles": true,
6603
7240
  children: [
7241
+ detailsCardCss,
6604
7242
  diffModalCss,
6605
7243
  panelIconButtonCss,
6606
7244
  diffCommentCss
6607
7245
  ]
6608
7246
  }),
6609
7247
  /* @__PURE__ */ jsxs("div", {
7248
+ className: detailsCardClass,
6610
7249
  style: {
6611
7250
  ...diffPanel,
6612
7251
  ...maximized ? diffPanelMaximized : {}
@@ -6728,6 +7367,7 @@ window.__ModuleLoader__.load({
6728
7367
  children: t("diffEmpty")
6729
7368
  }) : files.map((file, index) => /* @__PURE__ */ jsx(DiffFileSection, {
6730
7369
  file,
7370
+ root: repository.root,
6731
7371
  initiallyOpen: index === 0,
6732
7372
  t,
6733
7373
  comments: reviewComments.filter((comment) => comment.path === file.path),
@@ -7146,10 +7786,28 @@ window.__ModuleLoader__.load({
7146
7786
  }
7147
7787
  //#endregion
7148
7788
  //#region src/client/ClaudePullRequestsPanel.tsx
7789
+ const NO_WORKSPACE_STATE = {};
7790
+ const NO_WORKSPACES = {
7791
+ subscribe: () => () => {},
7792
+ getSnapshot: () => NO_WORKSPACE_STATE
7793
+ };
7149
7794
  const OVERVIEW_REFRESH_MS = 3e4;
7150
- /** Claude sessions worth listing: non-blank, with a checkout; running first. */
7151
- function claudeSessionRows(byId) {
7152
- return Object.values(byId).filter((row) => row !== void 0 && row.agentPreset === "claude" && row.blank !== true && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
7795
+ /** What a running session is blocked on: the latest permission or question
7796
+ * activity that is still in its started phase. */
7797
+ function overviewAttention(activities) {
7798
+ for (let index = activities.length - 1; index >= 0; index -= 1) {
7799
+ const activity = activities[index];
7800
+ if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
7801
+ return activity.phase === "started" ? activity.kind : void 0;
7802
+ }
7803
+ }
7804
+ /** Claude sessions worth listing: rows still in the host list (byId keeps
7805
+ * deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
7806
+ * running first. */
7807
+ function claudeSessionRows(state, archivedSessionIds = []) {
7808
+ const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
7809
+ const archived = new Set(archivedSessionIds);
7810
+ return rows.filter((row) => row !== void 0 && row.agentPreset === "claude" && row.blank !== true && row.origin !== "subagent" && !archived.has(row.id) && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
7153
7811
  }
7154
7812
  function Badge({ label, tone = "neutral" }) {
7155
7813
  const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
@@ -7170,9 +7828,27 @@ window.__ModuleLoader__.load({
7170
7828
  function repositoryName(remote) {
7171
7829
  return remote?.split("/").at(-1);
7172
7830
  }
7173
- function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions }) {
7831
+ function OverviewAttention({ source, running, t }) {
7832
+ const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
7833
+ const attention = running ? overviewAttention(snapshot.activities) : void 0;
7834
+ const usage = snapshot.contextUsage;
7835
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
7836
+ attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
7837
+ label: t("overviewNeedsPermission"),
7838
+ tone: "warning"
7839
+ }) : null,
7840
+ attention === "question" ? /* @__PURE__ */ jsx(Badge, {
7841
+ label: t("overviewNeedsAnswer"),
7842
+ tone: "warning"
7843
+ }) : null,
7844
+ usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
7845
+ ] });
7846
+ }
7847
+ function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
7174
7848
  const snapshot = useSyncExternalStore(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot);
7175
- const rows = useMemo(() => claudeSessionRows(snapshot.byId), [snapshot.byId]);
7849
+ const workspaceStore = workspaces ?? NO_WORKSPACES;
7850
+ const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
7851
+ const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
7176
7852
  const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
7177
7853
  const [statuses, setStatuses] = useState({});
7178
7854
  useEffect(() => {
@@ -7195,11 +7871,12 @@ window.__ModuleLoader__.load({
7195
7871
  };
7196
7872
  }, [cwdKey, loadStatus]);
7197
7873
  return /* @__PURE__ */ jsxs("div", {
7874
+ className: detailsCardClass,
7198
7875
  style: tasksPanel,
7199
7876
  children: [
7200
- /* @__PURE__ */ jsx("style", {
7877
+ /* @__PURE__ */ jsxs("style", {
7201
7878
  "data-dsh-claude-overview-styles": true,
7202
- children: panelIconButtonCss
7879
+ children: [detailsCardCss, panelIconButtonCss]
7203
7880
  }),
7204
7881
  /* @__PURE__ */ jsxs("header", {
7205
7882
  style: tasksHeader,
@@ -7267,7 +7944,12 @@ window.__ModuleLoader__.load({
7267
7944
  autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
7268
7945
  label: t("overviewAutoFix"),
7269
7946
  tone: "success"
7270
- }) : null
7947
+ }) : null,
7948
+ projectionFor === void 0 ? null : /* @__PURE__ */ jsx(OverviewAttention, {
7949
+ source: projectionFor(row.id),
7950
+ running: row.running === true,
7951
+ t
7952
+ })
7271
7953
  ]
7272
7954
  })]
7273
7955
  }, row.id);
@@ -7277,6 +7959,602 @@ window.__ModuleLoader__.load({
7277
7959
  });
7278
7960
  }
7279
7961
  //#endregion
7962
+ //#region src/client/ask-api.ts
7963
+ function record(value) {
7964
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
7965
+ }
7966
+ function parseAskEvent(line) {
7967
+ const event = record(JSON.parse(line));
7968
+ if (event?.type === "delta" && typeof event.text === "string") return {
7969
+ type: "delta",
7970
+ text: event.text
7971
+ };
7972
+ if (event?.type === "thinking" && typeof event.text === "string") return {
7973
+ type: "thinking",
7974
+ text: event.text
7975
+ };
7976
+ if (event?.type === "status" && typeof event.text === "string") return {
7977
+ type: "status",
7978
+ text: event.text
7979
+ };
7980
+ if (event?.type === "tool" && typeof event.id === "string" && (event.phase === "start" || event.phase === "input" || event.phase === "done")) return {
7981
+ type: "tool",
7982
+ id: event.id,
7983
+ phase: event.phase,
7984
+ ...typeof event.name === "string" ? { name: event.name } : {},
7985
+ ...typeof event.summary === "string" ? { summary: event.summary } : {},
7986
+ ...event.error === true ? { error: true } : {}
7987
+ };
7988
+ if (event?.type === "done") return { type: "done" };
7989
+ if (event?.type === "error") return {
7990
+ type: "error",
7991
+ message: typeof event.message === "string" ? event.message : "The question could not be answered."
7992
+ };
7993
+ throw new Error("Invalid ask stream event.");
7994
+ }
7995
+ /** Stream an answer about selected reply text; resolves when the answer completes. */
7996
+ async function askAboutSelection(sessionId, request, onProgress, signal) {
7997
+ const response = await fetch(`${CLAUDE_ASK_PATH}?sessionId=${encodeURIComponent(sessionId)}`, {
7998
+ method: "POST",
7999
+ credentials: "same-origin",
8000
+ headers: {
8001
+ accept: "application/x-ndjson",
8002
+ "content-type": "application/json"
8003
+ },
8004
+ body: JSON.stringify(request),
8005
+ ...signal === void 0 ? {} : { signal }
8006
+ });
8007
+ if (!response.ok) {
8008
+ const body = record(await response.json().catch(() => void 0));
8009
+ throw new Error(typeof body?.message === "string" ? body.message : "The question could not be sent.");
8010
+ }
8011
+ if (response.body === null) throw new Error("The answer stream is unavailable.");
8012
+ const reader = response.body.getReader();
8013
+ const decoder = new TextDecoder();
8014
+ let buffer = "";
8015
+ let finished = false;
8016
+ const handle = (line) => {
8017
+ if (line.trim().length === 0) return;
8018
+ const event = parseAskEvent(line);
8019
+ if (event.type === "delta") onProgress({
8020
+ type: "text",
8021
+ text: event.text
8022
+ });
8023
+ else if (event.type === "thinking") onProgress({
8024
+ type: "thinking",
8025
+ text: event.text
8026
+ });
8027
+ else if (event.type === "status") onProgress({
8028
+ type: "status",
8029
+ text: event.text
8030
+ });
8031
+ else if (event.type === "tool") onProgress(event);
8032
+ else if (event.type === "done") finished = true;
8033
+ else throw new Error(event.message);
8034
+ };
8035
+ while (true) {
8036
+ const chunk = await reader.read();
8037
+ buffer += decoder.decode(chunk.value, { stream: !chunk.done });
8038
+ const lines = buffer.split("\n");
8039
+ buffer = lines.pop() ?? "";
8040
+ for (const line of lines) handle(line);
8041
+ if (chunk.done) break;
8042
+ }
8043
+ handle(buffer);
8044
+ if (!finished) throw new Error("The answer ended unexpectedly.");
8045
+ }
8046
+ //#endregion
8047
+ //#region src/client/ClaudeSelectionAsk.tsx
8048
+ const CHAT_NODE = "[data-chat-flow-kind]";
8049
+ const USER_KIND = "user";
8050
+ const MAX_SELECTION_CHARS = 8e3;
8051
+ const MAX_CONTEXT_CHARS = 16e3;
8052
+ const POPUP_WIDTH = 560;
8053
+ const POPUP_ESTIMATED_HEIGHT = 320;
8054
+ const TOOLBAR_WIDTH = 64;
8055
+ const HIGHLIGHT_NAME = "dsh-claude-ask";
8056
+ function appendAnswerBlock(blocks, progress) {
8057
+ if (progress.type === "text") {
8058
+ const last = blocks.at(-1);
8059
+ if (last?.kind === "text") return [...blocks.slice(0, -1), {
8060
+ kind: "text",
8061
+ text: last.text + progress.text
8062
+ }];
8063
+ return [...blocks, {
8064
+ kind: "text",
8065
+ text: progress.text
8066
+ }];
8067
+ }
8068
+ if (progress.type !== "tool") return blocks;
8069
+ const index = blocks.findIndex((block) => block.kind === "tool" && block.id === progress.id);
8070
+ if (index < 0) {
8071
+ if (progress.phase === "done") return blocks;
8072
+ return [...blocks, {
8073
+ kind: "tool",
8074
+ id: progress.id,
8075
+ name: progress.name ?? "tool",
8076
+ state: "running",
8077
+ ...progress.summary === void 0 ? {} : { summary: progress.summary }
8078
+ }];
8079
+ }
8080
+ const current = blocks[index];
8081
+ const next = {
8082
+ ...current,
8083
+ ...progress.name === void 0 ? {} : { name: progress.name },
8084
+ ...progress.summary === void 0 ? {} : { summary: progress.summary },
8085
+ state: progress.phase === "done" ? progress.error === true ? "error" : "done" : current.state
8086
+ };
8087
+ return [
8088
+ ...blocks.slice(0, index),
8089
+ next,
8090
+ ...blocks.slice(index + 1)
8091
+ ];
8092
+ }
8093
+ function answerText(blocks) {
8094
+ return blocks.filter((block) => block.kind === "text").map((block) => block.text.trim()).filter((text) => text.length > 0).join("\n\n");
8095
+ }
8096
+ function rectOf(range) {
8097
+ const rect = range.getBoundingClientRect();
8098
+ return {
8099
+ top: rect.top,
8100
+ left: rect.left,
8101
+ width: rect.width,
8102
+ bottom: rect.bottom
8103
+ };
8104
+ }
8105
+ /** Read the current selection when it sits inside an assistant reply. */
8106
+ function selectionInfoOf(selection, sessionId) {
8107
+ if (sessionId === void 0 || selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
8108
+ const range = selection.getRangeAt(0);
8109
+ const hostOf = (node) => {
8110
+ return (node instanceof Element ? node : node?.parentElement ?? null)?.closest(CHAT_NODE) ?? null;
8111
+ };
8112
+ const host = hostOf(range.startContainer) ?? hostOf(range.endContainer);
8113
+ if (host === null || host.dataset.chatFlowKind === USER_KIND) return void 0;
8114
+ const text = selection.toString().trim();
8115
+ if (text.length === 0) return void 0;
8116
+ return {
8117
+ text: text.slice(0, MAX_SELECTION_CHARS),
8118
+ context: (host.textContent ?? "").trim().slice(0, MAX_CONTEXT_CHARS),
8119
+ rect: rectOf(range),
8120
+ range: range.cloneRange()
8121
+ };
8122
+ }
8123
+ function toolbarPosition(rect, viewportWidth) {
8124
+ return {
8125
+ top: Math.max(8, rect.top - 40),
8126
+ left: Math.min(Math.max(8, rect.left + rect.width / 2 - TOOLBAR_WIDTH / 2), Math.max(8, viewportWidth - TOOLBAR_WIDTH - 8))
8127
+ };
8128
+ }
8129
+ /** Pick the side with room once, when the popup opens: below the selection
8130
+ * unless the space there is short and above is roomier. */
8131
+ function popupSide(rect, viewportHeight) {
8132
+ const roomBelow = viewportHeight - rect.bottom - 16;
8133
+ const roomAbove = rect.top - 16;
8134
+ return roomBelow >= POPUP_ESTIMATED_HEIGHT || roomBelow >= roomAbove ? "below" : "above";
8135
+ }
8136
+ /** Anchor on the chosen side so streamed content grows away from the text and
8137
+ * scrolls inside the popup instead of running off screen. */
8138
+ function popupPlacement(rect, side, viewportWidth, viewportHeight) {
8139
+ const width = Math.min(POPUP_WIDTH, viewportWidth - 16);
8140
+ const left = Math.min(Math.max(8, rect.left), Math.max(8, viewportWidth - width - 8));
8141
+ const cap = Math.round(viewportHeight * .7);
8142
+ if (side === "below") {
8143
+ const top = Math.max(8, rect.bottom + 8);
8144
+ return {
8145
+ top,
8146
+ left,
8147
+ width,
8148
+ maxHeight: Math.max(120, Math.min(cap, viewportHeight - top - 8))
8149
+ };
8150
+ }
8151
+ const bottom = Math.max(8, viewportHeight - rect.top + 8);
8152
+ return {
8153
+ bottom,
8154
+ left,
8155
+ width,
8156
+ maxHeight: Math.max(120, Math.min(cap, viewportHeight - bottom - 8))
8157
+ };
8158
+ }
8159
+ function CopyIcon() {
8160
+ return /* @__PURE__ */ jsxs("svg", {
8161
+ width: "14",
8162
+ height: "14",
8163
+ viewBox: "0 0 16 16",
8164
+ fill: "none",
8165
+ stroke: "currentColor",
8166
+ strokeWidth: "1.5",
8167
+ strokeLinecap: "round",
8168
+ strokeLinejoin: "round",
8169
+ "aria-hidden": "true",
8170
+ children: [/* @__PURE__ */ jsx("rect", {
8171
+ x: "5.5",
8172
+ y: "5.5",
8173
+ width: "8",
8174
+ height: "8",
8175
+ rx: "1.5"
8176
+ }), /* @__PURE__ */ jsx("path", { d: "M10.5 5.5V3.5a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2" })]
8177
+ });
8178
+ }
8179
+ function AskIcon() {
8180
+ return /* @__PURE__ */ jsxs("svg", {
8181
+ width: "14",
8182
+ height: "14",
8183
+ viewBox: "0 0 16 16",
8184
+ fill: "none",
8185
+ stroke: "currentColor",
8186
+ strokeWidth: "1.5",
8187
+ strokeLinecap: "round",
8188
+ strokeLinejoin: "round",
8189
+ "aria-hidden": "true",
8190
+ 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" }), /* @__PURE__ */ jsx("path", { d: "M6.4 6.3a1.6 1.6 0 1 1 2.3 1.5c-.5.3-.7.6-.7 1.1M8 10.6h.01" })]
8191
+ });
8192
+ }
8193
+ function CheckIcon() {
8194
+ return /* @__PURE__ */ jsx("svg", {
8195
+ width: "14",
8196
+ height: "14",
8197
+ viewBox: "0 0 16 16",
8198
+ fill: "none",
8199
+ stroke: "currentColor",
8200
+ strokeWidth: "1.8",
8201
+ strokeLinecap: "round",
8202
+ strokeLinejoin: "round",
8203
+ "aria-hidden": "true",
8204
+ children: /* @__PURE__ */ jsx("path", { d: "m3.5 8.5 3 3 6-7" })
8205
+ });
8206
+ }
8207
+ /** Selection toolbar over assistant replies: copy, or ask a follow-up whose
8208
+ * answer streams into a popup rendered with the Host Markdown renderer. */
8209
+ function ClaudeSelectionAsk({ t, currentSessionId, ownsSession, insertIntoChat }) {
8210
+ const [selection, setSelection] = useState();
8211
+ const [open, setOpen] = useState(false);
8212
+ const [question, setQuestion] = useState("");
8213
+ const [blocks, setBlocks] = useState([]);
8214
+ const answer = answerText(blocks);
8215
+ const [thinking, setThinking] = useState("");
8216
+ const [thinkingOpen, setThinkingOpen] = useState(false);
8217
+ const [started, setStarted] = useState(false);
8218
+ const [phase, setPhase] = useState("idle");
8219
+ const [error, setError] = useState();
8220
+ const [copied, setCopied] = useState();
8221
+ const [rect, setRect] = useState();
8222
+ const [side, setSide] = useState("below");
8223
+ const popupRef = useRef(null);
8224
+ const toolbarRef = useRef(null);
8225
+ const controller = useRef();
8226
+ const openRef = useRef(false);
8227
+ openRef.current = open;
8228
+ const close = useCallback(() => {
8229
+ controller.current?.abort();
8230
+ controller.current = void 0;
8231
+ setOpen(false);
8232
+ setSelection(void 0);
8233
+ setQuestion("");
8234
+ setBlocks([]);
8235
+ setThinking("");
8236
+ setThinkingOpen(false);
8237
+ setStarted(false);
8238
+ setPhase("idle");
8239
+ setError(void 0);
8240
+ setCopied(void 0);
8241
+ }, []);
8242
+ useEffect(() => {
8243
+ if (typeof document === "undefined") return;
8244
+ let timer;
8245
+ const update = () => {
8246
+ if (openRef.current) return;
8247
+ const sessionId = currentSessionId();
8248
+ if (sessionId === void 0 || !ownsSession(sessionId)) {
8249
+ setSelection(void 0);
8250
+ return;
8251
+ }
8252
+ const info = selectionInfoOf(document.getSelection(), sessionId);
8253
+ setSelection(info === void 0 ? void 0 : {
8254
+ sessionId,
8255
+ ...info
8256
+ });
8257
+ };
8258
+ const schedule = () => {
8259
+ if (timer !== void 0) clearTimeout(timer);
8260
+ timer = setTimeout(update, 120);
8261
+ };
8262
+ const onKey = (event) => {
8263
+ if (event.key === "Escape") close();
8264
+ };
8265
+ const onPointerDown = (event) => {
8266
+ if (!(event.target instanceof Node)) return;
8267
+ if (popupRef.current?.contains(event.target) === true || toolbarRef.current?.contains(event.target) === true) return;
8268
+ if (openRef.current) close();
8269
+ };
8270
+ document.addEventListener("selectionchange", schedule);
8271
+ document.addEventListener("mouseup", schedule);
8272
+ document.addEventListener("keydown", onKey);
8273
+ document.addEventListener("pointerdown", onPointerDown);
8274
+ return () => {
8275
+ if (timer !== void 0) clearTimeout(timer);
8276
+ document.removeEventListener("selectionchange", schedule);
8277
+ document.removeEventListener("mouseup", schedule);
8278
+ document.removeEventListener("keydown", onKey);
8279
+ document.removeEventListener("pointerdown", onPointerDown);
8280
+ };
8281
+ }, [
8282
+ close,
8283
+ currentSessionId,
8284
+ ownsSession
8285
+ ]);
8286
+ useEffect(() => () => controller.current?.abort(), []);
8287
+ useEffect(() => {
8288
+ setRect(selection?.rect);
8289
+ if (selection === void 0) return;
8290
+ let frame;
8291
+ const reposition = () => {
8292
+ if (frame !== void 0) return;
8293
+ frame = requestAnimationFrame(() => {
8294
+ frame = void 0;
8295
+ const next = rectOf(selection.range);
8296
+ if (next.width === 0 && next.bottom === next.top) return;
8297
+ setRect(next);
8298
+ });
8299
+ };
8300
+ document.addEventListener("scroll", reposition, true);
8301
+ window.addEventListener("resize", reposition);
8302
+ return () => {
8303
+ if (frame !== void 0) cancelAnimationFrame(frame);
8304
+ document.removeEventListener("scroll", reposition, true);
8305
+ window.removeEventListener("resize", reposition);
8306
+ };
8307
+ }, [selection]);
8308
+ useEffect(() => {
8309
+ if (!open || selection === void 0) return;
8310
+ const runtime = globalThis;
8311
+ const registry = runtime.CSS?.highlights;
8312
+ if (registry === void 0 || runtime.Highlight === void 0) return;
8313
+ registry.set(HIGHLIGHT_NAME, new runtime.Highlight(selection.range));
8314
+ return () => {
8315
+ registry.delete(HIGHLIGHT_NAME);
8316
+ };
8317
+ }, [open, selection]);
8318
+ useEffect(() => {
8319
+ if (copied === void 0) return;
8320
+ const timer = setTimeout(() => setCopied(void 0), 1200);
8321
+ return () => clearTimeout(timer);
8322
+ }, [copied]);
8323
+ if (selection === void 0 || rect === void 0) return /* @__PURE__ */ jsx("span", {
8324
+ "data-dsh-claude-selection-ask": "armed",
8325
+ hidden: true
8326
+ });
8327
+ const copy = (text, what) => {
8328
+ navigator.clipboard.writeText(text).then(() => {
8329
+ setCopied(what);
8330
+ }, () => void 0);
8331
+ };
8332
+ const submit = () => {
8333
+ if (question.trim().length === 0 || phase === "answering") return;
8334
+ controller.current?.abort();
8335
+ const aborter = new AbortController();
8336
+ controller.current = aborter;
8337
+ setBlocks([]);
8338
+ setThinking("");
8339
+ setThinkingOpen(false);
8340
+ setStarted(false);
8341
+ setError(void 0);
8342
+ setPhase("answering");
8343
+ askAboutSelection(selection.sessionId, {
8344
+ selection: selection.text,
8345
+ context: selection.context,
8346
+ question
8347
+ }, (progress) => {
8348
+ if (progress.type === "status") setStarted(true);
8349
+ else if (progress.type === "thinking") setThinking((current) => current + progress.text);
8350
+ else setBlocks((current) => appendAnswerBlock(current, progress));
8351
+ }, aborter.signal).then(() => {
8352
+ if (!aborter.signal.aborted) setPhase("done");
8353
+ }, (reason) => {
8354
+ if (aborter.signal.aborted) return;
8355
+ setError(reason instanceof Error ? reason.message : t("askFailed"));
8356
+ setPhase("done");
8357
+ });
8358
+ };
8359
+ const viewportWidth = typeof window === "undefined" ? 1280 : window.innerWidth;
8360
+ const viewportHeight = typeof window === "undefined" ? 800 : window.innerHeight;
8361
+ if (!open) {
8362
+ const position = toolbarPosition(rect, viewportWidth);
8363
+ return /* @__PURE__ */ jsxs("div", {
8364
+ ref: toolbarRef,
8365
+ role: "toolbar",
8366
+ "aria-label": t("askToolbar"),
8367
+ style: {
8368
+ ...askToolbar,
8369
+ top: position.top,
8370
+ left: position.left
8371
+ },
8372
+ children: [
8373
+ /* @__PURE__ */ jsx("style", {
8374
+ "data-dsh-claude-ask-styles": true,
8375
+ children: panelIconButtonCss
8376
+ }),
8377
+ /* @__PURE__ */ jsx(Tooltip, {
8378
+ label: copied === "selection" ? t("askCopied") : t("copyTooltip"),
8379
+ side: "top",
8380
+ delayMs: 300,
8381
+ children: /* @__PURE__ */ jsx("button", {
8382
+ type: "button",
8383
+ className: panelIconButtonClass,
8384
+ "aria-label": t("copyTooltip"),
8385
+ onMouseDown: (event) => {
8386
+ event.preventDefault();
8387
+ },
8388
+ onClick: () => {
8389
+ copy(selection.text, "selection");
8390
+ },
8391
+ children: copied === "selection" ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
8392
+ })
8393
+ }),
8394
+ /* @__PURE__ */ jsx(Tooltip, {
8395
+ label: t("askTooltip"),
8396
+ side: "top",
8397
+ delayMs: 300,
8398
+ children: /* @__PURE__ */ jsx("button", {
8399
+ type: "button",
8400
+ className: panelIconButtonClass,
8401
+ "aria-label": t("askTooltip"),
8402
+ onMouseDown: (event) => {
8403
+ event.preventDefault();
8404
+ },
8405
+ onClick: () => {
8406
+ setSide(popupSide(rect, viewportHeight));
8407
+ setOpen(true);
8408
+ },
8409
+ children: /* @__PURE__ */ jsx(AskIcon, {})
8410
+ })
8411
+ })
8412
+ ]
8413
+ });
8414
+ }
8415
+ const placement = popupPlacement(rect, side, viewportWidth, viewportHeight);
8416
+ return /* @__PURE__ */ jsxs("div", {
8417
+ ref: popupRef,
8418
+ role: "dialog",
8419
+ "aria-label": t("askTooltip"),
8420
+ style: {
8421
+ ...askPopup,
8422
+ ...placement
8423
+ },
8424
+ children: [
8425
+ /* @__PURE__ */ jsxs("style", {
8426
+ "data-dsh-claude-ask-styles": true,
8427
+ children: [panelIconButtonCss, askHighlightCss]
8428
+ }),
8429
+ /* @__PURE__ */ jsx("p", {
8430
+ style: askQuote,
8431
+ children: selection.text
8432
+ }),
8433
+ phase === "idle" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("textarea", {
8434
+ autoFocus: true,
8435
+ value: question,
8436
+ placeholder: t("askPlaceholder"),
8437
+ style: askTextarea,
8438
+ onChange: (event) => {
8439
+ setQuestion(event.currentTarget.value);
8440
+ },
8441
+ onKeyDown: (event) => {
8442
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
8443
+ event.preventDefault();
8444
+ submit();
8445
+ }
8446
+ }
8447
+ }), /* @__PURE__ */ jsxs("div", {
8448
+ style: askActions,
8449
+ children: [/* @__PURE__ */ jsx("button", {
8450
+ type: "button",
8451
+ style: askButton,
8452
+ onClick: close,
8453
+ children: t("askClose")
8454
+ }), /* @__PURE__ */ jsx("button", {
8455
+ type: "button",
8456
+ style: {
8457
+ ...askButton,
8458
+ ...askPrimaryButton
8459
+ },
8460
+ disabled: question.trim().length === 0,
8461
+ onClick: submit,
8462
+ children: t("askSubmit")
8463
+ })]
8464
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8465
+ /* @__PURE__ */ jsx("p", {
8466
+ style: askQuestion,
8467
+ children: question
8468
+ }),
8469
+ /* @__PURE__ */ jsxs("div", {
8470
+ style: askAnswer,
8471
+ children: [
8472
+ thinking.length === 0 ? null : /* @__PURE__ */ jsxs("button", {
8473
+ type: "button",
8474
+ style: askThinkingRow,
8475
+ "aria-expanded": thinkingOpen,
8476
+ onClick: () => {
8477
+ setThinkingOpen((value) => !value);
8478
+ },
8479
+ children: [/* @__PURE__ */ jsx("span", {
8480
+ style: askThinkingLabel,
8481
+ children: phase === "answering" && answer.length === 0 ? t("askThinkingLive") : t("askThinkingLabel")
8482
+ }), /* @__PURE__ */ jsx("span", {
8483
+ style: thinkingOpen ? askThinkingFull : askThinkingPreview,
8484
+ children: thinkingOpen ? thinking : thinking.trimEnd().split("\n").filter((line) => line.trim().length > 0).at(-1)
8485
+ })]
8486
+ }),
8487
+ blocks.length === 0 && thinking.length === 0 && error === void 0 ? /* @__PURE__ */ jsx("span", {
8488
+ style: askStatus,
8489
+ children: started ? t("askThinking") : t("askStarting")
8490
+ }) : null,
8491
+ blocks.map((block, index) => block.kind === "text" ? /* @__PURE__ */ jsx(MarkdownText, {
8492
+ text: block.text,
8493
+ streaming: phase === "answering" && index === blocks.length - 1
8494
+ }, `text-${index}`) : /* @__PURE__ */ jsxs("div", {
8495
+ style: askToolRow,
8496
+ children: [
8497
+ /* @__PURE__ */ jsx("span", {
8498
+ style: {
8499
+ ...askToolGlyph,
8500
+ ...block.state === "error" ? askToolGlyphError : block.state === "done" ? askToolGlyphDone : {}
8501
+ },
8502
+ "aria-hidden": "true",
8503
+ children: block.state === "running" ? "…" : block.state === "done" ? "✓" : "×"
8504
+ }),
8505
+ /* @__PURE__ */ jsx("span", {
8506
+ style: askToolName,
8507
+ children: block.name
8508
+ }),
8509
+ block.summary === void 0 ? null : /* @__PURE__ */ jsx("span", {
8510
+ style: askToolSummary,
8511
+ children: block.summary
8512
+ })
8513
+ ]
8514
+ }, block.id)),
8515
+ error === void 0 ? null : /* @__PURE__ */ jsx("p", {
8516
+ role: "alert",
8517
+ style: askError,
8518
+ children: error
8519
+ })
8520
+ ]
8521
+ }),
8522
+ /* @__PURE__ */ jsxs("div", {
8523
+ style: askActions,
8524
+ children: [
8525
+ answer.length === 0 ? null : /* @__PURE__ */ jsx("button", {
8526
+ type: "button",
8527
+ style: askButton,
8528
+ onClick: () => {
8529
+ copy(answer, "answer");
8530
+ },
8531
+ children: copied === "answer" ? t("askCopied") : t("askCopyAnswer")
8532
+ }),
8533
+ answer.length === 0 || insertIntoChat === void 0 ? null : /* @__PURE__ */ jsx("button", {
8534
+ type: "button",
8535
+ style: askButton,
8536
+ onClick: () => {
8537
+ insertIntoChat(selection.sessionId, `> ${selection.text.replaceAll("\n", "\n> ")}\n\n**${question}**\n\n${answer}`);
8538
+ close();
8539
+ },
8540
+ children: t("askSendToChat")
8541
+ }),
8542
+ /* @__PURE__ */ jsx("button", {
8543
+ type: "button",
8544
+ style: {
8545
+ ...askButton,
8546
+ ...askPrimaryButton
8547
+ },
8548
+ onClick: close,
8549
+ children: t("askClose")
8550
+ })
8551
+ ]
8552
+ })
8553
+ ] })
8554
+ ]
8555
+ });
8556
+ }
8557
+ //#endregion
7280
8558
  //#region src/client/hero-dom-bridge.ts
7281
8559
  const PORTAL_ATTRIBUTE = "data-dsh-claude-hero-controls";
7282
8560
  function normalizedText(element) {
@@ -7347,6 +8625,10 @@ window.__ModuleLoader__.load({
7347
8625
  const normalized = query.trim().toLocaleLowerCase();
7348
8626
  return normalized.length === 0 ? branches : branches.filter((branch) => branch.toLocaleLowerCase().includes(normalized));
7349
8627
  }
8628
+ /** Toggle a ticket's membership in the multi-select, keyed by ticket key. */
8629
+ function toggleTicketSelection(tickets, ticket) {
8630
+ return tickets.some((item) => item.key === ticket.key) ? tickets.filter((item) => item.key !== ticket.key) : [...tickets, ticket];
8631
+ }
7350
8632
  function branchMenuNavigationIndex(current, count, key) {
7351
8633
  if (count <= 0) return 0;
7352
8634
  if (key === "Home") return 0;
@@ -7377,7 +8659,7 @@ window.__ModuleLoader__.load({
7377
8659
  function progressLabelKey(stage) {
7378
8660
  return PROGRESS_LABEL_KEYS[stage];
7379
8661
  }
7380
- function WorktreeProgressCard({ stage, error, t, onDismiss }) {
8662
+ function WorktreeProgressCard({ stage, error, context, t, onDismiss }) {
7381
8663
  const current = WORKTREE_PROGRESS_STAGES.indexOf(stage);
7382
8664
  const visible = WORKTREE_PROGRESS_STAGES.slice(0, Math.max(0, current) + 1);
7383
8665
  return /* @__PURE__ */ jsxs("div", {
@@ -7421,9 +8703,12 @@ window.__ModuleLoader__.load({
7421
8703
  }),
7422
8704
  /* @__PURE__ */ jsxs("div", {
7423
8705
  style: heroWorktreeProgressCopy,
7424
- children: [/* @__PURE__ */ jsx("strong", {
8706
+ children: [/* @__PURE__ */ jsxs("strong", {
7425
8707
  style: heroWorktreeProgressTitle,
7426
- children: error === void 0 ? t("repositoryProgressTitle") : t("repositoryProgressFailed")
8708
+ children: [error === void 0 ? t("repositoryProgressTitle") : t("repositoryProgressFailed"), context === void 0 ? null : /* @__PURE__ */ jsxs("span", {
8709
+ style: heroWorktreeProgressContext,
8710
+ children: [" ", context]
8711
+ })]
7427
8712
  }), /* @__PURE__ */ jsx("span", {
7428
8713
  style: heroWorktreeProgressCurrent,
7429
8714
  children: error ?? t(progressLabelKey(stage))
@@ -7470,7 +8755,7 @@ window.__ModuleLoader__.load({
7470
8755
  })]
7471
8756
  });
7472
8757
  }
7473
- function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, onMenuOpenChange, onSelect, onWorktreeChange }) {
8758
+ function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, worktreeLocked = false, onMenuOpenChange, onSelect, onWorktreeChange }) {
7474
8759
  const pickerRef = useRef(null);
7475
8760
  const searchRef = useRef(null);
7476
8761
  const optionRefs = useRef([]);
@@ -7599,11 +8884,11 @@ window.__ModuleLoader__.load({
7599
8884
  /* @__PURE__ */ jsxs("button", {
7600
8885
  type: "button",
7601
8886
  role: "checkbox",
7602
- "aria-checked": worktree,
7603
- disabled: busy,
8887
+ "aria-checked": worktreeLocked || worktree,
8888
+ disabled: busy || worktreeLocked,
7604
8889
  style: {
7605
8890
  ...heroWorktreeToggle,
7606
- ...worktree || worktreeHovered ? heroWorktreeToggleActive : {},
8891
+ ...worktreeLocked || worktree || worktreeHovered ? heroWorktreeToggleActive : {},
7607
8892
  ...worktreeFocused ? heroWorktreeToggleFocused : {}
7608
8893
  },
7609
8894
  onMouseEnter: () => {
@@ -7612,8 +8897,8 @@ window.__ModuleLoader__.load({
7612
8897
  onMouseLeave: () => {
7613
8898
  setWorktreeHovered(false);
7614
8899
  },
7615
- onFocus: () => {
7616
- setWorktreeFocused(true);
8900
+ onFocus: (event) => {
8901
+ setWorktreeFocused(event.currentTarget.matches(":focus-visible"));
7617
8902
  },
7618
8903
  onBlur: () => {
7619
8904
  setWorktreeFocused(false);
@@ -7625,9 +8910,9 @@ window.__ModuleLoader__.load({
7625
8910
  "aria-hidden": "true",
7626
8911
  style: {
7627
8912
  ...heroWorktreeCheckbox,
7628
- ...worktree ? heroWorktreeCheckboxChecked : {}
8913
+ ...worktreeLocked || worktree ? heroWorktreeCheckboxChecked : {}
7629
8914
  },
7630
- children: worktree ? /* @__PURE__ */ jsx("svg", {
8915
+ children: worktreeLocked || worktree ? /* @__PURE__ */ jsx("svg", {
7631
8916
  viewBox: "0 0 12 12",
7632
8917
  style: heroWorktreeCheckboxIcon,
7633
8918
  children: /* @__PURE__ */ jsx("path", {
@@ -7644,7 +8929,7 @@ window.__ModuleLoader__.load({
7644
8929
  ]
7645
8930
  });
7646
8931
  }
7647
- function ClaudeHeroRepositoryControls({ sessionId, useSessions, useWorkspaces, input, t, prepare }) {
8932
+ function ClaudeHeroRepositoryControls({ sessionId, useSessions, useWorkspaces, input, t, prepare, prepareMany }) {
7648
8933
  const cwd = useSessions((state) => state.byId[sessionId]?.cwd);
7649
8934
  const workspacePath = useWorkspaces((state) => state.items.find((item) => item.sessionIds.includes(sessionId))?.path);
7650
8935
  const [portal, setPortal] = useState();
@@ -7657,7 +8942,9 @@ window.__ModuleLoader__.load({
7657
8942
  const [progressError, setProgressError] = useState();
7658
8943
  const [error, setError] = useState();
7659
8944
  const [jiraConnected, setJiraConnected] = useState(false);
7660
- const [ticket, setTicket] = useState();
8945
+ const [tickets, setTickets] = useState([]);
8946
+ const [batchProgress, setBatchProgress] = useState();
8947
+ const [batchDone, setBatchDone] = useState();
7661
8948
  const [ticketMenuOpen, setTicketMenuOpen] = useState(false);
7662
8949
  const [ticketQuery, setTicketQuery] = useState("");
7663
8950
  const [ticketResults, setTicketResults] = useState();
@@ -7706,7 +8993,9 @@ window.__ModuleLoader__.load({
7706
8993
  setProgressStage(void 0);
7707
8994
  setProgressError(void 0);
7708
8995
  setError(void 0);
7709
- setTicket(void 0);
8996
+ setTickets([]);
8997
+ setBatchProgress(void 0);
8998
+ setBatchDone(void 0);
7710
8999
  setTicketMenuOpen(false);
7711
9000
  setTicketQuery("");
7712
9001
  setTicketResults(void 0);
@@ -7728,19 +9017,46 @@ window.__ModuleLoader__.load({
7728
9017
  sessionId
7729
9018
  ]);
7730
9019
  const availableBranches = branches === void 0 ? [] : repositoryBranchOptions(branches);
7731
- const changed = branches !== void 0 && selected.length > 0 && (worktree || ticket !== void 0 || selected !== branches.current);
9020
+ const changed = branches !== void 0 && selected.length > 0 && (worktree || tickets.length > 0 || selected !== branches.current);
7732
9021
  useEffect(() => {
7733
9022
  if (!changed || portal === void 0 || busy) return;
7734
9023
  const submit = (event) => {
7735
- if (pendingRef.current || input.draft.trim().length === 0 && ticket === void 0) return;
9024
+ if (pendingRef.current || input.draft.trim().length === 0 && tickets.length === 0) return;
7736
9025
  if (!(event instanceof KeyboardEvent ? shouldInterceptKey(event) : shouldInterceptClick(event))) return;
7737
9026
  event.preventDefault();
7738
9027
  event.stopPropagation();
7739
9028
  pendingRef.current = true;
7740
9029
  setBusy(true);
7741
9030
  setProgressError(void 0);
7742
- setProgressStage(worktree ? "inspecting" : void 0);
7743
9031
  setError(void 0);
9032
+ setBatchDone(void 0);
9033
+ if (tickets.length > 1) {
9034
+ const total = tickets.length;
9035
+ setBatchProgress({
9036
+ ticketKey: tickets[0]?.key ?? "",
9037
+ index: 0,
9038
+ total,
9039
+ stage: "inspecting"
9040
+ });
9041
+ setProgressStage("inspecting");
9042
+ prepareMany(branches.root, selected, tickets, (progress) => {
9043
+ setBatchProgress(progress);
9044
+ setProgressStage(progress.stage);
9045
+ }).then(() => {
9046
+ setBatchDone(total);
9047
+ setBatchProgress(void 0);
9048
+ setProgressStage(void 0);
9049
+ setTickets([]);
9050
+ }, (reason) => {
9051
+ setProgressError(reason instanceof Error ? reason.message : String(reason));
9052
+ }).finally(() => {
9053
+ pendingRef.current = false;
9054
+ setBusy(false);
9055
+ });
9056
+ return;
9057
+ }
9058
+ const ticket = tickets[0];
9059
+ setProgressStage(worktree ? "inspecting" : void 0);
7744
9060
  prepare(branches.root, selected, worktree, (stage) => {
7745
9061
  if (worktree) setProgressStage(stage);
7746
9062
  }, ticket).catch((reason) => {
@@ -7765,8 +9081,9 @@ window.__ModuleLoader__.load({
7765
9081
  input.draft,
7766
9082
  portal,
7767
9083
  prepare,
9084
+ prepareMany,
7768
9085
  selected,
7769
- ticket,
9086
+ tickets,
7770
9087
  worktree
7771
9088
  ]);
7772
9089
  useEffect(() => {
@@ -7815,8 +9132,13 @@ window.__ModuleLoader__.load({
7815
9132
  document.removeEventListener("pointerdown", closeOnOutsidePointer);
7816
9133
  };
7817
9134
  }, [ticketMenuOpen]);
7818
- const chooseTicket = (next) => {
7819
- setTicket(next);
9135
+ const toggleTicket = (next) => {
9136
+ setTickets((current) => toggleTicketSelection(current, next));
9137
+ setError(void 0);
9138
+ setBatchDone(void 0);
9139
+ };
9140
+ const clearTickets = () => {
9141
+ setTickets([]);
7820
9142
  setError(void 0);
7821
9143
  setTicketMenuOpen(false);
7822
9144
  };
@@ -7836,6 +9158,7 @@ window.__ModuleLoader__.load({
7836
9158
  worktreeLabel: t("repositoryWorktree"),
7837
9159
  searchPlaceholder: t("repositoryBranchSearch"),
7838
9160
  emptySearchLabel: t("repositoryBranchSearchEmpty"),
9161
+ worktreeLocked: tickets.length > 1,
7839
9162
  onMenuOpenChange: setMenuOpen,
7840
9163
  onSelect: (branch) => {
7841
9164
  setSelected(branch);
@@ -7866,78 +9189,115 @@ window.__ModuleLoader__.load({
7866
9189
  /* @__PURE__ */ jsx(TicketIcon, {}),
7867
9190
  /* @__PURE__ */ jsx("span", {
7868
9191
  style: heroBranchName,
7869
- children: ticket === void 0 ? t("heroTicket") : ticket.key
9192
+ children: tickets.length === 0 ? t("heroTicket") : tickets.length === 1 ? tickets[0]?.key : t("heroTicketCount", { count: tickets.length })
7870
9193
  }),
7871
9194
  /* @__PURE__ */ jsx(IconChevronDownOutline14, {})
7872
9195
  ]
7873
9196
  }), ticketMenuOpen ? /* @__PURE__ */ jsxs("span", {
7874
9197
  role: "menu",
7875
- style: heroBranchMenu,
9198
+ style: {
9199
+ ...heroBranchMenu,
9200
+ ...heroTicketMenu
9201
+ },
7876
9202
  onKeyDown: (event) => {
7877
9203
  if (event.key === "Escape") {
7878
9204
  event.preventDefault();
7879
9205
  setTicketMenuOpen(false);
7880
9206
  }
7881
9207
  },
7882
- children: [/* @__PURE__ */ jsxs("label", {
7883
- style: heroBranchSearch,
7884
- children: [/* @__PURE__ */ jsx(IconSearchOutline16, {}), /* @__PURE__ */ jsx("input", {
7885
- ref: ticketSearchRef,
7886
- type: "search",
7887
- value: ticketQuery,
7888
- placeholder: t("heroTicketSearch"),
7889
- "aria-label": t("heroTicketSearch"),
7890
- style: heroBranchSearchInput,
7891
- onChange: (event) => {
7892
- setTicketQuery(event.currentTarget.value);
7893
- }
7894
- })]
7895
- }), /* @__PURE__ */ jsx("span", {
7896
- style: heroBranchList,
7897
- children: ticketError !== void 0 ? /* @__PURE__ */ jsx("span", {
7898
- style: heroBranchEmpty,
7899
- children: ticketError
7900
- }) : ticketResults === void 0 ? /* @__PURE__ */ jsx("span", {
7901
- style: heroBranchEmpty,
7902
- children: t("heroTicketLoading")
7903
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [ticket === void 0 ? null : /* @__PURE__ */ jsx("button", {
7904
- type: "button",
7905
- role: "menuitem",
7906
- style: heroBranchItem,
7907
- onClick: () => {
7908
- chooseTicket(void 0);
7909
- },
7910
- children: /* @__PURE__ */ jsx("span", {
7911
- style: heroBranchItemName,
9208
+ children: [
9209
+ /* @__PURE__ */ jsxs("label", {
9210
+ style: heroBranchSearch,
9211
+ children: [/* @__PURE__ */ jsx(IconSearchOutline16, {}), /* @__PURE__ */ jsx("input", {
9212
+ ref: ticketSearchRef,
9213
+ type: "search",
9214
+ value: ticketQuery,
9215
+ placeholder: t("heroTicketSearch"),
9216
+ "aria-label": t("heroTicketSearch"),
9217
+ style: heroBranchSearchInput,
9218
+ onChange: (event) => {
9219
+ setTicketQuery(event.currentTarget.value);
9220
+ }
9221
+ })]
9222
+ }),
9223
+ tickets.length === 0 ? null : /* @__PURE__ */ jsxs("span", {
9224
+ style: heroTicketChips,
9225
+ children: [tickets.map((item) => /* @__PURE__ */ jsxs("span", {
9226
+ style: heroTicketChip,
9227
+ children: [item.key, /* @__PURE__ */ jsx("button", {
9228
+ type: "button",
9229
+ style: heroTicketChipRemove,
9230
+ "aria-label": t("heroTicketRemove", { key: item.key }),
9231
+ onClick: () => {
9232
+ toggleTicket(item);
9233
+ },
9234
+ children: "×"
9235
+ })]
9236
+ }, item.key)), tickets.length > 1 ? /* @__PURE__ */ jsx("button", {
9237
+ type: "button",
9238
+ style: heroTicketChipsClear,
9239
+ onClick: clearTickets,
7912
9240
  children: t("heroTicketNone")
9241
+ }) : null]
9242
+ }),
9243
+ /* @__PURE__ */ jsx("span", {
9244
+ style: heroBranchList,
9245
+ children: ticketError !== void 0 ? /* @__PURE__ */ jsx("span", {
9246
+ style: heroBranchEmpty,
9247
+ children: ticketError
9248
+ }) : ticketResults === void 0 ? /* @__PURE__ */ jsx("span", {
9249
+ style: heroBranchEmpty,
9250
+ children: t("heroTicketLoading")
9251
+ }) : ticketResults.length === 0 ? /* @__PURE__ */ jsx("span", {
9252
+ style: heroBranchEmpty,
9253
+ children: t("heroTicketEmpty")
9254
+ }) : ticketResults.map((item) => {
9255
+ const picked = tickets.some((entry) => entry.key === item.key);
9256
+ return /* @__PURE__ */ jsxs("button", {
9257
+ type: "button",
9258
+ role: "menuitemcheckbox",
9259
+ "aria-checked": picked,
9260
+ style: heroBranchItem,
9261
+ onClick: () => {
9262
+ toggleTicket(item);
9263
+ },
9264
+ children: [
9265
+ /* @__PURE__ */ jsx("span", {
9266
+ "aria-hidden": "true",
9267
+ style: {
9268
+ ...heroTicketCheckbox,
9269
+ ...picked ? heroWorktreeCheckboxChecked : {}
9270
+ },
9271
+ children: picked ? /* @__PURE__ */ jsx("svg", {
9272
+ viewBox: "0 0 12 12",
9273
+ style: heroWorktreeCheckboxIcon,
9274
+ children: /* @__PURE__ */ jsx("path", {
9275
+ d: "m2.5 6.2 2.1 2.1 4.9-5",
9276
+ fill: "none",
9277
+ stroke: "currentColor",
9278
+ strokeLinecap: "round",
9279
+ strokeLinejoin: "round",
9280
+ strokeWidth: "1.8"
9281
+ })
9282
+ }) : null
9283
+ }),
9284
+ /* @__PURE__ */ jsx("span", {
9285
+ style: heroTicketKey,
9286
+ children: item.key
9287
+ }),
9288
+ /* @__PURE__ */ jsx("span", {
9289
+ style: heroBranchItemName,
9290
+ children: item.summary
9291
+ }),
9292
+ item.status === void 0 ? null : /* @__PURE__ */ jsx("span", {
9293
+ style: heroTicketStatus,
9294
+ children: item.status
9295
+ })
9296
+ ]
9297
+ }, item.key);
7913
9298
  })
7914
- }), ticketResults.length === 0 ? /* @__PURE__ */ jsx("span", {
7915
- style: heroBranchEmpty,
7916
- children: t("heroTicketEmpty")
7917
- }) : ticketResults.map((item) => /* @__PURE__ */ jsxs("button", {
7918
- type: "button",
7919
- role: "menuitem",
7920
- "aria-current": item.key === ticket?.key ? "true" : void 0,
7921
- style: heroBranchItem,
7922
- onClick: () => {
7923
- chooseTicket(item);
7924
- },
7925
- children: [
7926
- /* @__PURE__ */ jsx("span", {
7927
- style: heroTicketKey,
7928
- children: item.key
7929
- }),
7930
- /* @__PURE__ */ jsx("span", {
7931
- style: heroBranchItemName,
7932
- children: item.summary
7933
- }),
7934
- item.status === void 0 ? null : /* @__PURE__ */ jsx("span", {
7935
- style: heroTicketStatus,
7936
- children: item.status
7937
- })
7938
- ]
7939
- }, item.key))] })
7940
- })]
9299
+ })
9300
+ ]
7941
9301
  }) : null]
7942
9302
  })
7943
9303
  }),
@@ -7945,14 +9305,21 @@ window.__ModuleLoader__.load({
7945
9305
  role: "alert",
7946
9306
  style: heroRepositoryError,
7947
9307
  children: error
9308
+ }),
9309
+ batchDone === void 0 ? null : /* @__PURE__ */ jsx("span", {
9310
+ role: "status",
9311
+ style: heroRepositoryStatus,
9312
+ children: t("heroBatchStarted", { count: batchDone })
7948
9313
  })
7949
9314
  ] }), progressStage === void 0 || !busy && progressError === void 0 ? null : /* @__PURE__ */ jsx(WorktreeProgressCard, {
7950
9315
  stage: progressStage,
7951
9316
  ...progressError === void 0 ? {} : { error: progressError },
9317
+ ...batchProgress === void 0 ? {} : { context: `${batchProgress.ticketKey} · ${batchProgress.index + 1}/${batchProgress.total}` },
7952
9318
  t,
7953
9319
  onDismiss: () => {
7954
9320
  setProgressStage(void 0);
7955
9321
  setProgressError(void 0);
9322
+ setBatchProgress(void 0);
7956
9323
  }
7957
9324
  })]
7958
9325
  }), portal);
@@ -8436,7 +9803,10 @@ window.__ModuleLoader__.load({
8436
9803
  heroTicketSearch: "搜索 ticket 或输入 key…",
8437
9804
  heroTicketLoading: "搜索中…",
8438
9805
  heroTicketEmpty: "没有匹配的 ticket",
8439
- heroTicketNone: "不关联 ticket",
9806
+ heroTicketNone: "清除已选 ticket",
9807
+ heroTicketRemove: "移除 {key}",
9808
+ heroTicketCount: "{count} 个 ticket",
9809
+ heroBatchStarted: "已为 {count} 个 ticket 各自创建 Worktree 会话并开工",
8440
9810
  heroTicketNotConnected: "请先在 Settings 中连接 Jira",
8441
9811
  heroTicketFailed: "Ticket 搜索失败",
8442
9812
  jiraTitle: "Jira",
@@ -8456,14 +9826,31 @@ window.__ModuleLoader__.load({
8456
9826
  cleanupTitle: "清理已合并分支",
8457
9827
  cleanupDescription: "删除 worktree 与本地分支并移除对应 workspace;普通 checkout 则切回 base 分支并删除已合并分支。",
8458
9828
  cleanupCompleted: "已清理 {branch}",
8459
- overviewOpen: "查看所有 Claude 会话的 Pull Request",
8460
- overviewTitle: "Pull requests",
8461
- overviewBody: "所有 Claude 会话的分支与 PR 状态",
9829
+ overviewOpen: "打开 Claude 会话看板",
9830
+ overviewTitle: "会话看板",
9831
+ overviewBody: "所有 Claude 会话的运行状态、分支与 PR",
8462
9832
  overviewEmpty: "没有正在进行的 Claude 会话",
8463
9833
  overviewNoPr: "无 PR",
8464
9834
  overviewRunning: "运行中",
9835
+ overviewNeedsPermission: "等待权限确认",
9836
+ overviewNeedsAnswer: "等待回答",
9837
+ overviewContextUsage: "上下文 {percentage}%",
8465
9838
  overviewAutoFix: "Auto fix 已开启",
8466
9839
  overviewLoading: "读取中…",
9840
+ askToolbar: "选中文本操作",
9841
+ askTooltip: "针对选中内容追问",
9842
+ copyTooltip: "复制",
9843
+ askCopied: "已复制",
9844
+ askPlaceholder: "针对选中内容提问…",
9845
+ askSubmit: "发送",
9846
+ askStarting: "正在启动 Claude…",
9847
+ askThinking: "思考中…",
9848
+ askThinkingLive: "思考中 ·",
9849
+ askThinkingLabel: "Think ·",
9850
+ askCopyAnswer: "复制回答",
9851
+ askSendToChat: "转发到对话",
9852
+ askClose: "关闭",
9853
+ askFailed: "提问失败",
8467
9854
  reviewCommentAdd: "添加行评论",
8468
9855
  reviewCommentPlaceholder: "请求更改",
8469
9856
  reviewCommentSubmit: "注释",
@@ -8474,7 +9861,13 @@ window.__ModuleLoader__.load({
8474
9861
  reviewCommentsSend: "发送评论",
8475
9862
  reviewCommentsSendDraft: "请处理以上代码评论。",
8476
9863
  reviewCommentOldSide: "旧代码第 {line} 行",
8477
- reviewCommentNewSide: "第 {line} 行"
9864
+ reviewCommentNewSide: "第 {line} 行",
9865
+ reviewCommentOldRange: "旧代码第 {start}–{end} 行",
9866
+ reviewCommentNewRange: "第 {start}–{end} 行",
9867
+ diffUnmodifiedLines: "{count} 行未修改",
9868
+ diffUnmodifiedTail: "文件末尾之前还有未修改的行",
9869
+ diffExpandUp: "向上展开",
9870
+ diffExpandDown: "向下展开"
8478
9871
  };
8479
9872
  const en = {
8480
9873
  nav: "Claude Code",
@@ -8707,7 +10100,10 @@ window.__ModuleLoader__.load({
8707
10100
  heroTicketSearch: "Search tickets or enter a key…",
8708
10101
  heroTicketLoading: "Searching…",
8709
10102
  heroTicketEmpty: "No matching tickets",
8710
- heroTicketNone: "No ticket",
10103
+ heroTicketNone: "Clear selected tickets",
10104
+ heroTicketRemove: "Remove {key}",
10105
+ heroTicketCount: "{count} tickets",
10106
+ heroBatchStarted: "Kicked off {count} tickets, each in its own worktree session",
8711
10107
  heroTicketNotConnected: "Connect Jira in Settings first",
8712
10108
  heroTicketFailed: "Ticket search failed",
8713
10109
  jiraTitle: "Jira",
@@ -8727,14 +10123,31 @@ window.__ModuleLoader__.load({
8727
10123
  cleanupTitle: "Clean up merged branch",
8728
10124
  cleanupDescription: "Remove the worktree and local branch and delete the workspace; a plain checkout switches back to the base branch and deletes the merged branch.",
8729
10125
  cleanupCompleted: "Cleaned up {branch}",
8730
- overviewOpen: "Show pull requests across Claude sessions",
8731
- overviewTitle: "Pull requests",
8732
- overviewBody: "Branch and pull request state across Claude sessions",
10126
+ overviewOpen: "Open the Claude session board",
10127
+ overviewTitle: "Session board",
10128
+ overviewBody: "Run state, branch, and pull request across Claude sessions",
8733
10129
  overviewEmpty: "No active Claude sessions",
8734
10130
  overviewNoPr: "No PR",
8735
10131
  overviewRunning: "Running",
10132
+ overviewNeedsPermission: "Awaiting approval",
10133
+ overviewNeedsAnswer: "Awaiting answer",
10134
+ overviewContextUsage: "Context {percentage}%",
8736
10135
  overviewAutoFix: "Auto fix on",
8737
10136
  overviewLoading: "Loading…",
10137
+ askToolbar: "Selection actions",
10138
+ askTooltip: "Ask about this",
10139
+ copyTooltip: "Copy",
10140
+ askCopied: "Copied",
10141
+ askPlaceholder: "Ask about the selected text…",
10142
+ askSubmit: "Ask",
10143
+ askStarting: "Starting Claude…",
10144
+ askThinking: "Thinking…",
10145
+ askThinkingLive: "Thinking ·",
10146
+ askThinkingLabel: "Think ·",
10147
+ askCopyAnswer: "Copy answer",
10148
+ askSendToChat: "Send to chat",
10149
+ askClose: "Close",
10150
+ askFailed: "The question could not be answered",
8738
10151
  reviewCommentAdd: "Add line comment",
8739
10152
  reviewCommentPlaceholder: "Request changes",
8740
10153
  reviewCommentSubmit: "Comment",
@@ -8745,7 +10158,13 @@ window.__ModuleLoader__.load({
8745
10158
  reviewCommentsSend: "Send comments",
8746
10159
  reviewCommentsSendDraft: "Please address the attached review comments.",
8747
10160
  reviewCommentOldSide: "Old line {line}",
8748
- reviewCommentNewSide: "Line {line}"
10161
+ reviewCommentNewSide: "Line {line}",
10162
+ reviewCommentOldRange: "Old lines {start}–{end}",
10163
+ reviewCommentNewRange: "Lines {start}–{end}",
10164
+ diffUnmodifiedLines: "{count} unmodified lines",
10165
+ diffUnmodifiedTail: "More unmodified lines below",
10166
+ diffExpandUp: "Expand up",
10167
+ diffExpandDown: "Expand down"
8749
10168
  };
8750
10169
  //#endregion
8751
10170
  //#region src/client/index.tsx
@@ -8880,7 +10299,9 @@ window.__ModuleLoader__.load({
8880
10299
  sessions.open(id);
8881
10300
  },
8882
10301
  loadStatus: loadRepositoryStatusFor,
8883
- sessions: sessions.list
10302
+ sessions: sessions.list,
10303
+ ...workspaces === void 0 ? {} : { workspaces: workspaces.list },
10304
+ projectionFor: (id) => projections.source(id)
8884
10305
  })
8885
10306
  }, ClaudePullRequestsPanel);
8886
10307
  } catch {
@@ -9014,6 +10435,22 @@ window.__ModuleLoader__.load({
9014
10435
  };
9015
10436
  }
9016
10437
  }, ClaudeRepositoryStatus));
10438
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
10439
+ name: "shell.overlay",
10440
+ id: "claude-selection-ask",
10441
+ locale: namespace
10442
+ }, () => /* @__PURE__ */ jsx(ClaudeSelectionAsk, {
10443
+ t,
10444
+ currentSessionId: () => sessions?.list.getSnapshot().current,
10445
+ ownsSession: (sessionId) => projections.source(sessionId).getSnapshot().owned,
10446
+ ...sessions === void 0 || conversation === void 0 ? {} : { insertIntoChat: (sessionId, text) => {
10447
+ const scope = sessions.scope(sessionId);
10448
+ if (scope === void 0) return;
10449
+ const input = conversation.input.for(scope);
10450
+ const current = input.state.getSnapshot().draft;
10451
+ input.setDraft(current.trim() === "" ? text : `${current}\n\n${text}`);
10452
+ } }
10453
+ })));
9017
10454
  if (sessions !== void 0 && conversation !== void 0) ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
9018
10455
  name: "conversation.input.dock",
9019
10456
  id: "queue",
@@ -9077,6 +10514,52 @@ window.__ModuleLoader__.load({
9077
10514
  targetInput.submit();
9078
10515
  sourceInput.setDraft("");
9079
10516
  for (const imageId of imageIds) sourceInput.removeImage(imageId);
10517
+ },
10518
+ prepareMany: async (cwd, branch, tickets, onProgress) => {
10519
+ const sourceScope = sessions.scope(sourceSessionId);
10520
+ if (sourceScope === void 0) throw new Error(t("repositorySessionUnavailable"));
10521
+ const sourceInput = conversation.input.for(sourceScope);
10522
+ const rawDraft = sourceInput.state.getSnapshot().draft;
10523
+ const failures = [];
10524
+ for (const [index, ticket] of tickets.entries()) {
10525
+ const report = (stage) => {
10526
+ onProgress({
10527
+ ticketKey: ticket.key,
10528
+ index,
10529
+ total: tickets.length,
10530
+ stage
10531
+ });
10532
+ };
10533
+ try {
10534
+ report("inspecting");
10535
+ const prepared = await prepareRepository(cwd, branch, true, ticket.key, report);
10536
+ assignJiraTicket(ticket.key).catch((reason) => {
10537
+ console.warn(`dsh-claude: could not assign ${ticket.key}: ${reason instanceof Error ? reason.message : String(reason)}`);
10538
+ });
10539
+ report("creating-workspace");
10540
+ const workspace = await workspaces.create({ path: prepared.path });
10541
+ report("starting-session");
10542
+ const targetSessionId = await workspaces.connectWorkspace(workspace.workspaceId);
10543
+ const targetScope = sessions.scope(targetSessionId);
10544
+ if (targetScope === void 0) throw new Error(t("repositorySessionUnavailable"));
10545
+ const presetResponse = await connection.api.agentPresets.select({
10546
+ sessionId: targetSessionId,
10547
+ agentPreset: "claude"
10548
+ });
10549
+ if (!presetResponse.result.ok) throw new Error(presetResponse.result.error.message);
10550
+ sessions.noteAgentPreset(targetSessionId, presetResponse.result.value.agentPreset);
10551
+ const targetInput = conversation.input.for(targetScope);
10552
+ report("transferring-draft");
10553
+ targetInput.setDraft(rawDraft.trim() === "" ? ticketPrompt(ticket) : `${rawDraft.trimEnd()}\n\n${ticketContext(ticket)}`);
10554
+ if (prepared.leaseId !== void 0) await bindRepositoryLease(prepared.leaseId, targetSessionId);
10555
+ report("submitting");
10556
+ targetInput.submit();
10557
+ } catch (cause) {
10558
+ failures.push(`${ticket.key}: ${cause instanceof Error ? cause.message : String(cause)}`);
10559
+ }
10560
+ }
10561
+ if (failures.length < tickets.length) sourceInput.setDraft("");
10562
+ if (failures.length > 0) throw new Error(failures.join(" · "));
9080
10563
  }
9081
10564
  })
9082
10565
  }, ClaudeHeroRepositoryControls));