@norman-else/dsh-claude 0.1.25 → 0.1.27

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
@@ -12,6 +12,7 @@ window.__ModuleLoader__.load({
12
12
  const TASK_TOOL_NAMES = /* @__PURE__ */ new Set(["Task", "Agent"]);
13
13
  const CLAUDE_DOCTOR_PATH = "/plugins/dsh-claude/doctor";
14
14
  const CLAUDE_UPDATE_CHECK_PATH = "/plugins/dsh-claude/update/check";
15
+ const CLAUDE_USAGE_PATH = "/plugins/dsh-claude/usage";
15
16
  const CLAUDE_UPDATE_PATH = "/plugins/dsh-claude/update";
16
17
  const CLAUDE_PROJECTION_PATH = "/plugins/dsh-claude/projection";
17
18
  const CLAUDE_GLOBAL_SETTINGS_PATH = "/plugins/dsh-claude/settings/global";
@@ -20,7 +21,9 @@ window.__ModuleLoader__.load({
20
21
  const CLAUDE_REVIEW_COMMENT_PATH = "/plugins/dsh-claude/review-comments";
21
22
  const CLAUDE_REPOSITORY_FEEDBACK_PATH = "/plugins/dsh-claude/repository/feedback";
22
23
  const CLAUDE_REPOSITORY_STATUS_PATH = "/plugins/dsh-claude/repository/status";
24
+ const CLAUDE_REPOSITORY_FILE_PATH = "/plugins/dsh-claude/repository/file";
23
25
  const CLAUDE_JIRA_PATH = "/plugins/dsh-claude/jira";
26
+ const CLAUDE_ASK_PATH = "/plugins/dsh-claude/ask";
24
27
  //#endregion
25
28
  //#region src/client/task-projection.ts
26
29
  /** Tasks UI is reserved for detached work and genuine Claude subagents. */
@@ -711,6 +714,63 @@ window.__ModuleLoader__.load({
711
714
  fontWeight: 700,
712
715
  textAlign: "center"
713
716
  };
717
+ const settingHint = {
718
+ display: "inline-grid",
719
+ placeItems: "center",
720
+ width: 14,
721
+ height: 14,
722
+ marginLeft: 6,
723
+ verticalAlign: "text-top",
724
+ borderRadius: "50%",
725
+ border: "1px solid currentColor",
726
+ color: "var(--dsw-alias-label-tertiary)",
727
+ fontSize: 9,
728
+ fontWeight: 600,
729
+ lineHeight: 1,
730
+ cursor: "help",
731
+ userSelect: "none"
732
+ };
733
+ const planUsageMeter = {
734
+ display: "flex",
735
+ alignItems: "center",
736
+ gap: 10,
737
+ minWidth: 0
738
+ };
739
+ const planUsageTrack = {
740
+ flex: "1 1 auto",
741
+ height: 6,
742
+ minWidth: 60,
743
+ borderRadius: 3,
744
+ background: "var(--dsw-alias-bg-layer-2)",
745
+ overflow: "hidden"
746
+ };
747
+ const planUsageFill = {
748
+ display: "block",
749
+ height: "100%",
750
+ borderRadius: 3,
751
+ transition: "width 200ms ease"
752
+ };
753
+ const planUsageMeta = {
754
+ flex: "0 0 auto",
755
+ fontSize: 12,
756
+ whiteSpace: "nowrap"
757
+ };
758
+ /** Bar colour, plus a matching text colour once a window is worth noticing.
759
+ * Below the warning threshold the reading stays quiet: accent bar, muted text. */
760
+ function planUsageTone(utilization) {
761
+ if (utilization !== void 0 && utilization >= 90) return {
762
+ fill: "var(--dsw-alias-state-error-primary)",
763
+ text: "var(--dsw-alias-state-error-primary)"
764
+ };
765
+ if (utilization !== void 0 && utilization >= 75) return {
766
+ fill: "var(--dsw-alias-state-warning-primary, #d69e2e)",
767
+ text: "var(--dsw-alias-state-warning-primary, #d69e2e)"
768
+ };
769
+ return {
770
+ fill: utilization === void 0 ? "transparent" : "var(--dsw-static-blue-450)",
771
+ text: "var(--dsw-alias-label-tertiary)"
772
+ };
773
+ }
714
774
  const settingsActions = {
715
775
  display: "flex",
716
776
  flexWrap: "wrap",
@@ -751,16 +811,33 @@ window.__ModuleLoader__.load({
751
811
  const tasksPanel = {
752
812
  display: "flex",
753
813
  flexDirection: "column",
754
- width: "calc(100% - 16px)",
755
- height: "calc(100% - 16px)",
756
814
  minWidth: 0,
757
- margin: 8,
758
815
  overflow: "hidden",
759
816
  border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
760
817
  borderRadius: 12,
761
818
  background: "var(--dsw-alias-bg-layer-1)",
762
819
  boxShadow: "0 4px 16px color-mix(in srgb, #000 12%, transparent)"
763
820
  };
821
+ const detailsCardClass = "dshClaudeDetailsCard";
822
+ /**
823
+ * Geometry shared by every details-column card. Lives in a stylesheet rather
824
+ * than inline so the DSH Desktop macOS shell can be special-cased: its
825
+ * "advanced" frame floats a 32px opaque caption/drag row over the top of the
826
+ * conversation and details columns, 12px of which lands inside the column and
827
+ * used to cover the card's top border and corners.
828
+ */
829
+ const detailsCardCss = `
830
+ .${detailsCardClass} {
831
+ box-sizing: border-box;
832
+ width: calc(100% - 16px);
833
+ height: calc(100% - 16px);
834
+ margin: 8px;
835
+ }
836
+ .dshDesktopFrame[data-desktop-mode="advanced"][data-desktop-platform="darwin"] .dshDesktopDetailsSurface .${detailsCardClass} {
837
+ height: calc(100% - 28px);
838
+ margin-top: 20px;
839
+ }
840
+ `;
764
841
  const tasksHeader = {
765
842
  boxSizing: "border-box",
766
843
  height: 58,
@@ -1131,7 +1208,7 @@ window.__ModuleLoader__.load({
1131
1208
  background: "transparent",
1132
1209
  color: "inherit",
1133
1210
  font: "inherit",
1134
- fontSize: 12,
1211
+ fontSize: 13,
1135
1212
  lineHeight: "18px",
1136
1213
  cursor: "pointer"
1137
1214
  };
@@ -1178,7 +1255,7 @@ window.__ModuleLoader__.load({
1178
1255
  background: "transparent",
1179
1256
  color: "var(--dsw-alias-label-primary)",
1180
1257
  font: "inherit",
1181
- fontSize: 12,
1258
+ fontSize: 13,
1182
1259
  lineHeight: "18px"
1183
1260
  };
1184
1261
  const heroBranchList = {
@@ -1190,8 +1267,8 @@ window.__ModuleLoader__.load({
1190
1267
  };
1191
1268
  const heroBranchItem = {
1192
1269
  width: "100%",
1193
- height: 34,
1194
- minHeight: 34,
1270
+ height: 36,
1271
+ minHeight: 36,
1195
1272
  display: "flex",
1196
1273
  alignItems: "center",
1197
1274
  gap: 8,
@@ -1201,7 +1278,7 @@ window.__ModuleLoader__.load({
1201
1278
  background: "transparent",
1202
1279
  color: "var(--dsw-alias-label-primary)",
1203
1280
  font: "inherit",
1204
- fontSize: 12,
1281
+ fontSize: 13,
1205
1282
  lineHeight: "18px",
1206
1283
  textAlign: "left",
1207
1284
  cursor: "pointer"
@@ -1215,11 +1292,11 @@ window.__ModuleLoader__.load({
1215
1292
  whiteSpace: "nowrap"
1216
1293
  };
1217
1294
  const heroBranchEmpty = {
1218
- height: 34,
1295
+ height: 36,
1219
1296
  display: "grid",
1220
1297
  placeItems: "center",
1221
1298
  color: "var(--dsw-alias-label-tertiary)",
1222
- fontSize: 12
1299
+ fontSize: 13
1223
1300
  };
1224
1301
  const heroRepositoryDivider = {
1225
1302
  width: 1,
@@ -1239,7 +1316,7 @@ window.__ModuleLoader__.load({
1239
1316
  background: "transparent",
1240
1317
  color: "var(--dsw-alias-label-secondary)",
1241
1318
  font: "inherit",
1242
- fontSize: 12,
1319
+ fontSize: 13,
1243
1320
  lineHeight: "18px",
1244
1321
  whiteSpace: "nowrap",
1245
1322
  cursor: "pointer",
@@ -1332,6 +1409,10 @@ window.__ModuleLoader__.load({
1332
1409
  lineHeight: "18px",
1333
1410
  overflowWrap: "anywhere"
1334
1411
  };
1412
+ const heroWorktreeProgressContext = {
1413
+ color: "var(--dsw-alias-label-secondary)",
1414
+ fontWeight: 500
1415
+ };
1335
1416
  const heroWorktreeProgressDismiss = {
1336
1417
  width: 24,
1337
1418
  height: 24,
@@ -1691,6 +1772,14 @@ window.__ModuleLoader__.load({
1691
1772
  borderRadius: 999,
1692
1773
  background: "currentColor"
1693
1774
  };
1775
+ const repositoryGlyph = {
1776
+ width: 16,
1777
+ height: 16,
1778
+ flex: "none",
1779
+ display: "grid",
1780
+ placeItems: "center",
1781
+ color: "var(--dsw-alias-label-tertiary)"
1782
+ };
1694
1783
  const repositoryItemSuccess = { color: "var(--dsw-alias-state-success-primary)" };
1695
1784
  const repositoryItemWarning = { color: "var(--dsw-alias-state-warning-primary, #d69e2e)" };
1696
1785
  const repositoryItemError = { color: "var(--dsw-alias-state-error-primary)" };
@@ -1909,19 +1998,77 @@ window.__ModuleLoader__.load({
1909
1998
  fontWeight: 650,
1910
1999
  cursor: "pointer"
1911
2000
  };
2001
+ /** Ticket summaries need room; the branch menu's 280px truncates them badly. */
2002
+ const heroTicketMenu = { width: 420 };
1912
2003
  const heroTicketKey = {
1913
2004
  flex: "none",
1914
- color: "var(--dsw-alias-label-tertiary)",
1915
- fontSize: 11,
2005
+ color: "var(--dsw-alias-label-secondary)",
2006
+ fontSize: 12,
1916
2007
  fontWeight: 650,
1917
2008
  letterSpacing: .2
1918
2009
  };
2010
+ const heroTicketChips = {
2011
+ display: "flex",
2012
+ flexWrap: "wrap",
2013
+ alignItems: "center",
2014
+ gap: 6,
2015
+ padding: "2px 2px 6px",
2016
+ borderBottom: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 12%, transparent))"
2017
+ };
2018
+ const heroTicketChip = {
2019
+ display: "inline-flex",
2020
+ alignItems: "center",
2021
+ gap: 4,
2022
+ padding: "2px 4px 2px 8px",
2023
+ border: "1px solid color-mix(in srgb, var(--dsw-static-blue-450) 40%, transparent)",
2024
+ borderRadius: 999,
2025
+ background: "color-mix(in srgb, var(--dsw-static-blue-450) 12%, transparent)",
2026
+ color: "var(--dsw-alias-label-primary)",
2027
+ fontSize: 12,
2028
+ fontWeight: 600,
2029
+ letterSpacing: .2
2030
+ };
2031
+ const heroTicketChipRemove = {
2032
+ width: 16,
2033
+ height: 16,
2034
+ display: "grid",
2035
+ placeItems: "center",
2036
+ padding: 0,
2037
+ border: "none",
2038
+ borderRadius: 999,
2039
+ background: "transparent",
2040
+ color: "var(--dsw-alias-label-tertiary)",
2041
+ fontSize: 12,
2042
+ lineHeight: 1,
2043
+ cursor: "pointer"
2044
+ };
2045
+ const heroTicketChipsClear = {
2046
+ padding: "2px 4px",
2047
+ border: "none",
2048
+ background: "transparent",
2049
+ color: "var(--dsw-alias-label-tertiary)",
2050
+ fontSize: 12,
2051
+ cursor: "pointer"
2052
+ };
2053
+ const heroTicketCheckbox = {
2054
+ width: 15,
2055
+ height: 15,
2056
+ boxSizing: "border-box",
2057
+ display: "grid",
2058
+ placeItems: "center",
2059
+ flex: "none",
2060
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2061
+ borderRadius: 4,
2062
+ background: "var(--dsw-alias-bg-layer-2)",
2063
+ color: "var(--dsw-alias-label-on-accent, #fff)",
2064
+ transition: "border-color 120ms ease, background 120ms ease"
2065
+ };
1919
2066
  const heroTicketStatus = {
1920
2067
  flex: "none",
1921
- maxWidth: 90,
2068
+ maxWidth: 110,
1922
2069
  overflow: "hidden",
1923
2070
  color: "var(--dsw-alias-label-tertiary)",
1924
- fontSize: 11,
2071
+ fontSize: 12,
1925
2072
  textOverflow: "ellipsis",
1926
2073
  whiteSpace: "nowrap"
1927
2074
  };
@@ -2008,6 +2155,197 @@ window.__ModuleLoader__.load({
2008
2155
  fontSize: 13,
2009
2156
  textAlign: "center"
2010
2157
  };
2158
+ const askSurfaceBorder = "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))";
2159
+ const askSurfaceShadow = "var(--dsw-shadow-lv3, 0 8px 24px color-mix(in srgb, #000 24%, transparent))";
2160
+ /** Painted through CSS.highlights while the follow-up popup is open. */
2161
+ const askHighlightCss = `
2162
+ ::highlight(dsh-claude-ask) {
2163
+ background-color: color-mix(in srgb, var(--dsw-static-blue-450) 30%, transparent);
2164
+ color: inherit;
2165
+ }
2166
+ `;
2167
+ const askToolbar = {
2168
+ position: "fixed",
2169
+ zIndex: 2e3,
2170
+ pointerEvents: "auto",
2171
+ display: "inline-flex",
2172
+ alignItems: "center",
2173
+ gap: 2,
2174
+ padding: 3,
2175
+ border: askSurfaceBorder,
2176
+ borderRadius: 9,
2177
+ background: "var(--dsw-alias-bg-layer-1)",
2178
+ color: "var(--dsw-alias-label-primary)",
2179
+ boxShadow: askSurfaceShadow
2180
+ };
2181
+ const askPopup = {
2182
+ position: "fixed",
2183
+ zIndex: 2e3,
2184
+ pointerEvents: "auto",
2185
+ boxSizing: "border-box",
2186
+ display: "flex",
2187
+ flexDirection: "column",
2188
+ gap: 10,
2189
+ padding: 12,
2190
+ border: askSurfaceBorder,
2191
+ borderRadius: 12,
2192
+ background: "var(--dsw-alias-bg-layer-1)",
2193
+ color: "var(--dsw-alias-label-primary)",
2194
+ boxShadow: askSurfaceShadow,
2195
+ fontSize: 13,
2196
+ lineHeight: "20px"
2197
+ };
2198
+ const askQuote = {
2199
+ flex: "none",
2200
+ display: "-webkit-box",
2201
+ WebkitLineClamp: 3,
2202
+ WebkitBoxOrient: "vertical",
2203
+ margin: 0,
2204
+ padding: "6px 10px",
2205
+ overflow: "hidden",
2206
+ borderLeft: "3px solid color-mix(in srgb, var(--dsw-static-blue-450) 60%, transparent)",
2207
+ borderRadius: 6,
2208
+ background: "var(--dsw-alias-bg-layer-2)",
2209
+ color: "var(--dsw-alias-label-secondary)",
2210
+ fontSize: 12,
2211
+ lineHeight: "18px",
2212
+ whiteSpace: "pre-wrap",
2213
+ wordBreak: "break-word"
2214
+ };
2215
+ const askQuestion = {
2216
+ flex: "none",
2217
+ margin: 0,
2218
+ color: "var(--dsw-alias-label-primary)",
2219
+ fontWeight: 600,
2220
+ whiteSpace: "pre-wrap",
2221
+ wordBreak: "break-word"
2222
+ };
2223
+ const askTextarea = {
2224
+ width: "100%",
2225
+ minHeight: 64,
2226
+ boxSizing: "border-box",
2227
+ padding: "8px 10px",
2228
+ border: askSurfaceBorder,
2229
+ borderRadius: 8,
2230
+ outline: "none",
2231
+ background: "var(--dsw-alias-bg-layer-2)",
2232
+ color: "var(--dsw-alias-label-primary)",
2233
+ font: "inherit",
2234
+ fontSize: 13,
2235
+ lineHeight: "20px",
2236
+ resize: "vertical"
2237
+ };
2238
+ const askActions = {
2239
+ flex: "none",
2240
+ display: "flex",
2241
+ alignItems: "center",
2242
+ justifyContent: "flex-end",
2243
+ gap: 6
2244
+ };
2245
+ const askAnswer = {
2246
+ flex: "1 1 auto",
2247
+ minHeight: 0,
2248
+ overflowY: "auto",
2249
+ padding: "2px 2px 0",
2250
+ wordBreak: "break-word"
2251
+ };
2252
+ const askButton = {
2253
+ minHeight: 26,
2254
+ padding: "3px 10px",
2255
+ border: askSurfaceBorder,
2256
+ borderRadius: 7,
2257
+ outline: "none",
2258
+ background: "var(--dsw-alias-bg-layer-2)",
2259
+ color: "var(--dsw-alias-label-primary)",
2260
+ font: "inherit",
2261
+ fontSize: 12,
2262
+ lineHeight: "18px",
2263
+ fontWeight: 600,
2264
+ cursor: "pointer"
2265
+ };
2266
+ const askPrimaryButton = {
2267
+ border: "1px solid transparent",
2268
+ background: "var(--dsw-static-blue-450)",
2269
+ color: "var(--dsw-alias-label-on-accent, #fff)"
2270
+ };
2271
+ const askThinkingRow = {
2272
+ width: "100%",
2273
+ display: "flex",
2274
+ alignItems: "baseline",
2275
+ gap: 6,
2276
+ margin: "0 0 8px",
2277
+ padding: 0,
2278
+ border: "none",
2279
+ outline: "none",
2280
+ background: "transparent",
2281
+ color: "var(--dsw-alias-label-tertiary)",
2282
+ font: "inherit",
2283
+ fontSize: 12,
2284
+ lineHeight: "18px",
2285
+ textAlign: "left",
2286
+ cursor: "pointer"
2287
+ };
2288
+ const askThinkingLabel = {
2289
+ flex: "none",
2290
+ fontWeight: 600
2291
+ };
2292
+ const askThinkingPreview = {
2293
+ minWidth: 0,
2294
+ flex: 1,
2295
+ overflow: "hidden",
2296
+ textOverflow: "ellipsis",
2297
+ whiteSpace: "nowrap"
2298
+ };
2299
+ const askThinkingFull = {
2300
+ minWidth: 0,
2301
+ flex: 1,
2302
+ maxHeight: 200,
2303
+ overflowY: "auto",
2304
+ whiteSpace: "pre-wrap",
2305
+ wordBreak: "break-word"
2306
+ };
2307
+ const askToolRow = {
2308
+ display: "flex",
2309
+ alignItems: "baseline",
2310
+ gap: 6,
2311
+ margin: "2px 0 6px",
2312
+ minWidth: 0,
2313
+ color: "var(--dsw-alias-label-tertiary)",
2314
+ fontSize: 12,
2315
+ lineHeight: "18px"
2316
+ };
2317
+ const askToolGlyph = {
2318
+ flex: "none",
2319
+ width: 14,
2320
+ textAlign: "center",
2321
+ color: "var(--dsw-static-blue-450)"
2322
+ };
2323
+ const askToolGlyphDone = { color: "var(--dsw-alias-state-success-primary)" };
2324
+ const askToolGlyphError = { color: "var(--dsw-alias-state-error-primary)" };
2325
+ const askToolName = {
2326
+ flex: "none",
2327
+ color: "var(--dsw-alias-label-secondary)",
2328
+ fontWeight: 600
2329
+ };
2330
+ const askToolSummary = {
2331
+ minWidth: 0,
2332
+ flex: 1,
2333
+ overflow: "hidden",
2334
+ fontFamily: "var(--dsw-font-family-code, ui-monospace, monospace)",
2335
+ fontSize: 11,
2336
+ textOverflow: "ellipsis",
2337
+ whiteSpace: "nowrap"
2338
+ };
2339
+ const askStatus = {
2340
+ marginRight: "auto",
2341
+ color: "var(--dsw-alias-label-tertiary)",
2342
+ fontSize: 12
2343
+ };
2344
+ const askError = {
2345
+ margin: 0,
2346
+ color: "var(--dsw-alias-state-error-primary)",
2347
+ fontSize: 12
2348
+ };
2011
2349
  const repositoryMergeTrigger = {
2012
2350
  flex: "none",
2013
2351
  display: "inline-flex",
@@ -2037,8 +2375,11 @@ window.__ModuleLoader__.load({
2037
2375
  flex: none;
2038
2376
  display: grid;
2039
2377
  place-items: center;
2040
- align-self: center;
2041
- margin: 0 2px;
2378
+ /* Stay on the line-number row: a wrapped line makes the flex row taller, and
2379
+ centering would float the button into the middle of the wrapped block.
2380
+ The margin centers the 18px button inside the first 22px line box. */
2381
+ align-self: flex-start;
2382
+ margin: 2px 2px 0;
2042
2383
  padding: 0;
2043
2384
  border: none;
2044
2385
  border-radius: 4px;
@@ -2231,10 +2572,7 @@ window.__ModuleLoader__.load({
2231
2572
  const diffPanel = {
2232
2573
  display: "flex",
2233
2574
  flexDirection: "column",
2234
- width: "calc(100% - 16px)",
2235
- height: "calc(100% - 16px)",
2236
2575
  minWidth: 0,
2237
- margin: 8,
2238
2576
  overflow: "hidden",
2239
2577
  border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2240
2578
  borderRadius: 12,
@@ -2349,26 +2687,41 @@ window.__ModuleLoader__.load({
2349
2687
  background: "var(--dsw-alias-bg-layer-1)",
2350
2688
  color: "var(--dsw-alias-label-primary)",
2351
2689
  font: "inherit",
2352
- fontSize: 12,
2690
+ fontSize: 13,
2691
+ lineHeight: "20px",
2353
2692
  textAlign: "left",
2354
2693
  cursor: "pointer"
2355
2694
  };
2356
2695
  const diffFilePath = {
2357
2696
  minWidth: 0,
2358
2697
  flex: 1,
2698
+ display: "flex",
2699
+ alignItems: "baseline",
2700
+ overflow: "hidden"
2701
+ };
2702
+ /** Directory part: truncated from the left so the nearest folders stay visible. */
2703
+ const diffFileDir = {
2704
+ minWidth: 0,
2359
2705
  overflow: "hidden",
2360
2706
  textOverflow: "ellipsis",
2707
+ whiteSpace: "nowrap",
2708
+ direction: "rtl",
2709
+ textAlign: "left",
2710
+ color: "var(--dsw-alias-label-tertiary)"
2711
+ };
2712
+ const diffFileName = {
2713
+ flex: "none",
2714
+ fontWeight: 650,
2361
2715
  whiteSpace: "nowrap"
2362
2716
  };
2363
2717
  const diffFileStats = {
2364
2718
  flex: "none",
2365
2719
  display: "inline-flex",
2366
2720
  gap: 4,
2367
- fontSize: 12,
2721
+ fontSize: 13,
2368
2722
  fontWeight: 650
2369
2723
  };
2370
2724
  const diffCode = {
2371
- minWidth: "max-content",
2372
2725
  padding: "4px 0 8px",
2373
2726
  background: "var(--dsw-alias-bg-base)",
2374
2727
  fontFamily: "var(--dsw-font-family-mono, ui-monospace, SFMono-Regular, Consolas, monospace)",
@@ -2378,7 +2731,12 @@ window.__ModuleLoader__.load({
2378
2731
  const diffLine = {
2379
2732
  minHeight: 17,
2380
2733
  display: "flex",
2381
- whiteSpace: "pre"
2734
+ whiteSpace: "pre-wrap",
2735
+ overflowWrap: "anywhere"
2736
+ };
2737
+ const diffLineText = {
2738
+ flex: 1,
2739
+ minWidth: 0
2382
2740
  };
2383
2741
  const diffLineNumber = {
2384
2742
  width: 38,
@@ -2408,6 +2766,40 @@ window.__ModuleLoader__.load({
2408
2766
  color: "var(--dsw-alias-label-tertiary)",
2409
2767
  fontStyle: "italic"
2410
2768
  };
2769
+ const diffLineSelected = {
2770
+ background: "color-mix(in srgb, var(--dsw-static-blue-450) 14%, transparent)",
2771
+ boxShadow: "inset 2px 0 0 var(--dsw-static-blue-450)"
2772
+ };
2773
+ /** Sits where the comment button, line number and marker would be so the label aligns with code. */
2774
+ const diffGapControls = {
2775
+ width: 84,
2776
+ flex: "none",
2777
+ boxSizing: "border-box",
2778
+ display: "inline-flex",
2779
+ alignItems: "center",
2780
+ justifyContent: "flex-end",
2781
+ gap: 2,
2782
+ paddingRight: 6
2783
+ };
2784
+ const diffGapButton = {
2785
+ width: 20,
2786
+ height: 18,
2787
+ display: "grid",
2788
+ placeItems: "center",
2789
+ padding: 0,
2790
+ border: "none",
2791
+ borderRadius: 4,
2792
+ background: "transparent",
2793
+ color: "var(--dsw-alias-label-secondary)",
2794
+ font: "inherit",
2795
+ cursor: "pointer"
2796
+ };
2797
+ const diffCommentRange = {
2798
+ marginBottom: 6,
2799
+ color: "var(--dsw-alias-label-secondary)",
2800
+ fontSize: 12,
2801
+ fontWeight: 600
2802
+ };
2411
2803
  const diffLineContext = { color: "var(--dsw-alias-label-secondary)" };
2412
2804
  const diffNotice = {
2413
2805
  margin: 10,
@@ -2781,11 +3173,12 @@ window.__ModuleLoader__.load({
2781
3173
  const clearFinished = () => setDismissedSettledIds((previous) => /* @__PURE__ */ new Set([...previous, ...tasks.filter((task) => task.status !== "running").map((task) => task.taskId)]));
2782
3174
  if (!projection.owned) return null;
2783
3175
  return /* @__PURE__ */ jsxs("div", {
3176
+ className: detailsCardClass,
2784
3177
  style: tasksPanel,
2785
3178
  children: [
2786
- /* @__PURE__ */ jsx("style", {
3179
+ /* @__PURE__ */ jsxs("style", {
2787
3180
  "data-dsh-claude-panel-icon-styles": true,
2788
- children: panelIconButtonCss
3181
+ children: [detailsCardCss, panelIconButtonCss]
2789
3182
  }),
2790
3183
  /* @__PURE__ */ jsxs("div", {
2791
3184
  style: tasksHeader,
@@ -3018,7 +3411,7 @@ window.__ModuleLoader__.load({
3018
3411
  const MAX_REVIEW_COMMENTS = 50;
3019
3412
  const MAX_REVIEW_COMMENT_CHARS = 2e3;
3020
3413
  const MAX_TRANSCRIPT_CHARS = 64e3;
3021
- function record$6(value) {
3414
+ function record$7(value) {
3022
3415
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3023
3416
  }
3024
3417
  function nonNegativeInteger(value) {
@@ -3028,18 +3421,18 @@ window.__ModuleLoader__.load({
3028
3421
  return value === void 0 || typeof value === "string" && value.length <= MAX_REPOSITORY_TEXT_CHARS;
3029
3422
  }
3030
3423
  function validateRepository(value) {
3031
- const repository = record$6(value);
3424
+ const repository = record$7(value);
3032
3425
  if (repository === void 0 || ![
3033
3426
  "ready",
3034
3427
  "not-repository",
3035
3428
  "unavailable"
3036
3429
  ].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
3430
  if (repository.diff !== void 0) {
3038
- const diff = record$6(repository.diff);
3431
+ const diff = record$7(repository.diff);
3039
3432
  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
3433
  }
3041
3434
  if (repository.pullRequest === void 0) return true;
3042
- const pullRequest = record$6(repository.pullRequest);
3435
+ const pullRequest = record$7(repository.pullRequest);
3043
3436
  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
3437
  "open",
3045
3438
  "closed",
@@ -3064,24 +3457,24 @@ window.__ModuleLoader__.load({
3064
3457
  }
3065
3458
  /** Validate the public route envelope before publishing it to UI components. */
3066
3459
  function parseClaudeClientProjection(value) {
3067
- const input = record$6(value);
3460
+ const input = record$7(value);
3068
3461
  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
3462
  for (const item of input.commands) {
3070
- const command = record$6(item);
3463
+ const command = record$7(item);
3071
3464
  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
3465
  }
3073
3466
  for (const item of input.activities) {
3074
- const activity = record$6(item);
3467
+ const activity = record$7(item);
3075
3468
  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
3469
  }
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);
3470
+ if (input.contextUsage !== void 0 && record$7(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
3471
+ const tasks = input.tasks === void 0 ? void 0 : record$7(input.tasks);
3079
3472
  if (tasks !== void 0 && !Array.isArray(tasks.tasks)) throw new Error("invalid Claude tasks projection");
3080
3473
  if (input.repository !== void 0 && !validateRepository(input.repository)) throw new Error("invalid Claude repository projection");
3081
3474
  if (input.reviewComments !== void 0) {
3082
3475
  if (!Array.isArray(input.reviewComments) || input.reviewComments.length > MAX_REVIEW_COMMENTS) throw new Error("invalid Claude review comment projection");
3083
3476
  for (const item of input.reviewComments) {
3084
- const comment = record$6(item);
3477
+ const comment = record$7(item);
3085
3478
  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
3479
  }
3087
3480
  }
@@ -3279,7 +3672,7 @@ window.__ModuleLoader__.load({
3279
3672
  } catch {
3280
3673
  return;
3281
3674
  }
3282
- const event = record$6(value);
3675
+ const event = record$7(value);
3283
3676
  if (event === void 0 || typeof event.type !== "string") return;
3284
3677
  try {
3285
3678
  switch (event.type) {
@@ -3538,7 +3931,7 @@ window.__ModuleLoader__.load({
3538
3931
  return value;
3539
3932
  }
3540
3933
  }
3541
- function record$5(value) {
3934
+ function record$6(value) {
3542
3935
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3543
3936
  }
3544
3937
  function text(value) {
@@ -3552,7 +3945,7 @@ window.__ModuleLoader__.load({
3552
3945
  if (typeof value === "string") return value;
3553
3946
  if (typeof value === "number" || typeof value === "boolean") return String(value);
3554
3947
  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");
3948
+ return record$6(value) === void 0 ? String(value) : Object.entries(value).map(([key, item]) => `${key}: ${displayValue(item)}`).join("\n");
3556
3949
  }
3557
3950
  function Section({ title, children }) {
3558
3951
  return /* @__PURE__ */ jsxs("section", {
@@ -3622,15 +4015,15 @@ window.__ModuleLoader__.load({
3622
4015
  function ToolPresentation({ tool, t }) {
3623
4016
  const inputValue = parsedValue(tool.input);
3624
4017
  const outputValue = parsedValue(tool.output);
3625
- const input = record$5(inputValue);
3626
- const output = record$5(outputValue);
4018
+ const input = record$6(inputValue);
4019
+ const output = record$6(outputValue);
3627
4020
  const outputTitle = tool.isError === true ? t("toolError") : t("toolOutput");
3628
4021
  if (tool.diffs !== void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DiffBlock, { diffs: [...tool.diffs] }), /* @__PURE__ */ jsx(TextDetail, {
3629
4022
  title: outputTitle,
3630
4023
  value: typeof outputValue === "string" ? outputValue : void 0
3631
4024
  })] });
3632
4025
  if (tool.toolName === "Read") {
3633
- const file = record$5(output?.file);
4026
+ const file = record$6(output?.file);
3634
4027
  const path = text(file?.filePath) ?? text(input?.file_path);
3635
4028
  const content = text(file?.content) ?? (typeof outputValue === "string" ? outputValue : void 0);
3636
4029
  const offset = numberValue(input?.offset) ?? 1;
@@ -3828,7 +4221,7 @@ window.__ModuleLoader__.load({
3828
4221
  if (code !== void 0) this.code = code;
3829
4222
  }
3830
4223
  };
3831
- function record$4(value) {
4224
+ function record$5(value) {
3832
4225
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3833
4226
  }
3834
4227
  async function call(path, init) {
@@ -3836,7 +4229,7 @@ window.__ModuleLoader__.load({
3836
4229
  credentials: "same-origin",
3837
4230
  ...init
3838
4231
  });
3839
- const body = record$4(await response.json());
4232
+ const body = record$5(await response.json());
3840
4233
  if (!response.ok) throw new JiraClientError(typeof body?.message === "string" ? body.message : "Jira is unavailable.", typeof body?.error === "string" ? body.error : void 0);
3841
4234
  if (body === void 0) throw new JiraClientError("Invalid Jira response.");
3842
4235
  return body;
@@ -3877,7 +4270,7 @@ window.__ModuleLoader__.load({
3877
4270
  if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
3878
4271
  const tickets = [];
3879
4272
  for (const item of body.tickets) {
3880
- const ticket = record$4(item);
4273
+ const ticket = record$5(item);
3881
4274
  if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
3882
4275
  tickets.push(ticket);
3883
4276
  }
@@ -4086,6 +4479,183 @@ window.__ModuleLoader__.load({
4086
4479
  }) : null]
4087
4480
  });
4088
4481
  }
4482
+ /** Fixed windows carry a translated label; server-named model buckets (e.g.
4483
+ * 'Fable') arrive with their own `label` and are shown verbatim. */
4484
+ const TRANSLATED_WINDOWS = /* @__PURE__ */ new Set([
4485
+ "five_hour",
4486
+ "seven_day",
4487
+ "seven_day_opus",
4488
+ "seven_day_sonnet"
4489
+ ]);
4490
+ /** Per-setting label and the effect note that used to sit as a standalone
4491
+ * paragraph under the card; it now hangs off the label as a hover hint. */
4492
+ const SETTING_COPY = {
4493
+ outputStyle: {
4494
+ label: "outputStyle",
4495
+ hint: "globalSettingsNewSession"
4496
+ },
4497
+ worktreeBranchPrefix: {
4498
+ label: "worktreeBranchPrefix",
4499
+ hint: "worktreeBranchPrefixEffect"
4500
+ },
4501
+ maxProcesses: {
4502
+ label: "maxProcessesSetting",
4503
+ hint: "maxProcessesEffect"
4504
+ },
4505
+ idleTimeoutMinutes: {
4506
+ label: "idleTimeoutSetting",
4507
+ hint: "idleTimeoutEffect"
4508
+ }
4509
+ };
4510
+ /** '?' badge that reveals a setting's effect note on hover or keyboard focus. */
4511
+ function SettingHint({ text, label }) {
4512
+ return /* @__PURE__ */ jsx(Tooltip, {
4513
+ label: text,
4514
+ side: "top",
4515
+ delayMs: 150,
4516
+ maxWidth: 320,
4517
+ children: /* @__PURE__ */ jsx("span", {
4518
+ role: "note",
4519
+ tabIndex: 0,
4520
+ "aria-label": `${label}: ${text}`,
4521
+ style: settingHint,
4522
+ children: "?"
4523
+ })
4524
+ });
4525
+ }
4526
+ function isPlanUsageReport(value) {
4527
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
4528
+ const report = value;
4529
+ return typeof report.available === "boolean" && typeof report.fetchedAt === "number" && Array.isArray(report.windows) && report.windows.every((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string");
4530
+ }
4531
+ /** Coarse duration for a reset countdown or a fetch age: minutes below an
4532
+ * hour, then hours, then days. Never negative — a passed reset reads '0m'. */
4533
+ function durationLabel(milliseconds) {
4534
+ const minutes = Math.max(0, Math.round(milliseconds / 6e4));
4535
+ if (minutes < 60) return `${minutes}m`;
4536
+ const hours = Math.floor(minutes / 60);
4537
+ if (hours < 24) return minutes % 60 === 0 ? `${hours}h` : `${hours}h ${minutes % 60}m`;
4538
+ return `${Math.floor(hours / 24)}d`;
4539
+ }
4540
+ function PlanUsageMeter({ window: usageWindow, t, now }) {
4541
+ const used = usageWindow.utilization;
4542
+ const tone = planUsageTone(used);
4543
+ const resetsIn = usageWindow.resetsAt === void 0 ? void 0 : Date.parse(usageWindow.resetsAt);
4544
+ return /* @__PURE__ */ jsxs("span", {
4545
+ style: planUsageMeter,
4546
+ children: [/* @__PURE__ */ jsx("span", {
4547
+ style: planUsageTrack,
4548
+ children: /* @__PURE__ */ jsx("span", { style: {
4549
+ ...planUsageFill,
4550
+ width: `${used ?? 0}%`,
4551
+ background: tone.fill
4552
+ } })
4553
+ }), /* @__PURE__ */ jsxs("span", {
4554
+ style: {
4555
+ ...planUsageMeta,
4556
+ color: tone.text
4557
+ },
4558
+ children: [used === void 0 ? "—" : t("planUsageUsed", { percent: Math.round(used) }), resetsIn === void 0 || !Number.isFinite(resetsIn) ? null : ` · ${t("planUsageResets", { age: durationLabel(resetsIn - now) })}`]
4559
+ })]
4560
+ });
4561
+ }
4562
+ /** Fetch the plan usage report; POST forces the backend to re-read it. */
4563
+ async function loadPlanUsage(refresh) {
4564
+ const response = await fetch(CLAUDE_USAGE_PATH, {
4565
+ method: refresh ? "POST" : "GET",
4566
+ credentials: "same-origin",
4567
+ headers: { accept: "application/json" }
4568
+ });
4569
+ const payload = await response.json();
4570
+ if (!response.ok) {
4571
+ const message = typeof payload === "object" && payload !== null && "error" in payload && typeof payload.error === "string" ? payload.error : `HTTP ${response.status}`;
4572
+ throw new Error(message);
4573
+ }
4574
+ if (!isPlanUsageReport(payload)) throw new Error("Invalid plan usage response");
4575
+ return payload;
4576
+ }
4577
+ function PlanUsageCard({ t, load }) {
4578
+ const [report, setReport] = useState();
4579
+ const [busy, setBusy] = useState(false);
4580
+ const [error, setError] = useState();
4581
+ const request = useCallback(async (refresh) => {
4582
+ setBusy(true);
4583
+ setError(void 0);
4584
+ try {
4585
+ setReport(await load(refresh));
4586
+ } catch (cause) {
4587
+ setError(cause instanceof Error ? cause.message : String(cause));
4588
+ } finally {
4589
+ setBusy(false);
4590
+ }
4591
+ }, [load]);
4592
+ useEffect(() => {
4593
+ request(false);
4594
+ }, [request]);
4595
+ const now = Date.now();
4596
+ return /* @__PURE__ */ jsxs("section", {
4597
+ style: settingsCard,
4598
+ children: [
4599
+ /* @__PURE__ */ jsxs("div", {
4600
+ style: settingsCardHeader,
4601
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
4602
+ style: settingsSectionHeading,
4603
+ children: t("planUsage")
4604
+ }), /* @__PURE__ */ jsx("p", {
4605
+ style: settingsBody,
4606
+ children: t("planUsageBody")
4607
+ })] }), /* @__PURE__ */ jsx("button", {
4608
+ type: "button",
4609
+ style: button,
4610
+ disabled: busy,
4611
+ onClick: () => {
4612
+ request(true);
4613
+ },
4614
+ children: busy ? t("planUsageRefreshing") : t("planUsageRefresh")
4615
+ })]
4616
+ }),
4617
+ report === void 0 ? /* @__PURE__ */ jsx("p", {
4618
+ style: notice,
4619
+ children: busy ? t("planUsageLoading") : t("planUsageNever")
4620
+ }) : report.windows.length === 0 ? /* @__PURE__ */ jsx("p", {
4621
+ style: notice,
4622
+ children: report.fetchedAt === 0 ? t("planUsageNever") : t("planUsageUnavailable")
4623
+ }) : /* @__PURE__ */ jsxs("div", {
4624
+ style: diagnosticGrid,
4625
+ children: [report.subscription === void 0 ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
4626
+ style: diagnosticLabel,
4627
+ children: t("planUsageSubscription")
4628
+ }), /* @__PURE__ */ jsx("span", {
4629
+ style: diagnosticValue,
4630
+ children: report.subscription
4631
+ })] }), report.windows.flatMap((usageWindow) => [/* @__PURE__ */ jsx("span", {
4632
+ style: diagnosticLabel,
4633
+ children: usageWindow.label ?? (TRANSLATED_WINDOWS.has(usageWindow.id) ? t(`planWindow_${usageWindow.id}`) : usageWindow.id)
4634
+ }, `${usageWindow.id}-label`), /* @__PURE__ */ jsx(PlanUsageMeter, {
4635
+ window: usageWindow,
4636
+ t,
4637
+ now
4638
+ }, `${usageWindow.id}-meter`)])]
4639
+ }),
4640
+ report !== void 0 && report.fetchedAt > 0 ? /* @__PURE__ */ jsx("p", {
4641
+ style: notice,
4642
+ children: t("planUsageUpdated", { age: durationLabel(now - report.fetchedAt) })
4643
+ }) : null,
4644
+ error === void 0 ? null : /* @__PURE__ */ jsxs("p", {
4645
+ role: "alert",
4646
+ style: {
4647
+ ...notice,
4648
+ color: "var(--dsw-alias-state-error-primary)"
4649
+ },
4650
+ children: [
4651
+ t("planUsageError"),
4652
+ ": ",
4653
+ error
4654
+ ]
4655
+ })
4656
+ ]
4657
+ });
4658
+ }
4089
4659
  function ClaudeCodeSettings({ t }) {
4090
4660
  const [report, setReport] = useState();
4091
4661
  const [error, setError] = useState();
@@ -4290,6 +4860,10 @@ window.__ModuleLoader__.load({
4290
4860
  })
4291
4861
  ]
4292
4862
  }),
4863
+ /* @__PURE__ */ jsx(PlanUsageCard, {
4864
+ t,
4865
+ load: loadPlanUsage
4866
+ }),
4293
4867
  /* @__PURE__ */ jsxs("section", {
4294
4868
  style: settingsCard,
4295
4869
  children: [
@@ -4303,37 +4877,31 @@ window.__ModuleLoader__.load({
4303
4877
  globalSettings === void 0 ? /* @__PURE__ */ jsx("p", {
4304
4878
  style: notice,
4305
4879
  children: t("globalSettingsLoading")
4306
- }) : globalSettings.settings.map((setting) => /* @__PURE__ */ jsxs("div", {
4307
- style: diagnosticGrid,
4308
- children: [/* @__PURE__ */ jsx("span", {
4309
- style: diagnosticLabel,
4310
- children: setting.key === "outputStyle" ? t("outputStyle") : setting.key === "worktreeBranchPrefix" ? t("worktreeBranchPrefix") : setting.key === "maxProcesses" ? t("maxProcessesSetting") : setting.key === "idleTimeoutMinutes" ? t("idleTimeoutSetting") : setting.key
4311
- }), setting.kind === "select" ? /* @__PURE__ */ jsx(GlobalSettingSelect, {
4312
- setting,
4313
- disabled: globalSettingsBusy,
4314
- onChange: (nextValue) => {
4315
- requestGlobalSettings({ [setting.key]: nextValue });
4316
- }
4317
- }) : /* @__PURE__ */ jsx(GlobalSettingText, {
4318
- setting,
4319
- disabled: globalSettingsBusy,
4320
- onChange: (nextValue) => {
4321
- requestGlobalSettings({ [setting.key]: nextValue });
4322
- }
4323
- })]
4324
- }, setting.key)),
4325
- globalSettings?.settings.some((setting) => setting.effect === "new-session") === true ? /* @__PURE__ */ jsx("p", {
4326
- style: notice,
4327
- children: t("globalSettingsNewSession")
4328
- }) : null,
4329
- globalSettings?.settings.some((setting) => setting.effect === "next-worktree") === true ? /* @__PURE__ */ jsx("p", {
4330
- style: notice,
4331
- children: t("worktreeBranchPrefixEffect")
4332
- }) : null,
4333
- globalSettings?.settings.some((setting) => setting.key === "maxProcesses") === true ? /* @__PURE__ */ jsx("p", {
4334
- style: notice,
4335
- children: t("limitsSettingEffect")
4336
- }) : null,
4880
+ }) : globalSettings.settings.map((setting) => {
4881
+ const copy = SETTING_COPY[setting.key];
4882
+ return /* @__PURE__ */ jsxs("div", {
4883
+ style: diagnosticGrid,
4884
+ children: [/* @__PURE__ */ jsxs("span", {
4885
+ style: diagnosticLabel,
4886
+ children: [copy === void 0 ? setting.key : t(copy.label), copy === void 0 ? null : /* @__PURE__ */ jsx(SettingHint, {
4887
+ text: t(copy.hint),
4888
+ label: t("settingHint")
4889
+ })]
4890
+ }), setting.kind === "select" ? /* @__PURE__ */ jsx(GlobalSettingSelect, {
4891
+ setting,
4892
+ disabled: globalSettingsBusy,
4893
+ onChange: (nextValue) => {
4894
+ requestGlobalSettings({ [setting.key]: nextValue });
4895
+ }
4896
+ }) : /* @__PURE__ */ jsx(GlobalSettingText, {
4897
+ setting,
4898
+ disabled: globalSettingsBusy,
4899
+ onChange: (nextValue) => {
4900
+ requestGlobalSettings({ [setting.key]: nextValue });
4901
+ }
4902
+ })]
4903
+ }, setting.key);
4904
+ }),
4337
4905
  globalSettingsError === void 0 ? null : /* @__PURE__ */ jsxs("p", {
4338
4906
  role: "alert",
4339
4907
  style: {
@@ -4580,33 +5148,33 @@ window.__ModuleLoader__.load({
4580
5148
  if (commit !== void 0) this.commit = commit;
4581
5149
  }
4582
5150
  };
4583
- function record$3(value) {
5151
+ function record$4(value) {
4584
5152
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4585
5153
  }
4586
5154
  async function response$1(pending) {
4587
5155
  const result = await pending;
4588
5156
  const body = await result.json();
4589
5157
  if (!result.ok) {
4590
- const error = record$3(body);
5158
+ const error = record$4(body);
4591
5159
  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
5160
  }
4593
5161
  return body;
4594
5162
  }
4595
5163
  function preview(value) {
4596
- const input = record$3(value);
5164
+ const input = record$4(value);
4597
5165
  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
5166
  for (const file of input.files) {
4599
- const item = record$3(file);
5167
+ const item = record$4(file);
4600
5168
  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
5169
  }
4602
5170
  for (const commit of input.unpushedCommits) {
4603
- const item = record$3(commit);
5171
+ const item = record$4(commit);
4604
5172
  if (item === void 0 || typeof item.hash !== "string" || typeof item.subject !== "string") throw new Error("Invalid repository action commit.");
4605
5173
  }
4606
5174
  return input;
4607
5175
  }
4608
5176
  function result(value) {
4609
- const input = record$3(value);
5177
+ const input = record$4(value);
4610
5178
  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
5179
  return input;
4612
5180
  }
@@ -4622,7 +5190,7 @@ window.__ModuleLoader__.load({
4622
5190
  })));
4623
5191
  }
4624
5192
  async function generateCommitMessage(sessionId, fingerprint, signal) {
4625
- const value = record$3(await response$1(fetch(endpoint("/message", sessionId), {
5193
+ const value = record$4(await response$1(fetch(endpoint("/message", sessionId), {
4626
5194
  method: "POST",
4627
5195
  credentials: "same-origin",
4628
5196
  headers: {
@@ -4655,16 +5223,16 @@ window.__ModuleLoader__.load({
4655
5223
  "saving-worktree",
4656
5224
  "switching-branch"
4657
5225
  ]);
4658
- function record$2(value) {
5226
+ function record$3(value) {
4659
5227
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4660
5228
  }
4661
5229
  function setupResult(value) {
4662
- const item = record$2(value);
5230
+ const item = record$3(value);
4663
5231
  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
5232
  return item;
4665
5233
  }
4666
5234
  function parseRepositorySetupEvent(line, onProgress) {
4667
- const event = record$2(JSON.parse(line));
5235
+ const event = record$3(JSON.parse(line));
4668
5236
  if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
4669
5237
  onProgress(event.stage);
4670
5238
  return;
@@ -4685,12 +5253,15 @@ window.__ModuleLoader__.load({
4685
5253
  }
4686
5254
  return body;
4687
5255
  }
5256
+ /** A wedged host must surface as an error; the hero has no other way out of its loading state. */
5257
+ const BRANCH_LOAD_TIMEOUT_MS = 15e3;
4688
5258
  function loadRepositoryBranches(cwd, signal) {
5259
+ const timeout = AbortSignal.timeout(BRANCH_LOAD_TIMEOUT_MS);
4689
5260
  return response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches?cwd=${encodeURIComponent(cwd)}`, {
4690
5261
  method: "GET",
4691
5262
  credentials: "same-origin",
4692
5263
  headers: { accept: "application/json" },
4693
- ...signal === void 0 ? {} : { signal }
5264
+ signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout])
4694
5265
  }));
4695
5266
  }
4696
5267
  async function prepareRepository(cwd, branch, worktree, branchName, onProgress = () => {}) {
@@ -4766,6 +5337,21 @@ window.__ModuleLoader__.load({
4766
5337
  if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
4767
5338
  return body;
4768
5339
  }
5340
+ /** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
5341
+ async function loadRepositoryFileLines(cwd, path, from, to, signal) {
5342
+ const query = `cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(path)}&from=${from}&to=${to}`;
5343
+ const body = await response(fetch(`${CLAUDE_REPOSITORY_FILE_PATH}?${query}`, {
5344
+ method: "GET",
5345
+ credentials: "same-origin",
5346
+ headers: { accept: "application/json" },
5347
+ ...signal === void 0 ? {} : { signal }
5348
+ }));
5349
+ if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
5350
+ return {
5351
+ lines: body.lines.map(String),
5352
+ total: body.total
5353
+ };
5354
+ }
4769
5355
  async function loadRepositoryStatusFor(cwd, signal) {
4770
5356
  const body = await response(fetch(`${CLAUDE_REPOSITORY_STATUS_PATH}?cwd=${encodeURIComponent(cwd)}`, {
4771
5357
  method: "GET",
@@ -4778,7 +5364,7 @@ window.__ModuleLoader__.load({
4778
5364
  }
4779
5365
  //#endregion
4780
5366
  //#region src/client/pr-feedback-api.ts
4781
- function record$1(value) {
5367
+ function record$2(value) {
4782
5368
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4783
5369
  }
4784
5370
  async function loadJson(path, sessionId, pullNumber, signal) {
@@ -4788,7 +5374,7 @@ window.__ModuleLoader__.load({
4788
5374
  headers: { accept: "application/json" },
4789
5375
  ...signal === void 0 ? {} : { signal }
4790
5376
  });
4791
- const body = record$1(await response.json());
5377
+ const body = record$2(await response.json());
4792
5378
  if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
4793
5379
  if (body === void 0) throw new Error("Invalid pull request feedback response.");
4794
5380
  return body;
@@ -4798,7 +5384,7 @@ window.__ModuleLoader__.load({
4798
5384
  if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
4799
5385
  const comments = [];
4800
5386
  for (const item of body.comments) {
4801
- const input = record$1(item);
5387
+ const input = record$2(item);
4802
5388
  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
5389
  comments.push(input);
4804
5390
  }
@@ -4809,7 +5395,7 @@ window.__ModuleLoader__.load({
4809
5395
  if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
4810
5396
  const checks = [];
4811
5397
  for (const item of body.checks) {
4812
- const input = record$1(item);
5398
+ const input = record$2(item);
4813
5399
  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
5400
  checks.push(input);
4815
5401
  }
@@ -4882,29 +5468,60 @@ window.__ModuleLoader__.load({
4882
5468
  }
4883
5469
  //#endregion
4884
5470
  //#region src/client/ClaudeRepositoryStatus.tsx
4885
- const RATE_LIMIT_TITLE_PREFIX = "Claude rate limit";
4886
- const RATE_LIMIT_BLOCKING_TITLE = "Claude rate limit is blocking requests";
4887
- /** Whether the newest quota update in the activity feed reports a blocked state. */
4888
- function rateLimitBlocked(activities) {
4889
- for (let index = activities.length - 1; index >= 0; index -= 1) {
4890
- const title = activities[index]?.title;
4891
- if (title?.startsWith(RATE_LIMIT_TITLE_PREFIX) === true) return title === RATE_LIMIT_BLOCKING_TITLE;
4892
- }
4893
- return false;
4894
- }
4895
- function StatusItem({ label, tone = "neutral" }) {
5471
+ /** Icon-only status: the tone carries the state, the full label lives in the tooltip and accessible name. */
5472
+ function StatusGlyph({ label, tone, children }) {
4896
5473
  const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : {};
4897
- return /* @__PURE__ */ jsxs("span", {
4898
- style: {
4899
- ...repositoryItem,
4900
- ...toneStyle
4901
- },
4902
- children: [/* @__PURE__ */ jsx("span", {
4903
- style: repositoryItemDot,
4904
- "aria-hidden": "true"
4905
- }), /* @__PURE__ */ jsx("span", {
4906
- style: repositoryItemLabel,
4907
- children: label
5474
+ return /* @__PURE__ */ jsx(Tooltip, {
5475
+ label,
5476
+ side: "top",
5477
+ delayMs: 250,
5478
+ children: /* @__PURE__ */ jsx("span", {
5479
+ role: "img",
5480
+ "aria-label": label,
5481
+ style: {
5482
+ ...repositoryGlyph,
5483
+ ...toneStyle
5484
+ },
5485
+ children
5486
+ })
5487
+ });
5488
+ }
5489
+ function ChecksGlyph({ state }) {
5490
+ return /* @__PURE__ */ jsxs("svg", {
5491
+ width: "14",
5492
+ height: "14",
5493
+ viewBox: "0 0 14 14",
5494
+ fill: "none",
5495
+ stroke: "currentColor",
5496
+ strokeWidth: "1.5",
5497
+ strokeLinecap: "round",
5498
+ strokeLinejoin: "round",
5499
+ "aria-hidden": "true",
5500
+ children: [/* @__PURE__ */ jsx("circle", {
5501
+ cx: "7",
5502
+ cy: "7",
5503
+ r: "5.5"
5504
+ }), 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", {
5505
+ d: "M4.6 7h.01M7 7h.01M9.4 7h.01",
5506
+ strokeWidth: "2"
5507
+ })]
5508
+ });
5509
+ }
5510
+ function ReviewGlyph() {
5511
+ return /* @__PURE__ */ jsxs("svg", {
5512
+ width: "14",
5513
+ height: "14",
5514
+ viewBox: "0 0 14 14",
5515
+ fill: "none",
5516
+ stroke: "currentColor",
5517
+ strokeWidth: "1.5",
5518
+ strokeLinecap: "round",
5519
+ strokeLinejoin: "round",
5520
+ "aria-hidden": "true",
5521
+ 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", {
5522
+ cx: "7",
5523
+ cy: "7",
5524
+ r: "1.8"
4908
5525
  })]
4909
5526
  });
4910
5527
  }
@@ -5233,9 +5850,10 @@ window.__ModuleLoader__.load({
5233
5850
  "aria-expanded": open,
5234
5851
  "aria-label": t("repositoryChecksOpen"),
5235
5852
  onClick: toggle,
5236
- children: /* @__PURE__ */ jsx(StatusItem, {
5853
+ children: /* @__PURE__ */ jsx(StatusGlyph, {
5237
5854
  label: t("repositoryChecks_failing"),
5238
- tone: "error"
5855
+ tone: "error",
5856
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: "failing" })
5239
5857
  })
5240
5858
  }), open ? /* @__PURE__ */ jsxs("span", {
5241
5859
  role: "dialog",
@@ -5745,25 +6363,17 @@ window.__ModuleLoader__.load({
5745
6363
  const aheadCount = repository.ahead ?? 0;
5746
6364
  const pushable = repository.remote !== void 0 && repository.detached !== true && (aheadCount > 0 || repository.upstream === false);
5747
6365
  const hasDiff = repository.diff !== void 0 && (repository.diff.additions > 0 || repository.diff.deletions > 0);
5748
- const rateLimited = rateLimitBlocked(projection.activities);
5749
6366
  if (repository.status !== "ready") return /* @__PURE__ */ jsx("div", {
5750
6367
  style: repositoryBarFrame,
5751
6368
  children: /* @__PURE__ */ jsxs("div", {
5752
6369
  style: repositoryBar,
5753
- children: [
5754
- /* @__PURE__ */ jsx("span", {
5755
- style: repositoryPrIcon,
5756
- children: /* @__PURE__ */ jsx(PullRequestIcon, {})
5757
- }),
5758
- /* @__PURE__ */ jsx("span", {
5759
- style: repositoryPrimary,
5760
- children: repository.status === "not-repository" ? t("repositoryNotGit") : t("repositoryUnavailable")
5761
- }),
5762
- rateLimited ? /* @__PURE__ */ jsx(StatusItem, {
5763
- label: t("repositoryRateLimited"),
5764
- tone: "warning"
5765
- }) : null
5766
- ]
6370
+ children: [/* @__PURE__ */ jsx("span", {
6371
+ style: repositoryPrIcon,
6372
+ children: /* @__PURE__ */ jsx(PullRequestIcon, {})
6373
+ }), /* @__PURE__ */ jsx("span", {
6374
+ style: repositoryPrimary,
6375
+ children: repository.status === "not-repository" ? t("repositoryNotGit") : t("repositoryUnavailable")
6376
+ })]
5767
6377
  })
5768
6378
  });
5769
6379
  return /* @__PURE__ */ jsx("div", {
@@ -5810,81 +6420,76 @@ window.__ModuleLoader__.load({
5810
6420
  }) : null,
5811
6421
  /* @__PURE__ */ jsxs("span", {
5812
6422
  style: repositoryStatusItems,
5813
- children: [
5814
- rateLimited ? /* @__PURE__ */ jsx(StatusItem, {
5815
- label: t("repositoryRateLimited"),
5816
- tone: "warning"
5817
- }) : null,
5818
- hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
5819
- type: "button",
5820
- style: {
5821
- ...diffTrigger,
5822
- ...merged ? diffTriggerMuted : {}
5823
- },
5824
- onClick: openDiff,
5825
- "aria-label": t("diffOpen"),
5826
- children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
5827
- style: merged ? diffAddMuted : diffAdd,
5828
- children: ["+", repository.diff.additions]
5829
- }), /* @__PURE__ */ jsxs("span", {
5830
- style: merged ? diffDeleteMuted : diffDelete,
5831
- children: ["", repository.diff.deletions]
5832
- })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
5833
- style: merged ? diffAheadMuted : diffAhead,
5834
- children: ["", aheadCount > 0 ? aheadCount : ""]
5835
- }) : null]
5836
- }) : null,
5837
- pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
5838
- style: repositoryMergedStatus,
5839
- children: [
5840
- /* @__PURE__ */ jsx("span", {
5841
- style: repositoryMergedDot,
5842
- "aria-hidden": "true"
5843
- }),
5844
- t("repositoryState_merged"),
5845
- mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
5846
- style: repositoryMergedAge,
5847
- children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
5848
- })
5849
- ]
5850
- }), /* @__PURE__ */ jsx(CleanupControl, {
5851
- repository,
5852
- t,
5853
- ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
5854
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
5855
- pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
5856
- sessionId,
5857
- pullNumber: pullRequest.number,
5858
- t,
5859
- ...submitPrompt === void 0 ? {} : { submitPrompt }
5860
- }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusItem, {
5861
- label: t(`repositoryChecks_${pullRequest.checks}`),
5862
- tone: pullRequest.checks === "passing" ? "success" : "warning"
5863
- }),
5864
- pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusItem, {
5865
- label: t(`repositoryReview_${pullRequest.review}`),
5866
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
5867
- }),
5868
- /* @__PURE__ */ jsx(AutoFixControl, {
5869
- sessionId,
5870
- repository,
5871
- running,
5872
- t,
5873
- ...submitPrompt === void 0 ? {} : { submitPrompt }
5874
- }),
5875
- /* @__PURE__ */ jsx(UpdateBranchControl, {
5876
- sessionId,
5877
- repository,
5878
- t,
5879
- ...submitPrompt === void 0 ? {} : { submitPrompt }
6423
+ children: [hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
6424
+ type: "button",
6425
+ style: {
6426
+ ...diffTrigger,
6427
+ ...merged ? diffTriggerMuted : {}
6428
+ },
6429
+ onClick: openDiff,
6430
+ "aria-label": t("diffOpen"),
6431
+ children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
6432
+ style: merged ? diffAddMuted : diffAdd,
6433
+ children: ["+", repository.diff.additions]
6434
+ }), /* @__PURE__ */ jsxs("span", {
6435
+ style: merged ? diffDeleteMuted : diffDelete,
6436
+ children: ["−", repository.diff.deletions]
6437
+ })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
6438
+ style: merged ? diffAheadMuted : diffAhead,
6439
+ children: ["↑", aheadCount > 0 ? aheadCount : ""]
6440
+ }) : null]
6441
+ }) : null, pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
6442
+ style: repositoryMergedStatus,
6443
+ children: [
6444
+ /* @__PURE__ */ jsx("span", {
6445
+ style: repositoryMergedDot,
6446
+ "aria-hidden": "true"
5880
6447
  }),
5881
- /* @__PURE__ */ jsx(MergePullRequestControl, {
5882
- sessionId,
5883
- repository,
5884
- t
6448
+ t("repositoryState_merged"),
6449
+ mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
6450
+ style: repositoryMergedAge,
6451
+ children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
5885
6452
  })
5886
- ] })
5887
- ]
6453
+ ]
6454
+ }), /* @__PURE__ */ jsx(CleanupControl, {
6455
+ repository,
6456
+ t,
6457
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
6458
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
6459
+ pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
6460
+ sessionId,
6461
+ pullNumber: pullRequest.number,
6462
+ t,
6463
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
6464
+ }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
6465
+ label: t(`repositoryChecks_${pullRequest.checks}`),
6466
+ tone: pullRequest.checks === "passing" ? "success" : "warning",
6467
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
6468
+ }),
6469
+ pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
6470
+ label: t(`repositoryReview_${pullRequest.review}`),
6471
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
6472
+ children: /* @__PURE__ */ jsx(ReviewGlyph, {})
6473
+ }),
6474
+ /* @__PURE__ */ jsx(AutoFixControl, {
6475
+ sessionId,
6476
+ repository,
6477
+ running,
6478
+ t,
6479
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
6480
+ }),
6481
+ /* @__PURE__ */ jsx(UpdateBranchControl, {
6482
+ sessionId,
6483
+ repository,
6484
+ t,
6485
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
6486
+ }),
6487
+ /* @__PURE__ */ jsx(MergePullRequestControl, {
6488
+ sessionId,
6489
+ repository,
6490
+ t
6491
+ })
6492
+ ] })]
5888
6493
  })
5889
6494
  ]
5890
6495
  })
@@ -5892,7 +6497,7 @@ window.__ModuleLoader__.load({
5892
6497
  }
5893
6498
  //#endregion
5894
6499
  //#region src/client/review-comment-api.ts
5895
- function record(value) {
6500
+ function record$1(value) {
5896
6501
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5897
6502
  }
5898
6503
  async function post(path, sessionId, body) {
@@ -5907,13 +6512,13 @@ window.__ModuleLoader__.load({
5907
6512
  });
5908
6513
  const value = await response.json();
5909
6514
  if (!response.ok) {
5910
- const error = record(value);
6515
+ const error = record$1(value);
5911
6516
  throw new Error(typeof error?.message === "string" ? error.message : "Review comment request failed.");
5912
6517
  }
5913
6518
  return value;
5914
6519
  }
5915
6520
  async function addReviewComment(sessionId, comment) {
5916
- const created = record(record(await post("", sessionId, comment))?.comment);
6521
+ const created = record$1(record$1(await post("", sessionId, comment))?.comment);
5917
6522
  if (created === void 0 || typeof created.id !== "string") throw new Error("Invalid review comment response.");
5918
6523
  return created;
5919
6524
  }
@@ -5926,7 +6531,15 @@ window.__ModuleLoader__.load({
5926
6531
  //#endregion
5927
6532
  //#region src/client/ClaudeReviewComments.tsx
5928
6533
  function reviewCommentChipLabel(comment) {
5929
- return `${comment.path.split("/").at(-1) ?? comment.path}:${comment.line}`;
6534
+ return `${comment.path.split("/").at(-1) ?? comment.path}:${comment.startLine === void 0 ? comment.line : `${comment.startLine}-${comment.line}`}`;
6535
+ }
6536
+ /** "Line 12" / "Lines 10–12", with the old-side variant when the comment sits on removed code. */
6537
+ function commentLineLabel(comment, t) {
6538
+ if (comment.startLine !== void 0) return t(comment.side === "old" ? "reviewCommentOldRange" : "reviewCommentNewRange", {
6539
+ start: comment.startLine,
6540
+ end: comment.line
6541
+ });
6542
+ return t(comment.side === "old" ? "reviewCommentOldSide" : "reviewCommentNewSide", { line: comment.line });
5930
6543
  }
5931
6544
  function CommentIcon() {
5932
6545
  return /* @__PURE__ */ jsx("svg", {
@@ -5992,7 +6605,7 @@ window.__ModuleLoader__.load({
5992
6605
  children: [
5993
6606
  comment.path,
5994
6607
  " · ",
5995
- comment.side === "old" ? t("reviewCommentOldSide", { line: comment.line }) : t("reviewCommentNewSide", { line: comment.line })
6608
+ commentLineLabel(comment, t)
5996
6609
  ]
5997
6610
  }), /* @__PURE__ */ jsx("p", {
5998
6611
  style: reviewCommentHoverText,
@@ -6101,17 +6714,41 @@ window.__ModuleLoader__.load({
6101
6714
  let oldLine = 0;
6102
6715
  let newLine = 0;
6103
6716
  let previousOldEnd;
6717
+ let previousNewEnd = 0;
6718
+ let expandable = false;
6104
6719
  for (const line of lines) {
6105
6720
  const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/u.exec(line);
6106
6721
  if (hunk !== null) {
6107
6722
  const nextOld = Number(hunk[1]);
6108
- if (previousOldEnd !== void 0 && nextOld > previousOldEnd) numbered.push({
6109
- line: `${nextOld - previousOldEnd} unmodified lines`,
6110
- kind: "collapsed"
6723
+ const nextNew = Number(hunk[3]);
6724
+ const oldCount = Number(hunk[2] ?? 1);
6725
+ const newCount = Number(hunk[4] ?? 1);
6726
+ if (previousOldEnd === void 0) {
6727
+ expandable = oldCount > 0 && newCount > 0;
6728
+ if (expandable && nextOld > 1) numbered.push({
6729
+ line: "",
6730
+ kind: "collapsed",
6731
+ gap: {
6732
+ oldStart: 1,
6733
+ newStart: 1,
6734
+ count: nextOld - 1,
6735
+ position: "top"
6736
+ }
6737
+ });
6738
+ } else if (nextOld > previousOldEnd) numbered.push({
6739
+ line: "",
6740
+ kind: "collapsed",
6741
+ gap: {
6742
+ oldStart: previousOldEnd,
6743
+ newStart: previousNewEnd,
6744
+ count: nextOld - previousOldEnd,
6745
+ position: "middle"
6746
+ }
6111
6747
  });
6112
6748
  oldLine = nextOld;
6113
- newLine = Number(hunk[3]);
6114
- previousOldEnd = nextOld + Number(hunk[2] ?? 1);
6749
+ newLine = nextNew;
6750
+ previousOldEnd = nextOld + oldCount;
6751
+ previousNewEnd = nextNew + newCount;
6115
6752
  numbered.push({
6116
6753
  line,
6117
6754
  kind: "hunk"
@@ -6143,8 +6780,76 @@ window.__ModuleLoader__.load({
6143
6780
  newLine += 1;
6144
6781
  }
6145
6782
  }
6783
+ if (expandable && previousOldEnd !== void 0) numbered.push({
6784
+ line: "",
6785
+ kind: "collapsed",
6786
+ gap: {
6787
+ oldStart: previousOldEnd,
6788
+ newStart: previousNewEnd,
6789
+ position: "bottom"
6790
+ }
6791
+ });
6146
6792
  return numbered;
6147
6793
  }
6794
+ /**
6795
+ * Splice revealed working-tree lines (keyed by new-side line number) into the
6796
+ * collapsed gaps. Expansion only ever grows a gap's edges, so each gap is a
6797
+ * top run + remaining gap + bottom run; `total` (file line count) turns the
6798
+ * open-ended tail gap into a bounded one.
6799
+ */
6800
+ function expandDiffRows(rows, revealed, total) {
6801
+ const out = [];
6802
+ for (const row of rows) {
6803
+ const gap = row.gap;
6804
+ if (row.kind !== "collapsed" || gap === void 0) {
6805
+ out.push(row);
6806
+ continue;
6807
+ }
6808
+ const count = gap.count ?? (total === void 0 ? void 0 : Math.max(0, total - gap.newStart + 1));
6809
+ const context = (offset) => ({
6810
+ line: ` ${revealed.get(gap.newStart + offset) ?? ""}`,
6811
+ kind: "context",
6812
+ oldLine: gap.oldStart + offset,
6813
+ newLine: gap.newStart + offset
6814
+ });
6815
+ let top = 0;
6816
+ while ((count === void 0 || top < count) && revealed.has(gap.newStart + top)) top += 1;
6817
+ let bottom = 0;
6818
+ if (count !== void 0) while (bottom < count - top && revealed.has(gap.newStart + count - 1 - bottom)) bottom += 1;
6819
+ for (let offset = 0; offset < top; offset += 1) out.push(context(offset));
6820
+ const remaining = count === void 0 ? void 0 : count - top - bottom;
6821
+ if (remaining === void 0 || remaining > 0) out.push({
6822
+ ...row,
6823
+ gap: {
6824
+ ...gap,
6825
+ oldStart: gap.oldStart + top,
6826
+ newStart: gap.newStart + top,
6827
+ ...remaining === void 0 ? {} : { count: remaining }
6828
+ }
6829
+ });
6830
+ if (count !== void 0) for (let offset = count - bottom; offset < count; offset += 1) out.push(context(offset));
6831
+ }
6832
+ return out;
6833
+ }
6834
+ /**
6835
+ * Anchor for a drag from row `from` to row `to`: every commentable row in
6836
+ * between on the same side as the first row, collapsed to its first/last line.
6837
+ */
6838
+ function rangeCommentAnchor(anchors, from, to) {
6839
+ const side = anchors[from]?.side;
6840
+ if (side === void 0) return void 0;
6841
+ const lines = anchors.slice(Math.min(from, to), Math.max(from, to) + 1).flatMap((anchor) => anchor?.side === side ? [anchor.line] : []);
6842
+ const line = Math.max(...lines);
6843
+ const startLine = Math.min(...lines);
6844
+ return startLine < line ? {
6845
+ line,
6846
+ side,
6847
+ startLine
6848
+ } : {
6849
+ line,
6850
+ side
6851
+ };
6852
+ }
6148
6853
  /** Which working-tree line a comment on this rendered diff row refers to. */
6149
6854
  function commentAnchorForLine(entry) {
6150
6855
  if (entry.kind === "add" || entry.kind === "context") return entry.newLine === void 0 ? void 0 : {
@@ -6156,22 +6861,32 @@ window.__ModuleLoader__.load({
6156
6861
  side: "old"
6157
6862
  };
6158
6863
  }
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;
6864
+ function DiffLine({ entry, addLabel, selected, onComment, onDragStart, onDragEnter }) {
6865
+ const style = entry.kind === "add" ? diffLineAdd : entry.kind === "delete" ? diffLineDelete : entry.kind === "hunk" ? diffLineHunk : diffLineContext;
6161
6866
  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
6867
  return /* @__PURE__ */ jsxs("div", {
6163
6868
  className: diffLineRowClass,
6164
6869
  style: {
6165
6870
  ...diffLine,
6166
- ...style
6871
+ ...style,
6872
+ ...selected ? diffLineSelected : {}
6167
6873
  },
6874
+ onPointerEnter: onDragEnter,
6168
6875
  children: [
6169
6876
  /* @__PURE__ */ jsx("button", {
6170
6877
  type: "button",
6171
6878
  className: diffCommentButtonClass,
6172
6879
  disabled: onComment === void 0,
6173
6880
  "aria-label": addLabel,
6174
- onClick: onComment,
6881
+ onPointerDown: (event) => {
6882
+ if (event.button !== 0 || onDragStart === void 0) return;
6883
+ event.preventDefault();
6884
+ if (event.currentTarget.hasPointerCapture?.(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
6885
+ onDragStart();
6886
+ },
6887
+ onClick: (event) => {
6888
+ if (event.detail === 0) onComment?.();
6889
+ },
6175
6890
  children: "+"
6176
6891
  }),
6177
6892
  /* @__PURE__ */ jsx("span", {
@@ -6180,14 +6895,105 @@ window.__ModuleLoader__.load({
6180
6895
  }),
6181
6896
  /* @__PURE__ */ jsx("span", {
6182
6897
  style: diffLineMarker,
6183
- children: entry.kind === "add" ? "+" : entry.kind === "delete" ? "−" : entry.kind === "collapsed" ? "⌄" : " "
6898
+ children: entry.kind === "add" ? "+" : entry.kind === "delete" ? "−" : " "
6184
6899
  }),
6185
- /* @__PURE__ */ jsx("span", { children: entry.kind === "collapsed" ? entry.line : entry.line.slice(entry.kind === "hunk" ? 0 : 1) })
6900
+ /* @__PURE__ */ jsx("span", {
6901
+ style: diffLineText,
6902
+ children: entry.line.slice(entry.kind === "hunk" ? 0 : 1)
6903
+ })
6186
6904
  ]
6187
6905
  });
6188
6906
  }
6189
- function DiffFileSection({ file, initiallyOpen, t, comments, ghComments, editorAnchor, editorNode, onOpenEditor, onRemoveComment }) {
6907
+ function ChevronGlyph({ direction }) {
6908
+ return /* @__PURE__ */ jsx("svg", {
6909
+ width: "14",
6910
+ height: "14",
6911
+ viewBox: "0 0 14 14",
6912
+ fill: "none",
6913
+ stroke: "currentColor",
6914
+ strokeWidth: "1.5",
6915
+ strokeLinecap: "round",
6916
+ strokeLinejoin: "round",
6917
+ "aria-hidden": "true",
6918
+ children: /* @__PURE__ */ jsx("path", { d: direction === "up" ? "M3 8.5l4-4 4 4" : "M3 5.5l4 4 4-4" })
6919
+ });
6920
+ }
6921
+ /** "N unmodified lines" separator with GitHub-style expanders: ↑ above the first hunk, ↑↓ between hunks, ↓ after the last. */
6922
+ function DiffGapRow({ gap, t, busy, onExpand }) {
6923
+ const button = (direction) => /* @__PURE__ */ jsx("button", {
6924
+ type: "button",
6925
+ style: diffGapButton,
6926
+ disabled: busy || onExpand === void 0,
6927
+ "aria-label": t(direction === "up" ? "diffExpandUp" : "diffExpandDown"),
6928
+ title: t(direction === "up" ? "diffExpandUp" : "diffExpandDown"),
6929
+ onClick: () => onExpand?.(gap, direction),
6930
+ children: /* @__PURE__ */ jsx(ChevronGlyph, { direction })
6931
+ });
6932
+ return /* @__PURE__ */ jsxs("div", {
6933
+ style: {
6934
+ ...diffLine,
6935
+ ...diffLineHunk
6936
+ },
6937
+ children: [/* @__PURE__ */ jsxs("span", {
6938
+ style: diffGapControls,
6939
+ children: [gap.position === "bottom" ? null : button("up"), gap.position === "top" ? null : button("down")]
6940
+ }), /* @__PURE__ */ jsx("span", {
6941
+ style: diffLineText,
6942
+ children: gap.count === void 0 ? t("diffUnmodifiedTail") : t("diffUnmodifiedLines", { count: gap.count })
6943
+ })]
6944
+ });
6945
+ }
6946
+ function DiffFileSection({ file, root, initiallyOpen, t, comments, ghComments, editorAnchor, editorNode, onOpenEditor, onRemoveComment }) {
6190
6947
  const [open, setOpen] = useState(initiallyOpen);
6948
+ const [revealed, setRevealed] = useState(() => /* @__PURE__ */ new Map());
6949
+ const [total, setTotal] = useState();
6950
+ const [expanding, setExpanding] = useState(false);
6951
+ const [drag, setDrag] = useState();
6952
+ const rows = useMemo(() => expandDiffRows(numberDiffLines(file.lines), revealed, total), [
6953
+ file.lines,
6954
+ revealed,
6955
+ total
6956
+ ]);
6957
+ const anchors = useMemo(() => rows.map(commentAnchorForLine), [rows]);
6958
+ const slash = file.path.lastIndexOf("/");
6959
+ const directory = slash < 0 ? "" : file.path.slice(0, slash + 1);
6960
+ const name = file.path.slice(slash + 1);
6961
+ const expand = (gap, direction) => {
6962
+ if (root === void 0 || expanding) return;
6963
+ const span = gap.count ?? 20;
6964
+ const size = Math.min(20, span);
6965
+ const from = direction === "down" ? gap.newStart : gap.newStart + span - size;
6966
+ setExpanding(true);
6967
+ loadRepositoryFileLines(root, file.path, from, from + size - 1).then((result) => {
6968
+ setRevealed((previous) => {
6969
+ const next = new Map(previous);
6970
+ result.lines.forEach((text, offset) => next.set(from + offset, text));
6971
+ return next;
6972
+ });
6973
+ setTotal(result.total);
6974
+ }, () => void 0).finally(() => setExpanding(false));
6975
+ };
6976
+ useEffect(() => {
6977
+ if (drag === void 0) return;
6978
+ const finish = () => {
6979
+ setDrag(void 0);
6980
+ const anchor = rangeCommentAnchor(anchors, drag.start, drag.end);
6981
+ if (anchor !== void 0) onOpenEditor(anchor);
6982
+ };
6983
+ window.addEventListener("pointerup", finish);
6984
+ window.addEventListener("pointercancel", finish);
6985
+ return () => {
6986
+ window.removeEventListener("pointerup", finish);
6987
+ window.removeEventListener("pointercancel", finish);
6988
+ };
6989
+ }, [
6990
+ anchors,
6991
+ drag,
6992
+ onOpenEditor
6993
+ ]);
6994
+ const dragLow = drag === void 0 ? void 0 : Math.min(drag.start, drag.end);
6995
+ const dragHigh = drag === void 0 ? void 0 : Math.max(drag.start, drag.end);
6996
+ const dragSide = drag === void 0 ? void 0 : anchors[drag.start]?.side;
6191
6997
  return /* @__PURE__ */ jsxs("section", {
6192
6998
  style: diffFile,
6193
6999
  children: [/* @__PURE__ */ jsxs("button", {
@@ -6203,9 +7009,16 @@ window.__ModuleLoader__.load({
6203
7009
  },
6204
7010
  children: "›"
6205
7011
  }),
6206
- /* @__PURE__ */ jsx("span", {
7012
+ /* @__PURE__ */ jsxs("span", {
6207
7013
  style: diffFilePath,
6208
- children: file.path
7014
+ title: file.path,
7015
+ children: [directory === "" ? null : /* @__PURE__ */ jsx("span", {
7016
+ style: diffFileDir,
7017
+ children: `\u202A${directory}\u202C`
7018
+ }), /* @__PURE__ */ jsx("span", {
7019
+ style: diffFileName,
7020
+ children: name
7021
+ })]
6209
7022
  }),
6210
7023
  /* @__PURE__ */ jsxs("span", {
6211
7024
  style: diffFileStats,
@@ -6220,22 +7033,39 @@ window.__ModuleLoader__.load({
6220
7033
  ]
6221
7034
  }), open ? /* @__PURE__ */ jsx("div", {
6222
7035
  style: diffCode,
6223
- children: numberDiffLines(file.lines).map((entry, index) => {
6224
- const anchor = commentAnchorForLine(entry);
7036
+ children: rows.map((entry, index) => {
7037
+ if (entry.kind === "collapsed" && entry.gap !== void 0) return /* @__PURE__ */ jsx(DiffGapRow, {
7038
+ gap: entry.gap,
7039
+ t,
7040
+ busy: expanding,
7041
+ onExpand: root === void 0 ? void 0 : expand
7042
+ }, `gap:${entry.gap.newStart}`);
7043
+ const anchor = anchors[index];
6225
7044
  const lineComments = anchor === void 0 ? [] : comments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
6226
7045
  const ghLineComments = anchor === void 0 ? [] : ghComments.filter((comment) => comment.line === anchor.line && comment.side === anchor.side);
6227
7046
  const editorOpen = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.line === anchor.line && editorAnchor.side === anchor.side;
7047
+ const inDrag = dragLow !== void 0 && dragHigh !== void 0 && index >= dragLow && index <= dragHigh && anchor?.side === dragSide;
7048
+ const inEditorRange = anchor !== void 0 && editorAnchor !== void 0 && editorAnchor.side === anchor.side && anchor.line <= editorAnchor.line && anchor.line >= (editorAnchor.startLine ?? editorAnchor.line);
6228
7049
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6229
7050
  /* @__PURE__ */ jsx(DiffLine, {
6230
7051
  entry,
6231
7052
  addLabel: t("reviewCommentAdd"),
6232
- onComment: anchor === void 0 ? void 0 : () => onOpenEditor(anchor)
7053
+ selected: inDrag || inEditorRange,
7054
+ onComment: anchor === void 0 ? void 0 : () => onOpenEditor(anchor),
7055
+ onDragStart: anchor === void 0 ? void 0 : () => setDrag({
7056
+ start: index,
7057
+ end: index
7058
+ }),
7059
+ onDragEnter: drag === void 0 ? void 0 : () => setDrag((current) => current === void 0 ? current : {
7060
+ ...current,
7061
+ end: index
7062
+ })
6233
7063
  }),
6234
7064
  lineComments.map((comment) => /* @__PURE__ */ jsxs("div", {
6235
7065
  style: diffCommentBlock,
6236
7066
  children: [/* @__PURE__ */ jsxs("div", {
6237
7067
  style: diffCommentCardMeta,
6238
- children: [/* @__PURE__ */ jsx("span", { children: comment.side === "old" ? t("reviewCommentOldSide", { line: comment.line }) : t("reviewCommentNewSide", { line: comment.line }) }), /* @__PURE__ */ jsx("button", {
7068
+ children: [/* @__PURE__ */ jsx("span", { children: commentLineLabel(comment, t) }), /* @__PURE__ */ jsx("button", {
6239
7069
  type: "button",
6240
7070
  style: reviewCommentChipRemove,
6241
7071
  "aria-label": t("reviewCommentRemove"),
@@ -6478,6 +7308,7 @@ window.__ModuleLoader__.load({
6478
7308
  const created = await addReviewComment(sessionId, {
6479
7309
  path: commentEditor.path,
6480
7310
  line: commentEditor.line,
7311
+ ...commentEditor.startLine === void 0 ? {} : { startLine: commentEditor.startLine },
6481
7312
  side: commentEditor.side,
6482
7313
  text: commentDraft.trim()
6483
7314
  });
@@ -6564,6 +7395,10 @@ window.__ModuleLoader__.load({
6564
7395
  const commentEditorNode = commentEditor === void 0 ? null : /* @__PURE__ */ jsxs("div", {
6565
7396
  style: diffCommentBlock,
6566
7397
  children: [
7398
+ /* @__PURE__ */ jsx("div", {
7399
+ style: diffCommentRange,
7400
+ children: commentLineLabel(commentEditor, t)
7401
+ }),
6567
7402
  /* @__PURE__ */ jsx("textarea", {
6568
7403
  className: diffCommentTextareaClass,
6569
7404
  style: diffCommentTextarea,
@@ -6601,12 +7436,14 @@ window.__ModuleLoader__.load({
6601
7436
  /* @__PURE__ */ jsxs("style", {
6602
7437
  "data-dsh-claude-repository-modal-styles": true,
6603
7438
  children: [
7439
+ detailsCardCss,
6604
7440
  diffModalCss,
6605
7441
  panelIconButtonCss,
6606
7442
  diffCommentCss
6607
7443
  ]
6608
7444
  }),
6609
7445
  /* @__PURE__ */ jsxs("div", {
7446
+ className: detailsCardClass,
6610
7447
  style: {
6611
7448
  ...diffPanel,
6612
7449
  ...maximized ? diffPanelMaximized : {}
@@ -6728,6 +7565,7 @@ window.__ModuleLoader__.load({
6728
7565
  children: t("diffEmpty")
6729
7566
  }) : files.map((file, index) => /* @__PURE__ */ jsx(DiffFileSection, {
6730
7567
  file,
7568
+ root: repository.root,
6731
7569
  initiallyOpen: index === 0,
6732
7570
  t,
6733
7571
  comments: reviewComments.filter((comment) => comment.path === file.path),
@@ -7146,10 +7984,28 @@ window.__ModuleLoader__.load({
7146
7984
  }
7147
7985
  //#endregion
7148
7986
  //#region src/client/ClaudePullRequestsPanel.tsx
7987
+ const NO_WORKSPACE_STATE = {};
7988
+ const NO_WORKSPACES = {
7989
+ subscribe: () => () => {},
7990
+ getSnapshot: () => NO_WORKSPACE_STATE
7991
+ };
7149
7992
  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));
7993
+ /** What a running session is blocked on: the latest permission or question
7994
+ * activity that is still in its started phase. */
7995
+ function overviewAttention(activities) {
7996
+ for (let index = activities.length - 1; index >= 0; index -= 1) {
7997
+ const activity = activities[index];
7998
+ if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
7999
+ return activity.phase === "started" ? activity.kind : void 0;
8000
+ }
8001
+ }
8002
+ /** Claude sessions worth listing: rows still in the host list (byId keeps
8003
+ * deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
8004
+ * running first. */
8005
+ function claudeSessionRows(state, archivedSessionIds = []) {
8006
+ const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
8007
+ const archived = new Set(archivedSessionIds);
8008
+ 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
8009
  }
7154
8010
  function Badge({ label, tone = "neutral" }) {
7155
8011
  const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
@@ -7167,112 +8023,732 @@ window.__ModuleLoader__.load({
7167
8023
  })]
7168
8024
  });
7169
8025
  }
7170
- function repositoryName(remote) {
7171
- return remote?.split("/").at(-1);
7172
- }
7173
- function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions }) {
7174
- const snapshot = useSyncExternalStore(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot);
7175
- const rows = useMemo(() => claudeSessionRows(snapshot.byId), [snapshot.byId]);
7176
- const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
7177
- const [statuses, setStatuses] = useState({});
8026
+ function repositoryName(remote) {
8027
+ return remote?.split("/").at(-1);
8028
+ }
8029
+ function OverviewAttention({ source, running, t }) {
8030
+ const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
8031
+ const attention = running ? overviewAttention(snapshot.activities) : void 0;
8032
+ const usage = snapshot.contextUsage;
8033
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
8034
+ attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
8035
+ label: t("overviewNeedsPermission"),
8036
+ tone: "warning"
8037
+ }) : null,
8038
+ attention === "question" ? /* @__PURE__ */ jsx(Badge, {
8039
+ label: t("overviewNeedsAnswer"),
8040
+ tone: "warning"
8041
+ }) : null,
8042
+ usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
8043
+ ] });
8044
+ }
8045
+ function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
8046
+ const snapshot = useSyncExternalStore(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot);
8047
+ const workspaceStore = workspaces ?? NO_WORKSPACES;
8048
+ const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
8049
+ const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
8050
+ const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
8051
+ const [statuses, setStatuses] = useState({});
8052
+ useEffect(() => {
8053
+ const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
8054
+ if (cwds.length === 0) return;
8055
+ const controller = new AbortController();
8056
+ const refresh = () => {
8057
+ for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
8058
+ if (!controller.signal.aborted) setStatuses((previous) => ({
8059
+ ...previous,
8060
+ [cwd]: status
8061
+ }));
8062
+ }, () => void 0);
8063
+ };
8064
+ refresh();
8065
+ const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
8066
+ return () => {
8067
+ controller.abort();
8068
+ clearInterval(timer);
8069
+ };
8070
+ }, [cwdKey, loadStatus]);
8071
+ return /* @__PURE__ */ jsxs("div", {
8072
+ className: detailsCardClass,
8073
+ style: tasksPanel,
8074
+ children: [
8075
+ /* @__PURE__ */ jsxs("style", {
8076
+ "data-dsh-claude-overview-styles": true,
8077
+ children: [detailsCardCss, panelIconButtonCss]
8078
+ }),
8079
+ /* @__PURE__ */ jsxs("header", {
8080
+ style: tasksHeader,
8081
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
8082
+ style: tasksHeading,
8083
+ children: t("overviewTitle")
8084
+ }), /* @__PURE__ */ jsx("span", {
8085
+ style: tasksTurnMeta,
8086
+ children: t("overviewBody")
8087
+ })] }), /* @__PURE__ */ jsx("button", {
8088
+ type: "button",
8089
+ className: panelIconButtonClass,
8090
+ "aria-label": t("diffClose"),
8091
+ onClick: closeDetails,
8092
+ children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
8093
+ })]
8094
+ }),
8095
+ /* @__PURE__ */ jsx("div", {
8096
+ style: overviewBody,
8097
+ children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
8098
+ style: overviewEmpty,
8099
+ children: t("overviewEmpty")
8100
+ }) : rows.map((row) => {
8101
+ const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
8102
+ const pullRequest = repository?.pullRequest;
8103
+ const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
8104
+ return /* @__PURE__ */ jsxs("button", {
8105
+ type: "button",
8106
+ style: overviewRow,
8107
+ onClick: () => {
8108
+ openSession(row.id);
8109
+ },
8110
+ children: [/* @__PURE__ */ jsxs("span", {
8111
+ style: overviewRowTop,
8112
+ children: [
8113
+ row.running === true ? /* @__PURE__ */ jsx("span", {
8114
+ style: overviewRunningDot,
8115
+ "aria-label": t("overviewRunning")
8116
+ }) : null,
8117
+ /* @__PURE__ */ jsx("span", {
8118
+ style: overviewTitle,
8119
+ children: row.displayTitle ?? row.id
8120
+ }),
8121
+ pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
8122
+ label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
8123
+ tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
8124
+ })
8125
+ ]
8126
+ }), /* @__PURE__ */ jsxs("span", {
8127
+ style: overviewMeta,
8128
+ children: [
8129
+ repositoryName(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName(repository?.remote) }),
8130
+ /* @__PURE__ */ jsx("span", {
8131
+ style: overviewBranch,
8132
+ children: branch
8133
+ }),
8134
+ pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
8135
+ label: t(`repositoryChecks_${pullRequest.checks}`),
8136
+ tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
8137
+ }) : null,
8138
+ pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
8139
+ label: t(`repositoryReview_${pullRequest.review}`),
8140
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
8141
+ }) : null,
8142
+ autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
8143
+ label: t("overviewAutoFix"),
8144
+ tone: "success"
8145
+ }) : null,
8146
+ projectionFor === void 0 ? null : /* @__PURE__ */ jsx(OverviewAttention, {
8147
+ source: projectionFor(row.id),
8148
+ running: row.running === true,
8149
+ t
8150
+ })
8151
+ ]
8152
+ })]
8153
+ }, row.id);
8154
+ })
8155
+ })
8156
+ ]
8157
+ });
8158
+ }
8159
+ //#endregion
8160
+ //#region src/client/ask-api.ts
8161
+ function record(value) {
8162
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8163
+ }
8164
+ function parseAskEvent(line) {
8165
+ const event = record(JSON.parse(line));
8166
+ if (event?.type === "delta" && typeof event.text === "string") return {
8167
+ type: "delta",
8168
+ text: event.text
8169
+ };
8170
+ if (event?.type === "thinking" && typeof event.text === "string") return {
8171
+ type: "thinking",
8172
+ text: event.text
8173
+ };
8174
+ if (event?.type === "status" && typeof event.text === "string") return {
8175
+ type: "status",
8176
+ text: event.text
8177
+ };
8178
+ if (event?.type === "tool" && typeof event.id === "string" && (event.phase === "start" || event.phase === "input" || event.phase === "done")) return {
8179
+ type: "tool",
8180
+ id: event.id,
8181
+ phase: event.phase,
8182
+ ...typeof event.name === "string" ? { name: event.name } : {},
8183
+ ...typeof event.summary === "string" ? { summary: event.summary } : {},
8184
+ ...event.error === true ? { error: true } : {}
8185
+ };
8186
+ if (event?.type === "done") return { type: "done" };
8187
+ if (event?.type === "error") return {
8188
+ type: "error",
8189
+ message: typeof event.message === "string" ? event.message : "The question could not be answered."
8190
+ };
8191
+ throw new Error("Invalid ask stream event.");
8192
+ }
8193
+ /** Stream an answer about selected reply text; resolves when the answer completes. */
8194
+ async function askAboutSelection(sessionId, request, onProgress, signal) {
8195
+ const response = await fetch(`${CLAUDE_ASK_PATH}?sessionId=${encodeURIComponent(sessionId)}`, {
8196
+ method: "POST",
8197
+ credentials: "same-origin",
8198
+ headers: {
8199
+ accept: "application/x-ndjson",
8200
+ "content-type": "application/json"
8201
+ },
8202
+ body: JSON.stringify(request),
8203
+ ...signal === void 0 ? {} : { signal }
8204
+ });
8205
+ if (!response.ok) {
8206
+ const body = record(await response.json().catch(() => void 0));
8207
+ throw new Error(typeof body?.message === "string" ? body.message : "The question could not be sent.");
8208
+ }
8209
+ if (response.body === null) throw new Error("The answer stream is unavailable.");
8210
+ const reader = response.body.getReader();
8211
+ const decoder = new TextDecoder();
8212
+ let buffer = "";
8213
+ let finished = false;
8214
+ const handle = (line) => {
8215
+ if (line.trim().length === 0) return;
8216
+ const event = parseAskEvent(line);
8217
+ if (event.type === "delta") onProgress({
8218
+ type: "text",
8219
+ text: event.text
8220
+ });
8221
+ else if (event.type === "thinking") onProgress({
8222
+ type: "thinking",
8223
+ text: event.text
8224
+ });
8225
+ else if (event.type === "status") onProgress({
8226
+ type: "status",
8227
+ text: event.text
8228
+ });
8229
+ else if (event.type === "tool") onProgress(event);
8230
+ else if (event.type === "done") finished = true;
8231
+ else throw new Error(event.message);
8232
+ };
8233
+ while (true) {
8234
+ const chunk = await reader.read();
8235
+ buffer += decoder.decode(chunk.value, { stream: !chunk.done });
8236
+ const lines = buffer.split("\n");
8237
+ buffer = lines.pop() ?? "";
8238
+ for (const line of lines) handle(line);
8239
+ if (chunk.done) break;
8240
+ }
8241
+ handle(buffer);
8242
+ if (!finished) throw new Error("The answer ended unexpectedly.");
8243
+ }
8244
+ //#endregion
8245
+ //#region src/client/ClaudeSelectionAsk.tsx
8246
+ const CHAT_NODE = "[data-chat-flow-kind]";
8247
+ const USER_KIND = "user";
8248
+ const MAX_SELECTION_CHARS = 8e3;
8249
+ const MAX_CONTEXT_CHARS = 16e3;
8250
+ const POPUP_WIDTH = 560;
8251
+ const POPUP_ESTIMATED_HEIGHT = 320;
8252
+ const TOOLBAR_WIDTH = 64;
8253
+ const HIGHLIGHT_NAME = "dsh-claude-ask";
8254
+ function appendAnswerBlock(blocks, progress) {
8255
+ if (progress.type === "text") {
8256
+ const last = blocks.at(-1);
8257
+ if (last?.kind === "text") return [...blocks.slice(0, -1), {
8258
+ kind: "text",
8259
+ text: last.text + progress.text
8260
+ }];
8261
+ return [...blocks, {
8262
+ kind: "text",
8263
+ text: progress.text
8264
+ }];
8265
+ }
8266
+ if (progress.type !== "tool") return blocks;
8267
+ const index = blocks.findIndex((block) => block.kind === "tool" && block.id === progress.id);
8268
+ if (index < 0) {
8269
+ if (progress.phase === "done") return blocks;
8270
+ return [...blocks, {
8271
+ kind: "tool",
8272
+ id: progress.id,
8273
+ name: progress.name ?? "tool",
8274
+ state: "running",
8275
+ ...progress.summary === void 0 ? {} : { summary: progress.summary }
8276
+ }];
8277
+ }
8278
+ const current = blocks[index];
8279
+ const next = {
8280
+ ...current,
8281
+ ...progress.name === void 0 ? {} : { name: progress.name },
8282
+ ...progress.summary === void 0 ? {} : { summary: progress.summary },
8283
+ state: progress.phase === "done" ? progress.error === true ? "error" : "done" : current.state
8284
+ };
8285
+ return [
8286
+ ...blocks.slice(0, index),
8287
+ next,
8288
+ ...blocks.slice(index + 1)
8289
+ ];
8290
+ }
8291
+ function answerText(blocks) {
8292
+ return blocks.filter((block) => block.kind === "text").map((block) => block.text.trim()).filter((text) => text.length > 0).join("\n\n");
8293
+ }
8294
+ function rectOf(range) {
8295
+ const rect = range.getBoundingClientRect();
8296
+ return {
8297
+ top: rect.top,
8298
+ left: rect.left,
8299
+ width: rect.width,
8300
+ bottom: rect.bottom
8301
+ };
8302
+ }
8303
+ /** Read the current selection when it sits inside an assistant reply. */
8304
+ function selectionInfoOf(selection, sessionId) {
8305
+ if (sessionId === void 0 || selection === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
8306
+ const range = selection.getRangeAt(0);
8307
+ const hostOf = (node) => {
8308
+ return (node instanceof Element ? node : node?.parentElement ?? null)?.closest(CHAT_NODE) ?? null;
8309
+ };
8310
+ const host = hostOf(range.startContainer) ?? hostOf(range.endContainer);
8311
+ if (host === null || host.dataset.chatFlowKind === USER_KIND) return void 0;
8312
+ const text = selection.toString().trim();
8313
+ if (text.length === 0) return void 0;
8314
+ return {
8315
+ text: text.slice(0, MAX_SELECTION_CHARS),
8316
+ context: (host.textContent ?? "").trim().slice(0, MAX_CONTEXT_CHARS),
8317
+ rect: rectOf(range),
8318
+ range: range.cloneRange()
8319
+ };
8320
+ }
8321
+ function toolbarPosition(rect, viewportWidth) {
8322
+ return {
8323
+ top: Math.max(8, rect.top - 40),
8324
+ left: Math.min(Math.max(8, rect.left + rect.width / 2 - TOOLBAR_WIDTH / 2), Math.max(8, viewportWidth - TOOLBAR_WIDTH - 8))
8325
+ };
8326
+ }
8327
+ /** Pick the side with room once, when the popup opens: below the selection
8328
+ * unless the space there is short and above is roomier. */
8329
+ function popupSide(rect, viewportHeight) {
8330
+ const roomBelow = viewportHeight - rect.bottom - 16;
8331
+ const roomAbove = rect.top - 16;
8332
+ return roomBelow >= POPUP_ESTIMATED_HEIGHT || roomBelow >= roomAbove ? "below" : "above";
8333
+ }
8334
+ /** Anchor on the chosen side so streamed content grows away from the text and
8335
+ * scrolls inside the popup instead of running off screen. */
8336
+ function popupPlacement(rect, side, viewportWidth, viewportHeight) {
8337
+ const width = Math.min(POPUP_WIDTH, viewportWidth - 16);
8338
+ const left = Math.min(Math.max(8, rect.left), Math.max(8, viewportWidth - width - 8));
8339
+ const cap = Math.round(viewportHeight * .7);
8340
+ if (side === "below") {
8341
+ const top = Math.max(8, rect.bottom + 8);
8342
+ return {
8343
+ top,
8344
+ left,
8345
+ width,
8346
+ maxHeight: Math.max(120, Math.min(cap, viewportHeight - top - 8))
8347
+ };
8348
+ }
8349
+ const bottom = Math.max(8, viewportHeight - rect.top + 8);
8350
+ return {
8351
+ bottom,
8352
+ left,
8353
+ width,
8354
+ maxHeight: Math.max(120, Math.min(cap, viewportHeight - bottom - 8))
8355
+ };
8356
+ }
8357
+ function CopyIcon() {
8358
+ return /* @__PURE__ */ jsxs("svg", {
8359
+ width: "14",
8360
+ height: "14",
8361
+ viewBox: "0 0 16 16",
8362
+ fill: "none",
8363
+ stroke: "currentColor",
8364
+ strokeWidth: "1.5",
8365
+ strokeLinecap: "round",
8366
+ strokeLinejoin: "round",
8367
+ "aria-hidden": "true",
8368
+ children: [/* @__PURE__ */ jsx("rect", {
8369
+ x: "5.5",
8370
+ y: "5.5",
8371
+ width: "8",
8372
+ height: "8",
8373
+ rx: "1.5"
8374
+ }), /* @__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" })]
8375
+ });
8376
+ }
8377
+ function AskIcon() {
8378
+ return /* @__PURE__ */ jsxs("svg", {
8379
+ width: "14",
8380
+ height: "14",
8381
+ viewBox: "0 0 16 16",
8382
+ fill: "none",
8383
+ stroke: "currentColor",
8384
+ strokeWidth: "1.5",
8385
+ strokeLinecap: "round",
8386
+ strokeLinejoin: "round",
8387
+ "aria-hidden": "true",
8388
+ 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" })]
8389
+ });
8390
+ }
8391
+ function CheckIcon() {
8392
+ return /* @__PURE__ */ jsx("svg", {
8393
+ width: "14",
8394
+ height: "14",
8395
+ viewBox: "0 0 16 16",
8396
+ fill: "none",
8397
+ stroke: "currentColor",
8398
+ strokeWidth: "1.8",
8399
+ strokeLinecap: "round",
8400
+ strokeLinejoin: "round",
8401
+ "aria-hidden": "true",
8402
+ children: /* @__PURE__ */ jsx("path", { d: "m3.5 8.5 3 3 6-7" })
8403
+ });
8404
+ }
8405
+ /** Selection toolbar over assistant replies: copy, or ask a follow-up whose
8406
+ * answer streams into a popup rendered with the Host Markdown renderer. */
8407
+ function ClaudeSelectionAsk({ t, currentSessionId, ownsSession, insertIntoChat }) {
8408
+ const [selection, setSelection] = useState();
8409
+ const [open, setOpen] = useState(false);
8410
+ const [question, setQuestion] = useState("");
8411
+ const [blocks, setBlocks] = useState([]);
8412
+ const answer = answerText(blocks);
8413
+ const [thinking, setThinking] = useState("");
8414
+ const [thinkingOpen, setThinkingOpen] = useState(false);
8415
+ const [started, setStarted] = useState(false);
8416
+ const [phase, setPhase] = useState("idle");
8417
+ const [error, setError] = useState();
8418
+ const [copied, setCopied] = useState();
8419
+ const [rect, setRect] = useState();
8420
+ const [side, setSide] = useState("below");
8421
+ const popupRef = useRef(null);
8422
+ const toolbarRef = useRef(null);
8423
+ const controller = useRef();
8424
+ const openRef = useRef(false);
8425
+ openRef.current = open;
8426
+ const close = useCallback(() => {
8427
+ controller.current?.abort();
8428
+ controller.current = void 0;
8429
+ setOpen(false);
8430
+ setSelection(void 0);
8431
+ setQuestion("");
8432
+ setBlocks([]);
8433
+ setThinking("");
8434
+ setThinkingOpen(false);
8435
+ setStarted(false);
8436
+ setPhase("idle");
8437
+ setError(void 0);
8438
+ setCopied(void 0);
8439
+ }, []);
7178
8440
  useEffect(() => {
7179
- const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
7180
- if (cwds.length === 0) return;
7181
- const controller = new AbortController();
7182
- const refresh = () => {
7183
- for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
7184
- if (!controller.signal.aborted) setStatuses((previous) => ({
7185
- ...previous,
7186
- [cwd]: status
7187
- }));
7188
- }, () => void 0);
8441
+ if (typeof document === "undefined") return;
8442
+ let timer;
8443
+ const update = () => {
8444
+ if (openRef.current) return;
8445
+ const sessionId = currentSessionId();
8446
+ if (sessionId === void 0 || !ownsSession(sessionId)) {
8447
+ setSelection(void 0);
8448
+ return;
8449
+ }
8450
+ const info = selectionInfoOf(document.getSelection(), sessionId);
8451
+ setSelection(info === void 0 ? void 0 : {
8452
+ sessionId,
8453
+ ...info
8454
+ });
7189
8455
  };
7190
- refresh();
7191
- const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
8456
+ const schedule = () => {
8457
+ if (timer !== void 0) clearTimeout(timer);
8458
+ timer = setTimeout(update, 120);
8459
+ };
8460
+ const onKey = (event) => {
8461
+ if (event.key === "Escape") close();
8462
+ };
8463
+ const onPointerDown = (event) => {
8464
+ if (!(event.target instanceof Node)) return;
8465
+ if (popupRef.current?.contains(event.target) === true || toolbarRef.current?.contains(event.target) === true) return;
8466
+ if (openRef.current) close();
8467
+ };
8468
+ document.addEventListener("selectionchange", schedule);
8469
+ document.addEventListener("mouseup", schedule);
8470
+ document.addEventListener("keydown", onKey);
8471
+ document.addEventListener("pointerdown", onPointerDown);
7192
8472
  return () => {
7193
- controller.abort();
7194
- clearInterval(timer);
8473
+ if (timer !== void 0) clearTimeout(timer);
8474
+ document.removeEventListener("selectionchange", schedule);
8475
+ document.removeEventListener("mouseup", schedule);
8476
+ document.removeEventListener("keydown", onKey);
8477
+ document.removeEventListener("pointerdown", onPointerDown);
7195
8478
  };
7196
- }, [cwdKey, loadStatus]);
8479
+ }, [
8480
+ close,
8481
+ currentSessionId,
8482
+ ownsSession
8483
+ ]);
8484
+ useEffect(() => () => controller.current?.abort(), []);
8485
+ useEffect(() => {
8486
+ setRect(selection?.rect);
8487
+ if (selection === void 0) return;
8488
+ let frame;
8489
+ const reposition = () => {
8490
+ if (frame !== void 0) return;
8491
+ frame = requestAnimationFrame(() => {
8492
+ frame = void 0;
8493
+ const next = rectOf(selection.range);
8494
+ if (next.width === 0 && next.bottom === next.top) return;
8495
+ setRect(next);
8496
+ });
8497
+ };
8498
+ document.addEventListener("scroll", reposition, true);
8499
+ window.addEventListener("resize", reposition);
8500
+ return () => {
8501
+ if (frame !== void 0) cancelAnimationFrame(frame);
8502
+ document.removeEventListener("scroll", reposition, true);
8503
+ window.removeEventListener("resize", reposition);
8504
+ };
8505
+ }, [selection]);
8506
+ useEffect(() => {
8507
+ if (!open || selection === void 0) return;
8508
+ const runtime = globalThis;
8509
+ const registry = runtime.CSS?.highlights;
8510
+ if (registry === void 0 || runtime.Highlight === void 0) return;
8511
+ registry.set(HIGHLIGHT_NAME, new runtime.Highlight(selection.range));
8512
+ return () => {
8513
+ registry.delete(HIGHLIGHT_NAME);
8514
+ };
8515
+ }, [open, selection]);
8516
+ useEffect(() => {
8517
+ if (copied === void 0) return;
8518
+ const timer = setTimeout(() => setCopied(void 0), 1200);
8519
+ return () => clearTimeout(timer);
8520
+ }, [copied]);
8521
+ if (selection === void 0 || rect === void 0) return /* @__PURE__ */ jsx("span", {
8522
+ "data-dsh-claude-selection-ask": "armed",
8523
+ hidden: true
8524
+ });
8525
+ const copy = (text, what) => {
8526
+ navigator.clipboard.writeText(text).then(() => {
8527
+ setCopied(what);
8528
+ }, () => void 0);
8529
+ };
8530
+ const submit = () => {
8531
+ if (question.trim().length === 0 || phase === "answering") return;
8532
+ controller.current?.abort();
8533
+ const aborter = new AbortController();
8534
+ controller.current = aborter;
8535
+ setBlocks([]);
8536
+ setThinking("");
8537
+ setThinkingOpen(false);
8538
+ setStarted(false);
8539
+ setError(void 0);
8540
+ setPhase("answering");
8541
+ askAboutSelection(selection.sessionId, {
8542
+ selection: selection.text,
8543
+ context: selection.context,
8544
+ question
8545
+ }, (progress) => {
8546
+ if (progress.type === "status") setStarted(true);
8547
+ else if (progress.type === "thinking") setThinking((current) => current + progress.text);
8548
+ else setBlocks((current) => appendAnswerBlock(current, progress));
8549
+ }, aborter.signal).then(() => {
8550
+ if (!aborter.signal.aborted) setPhase("done");
8551
+ }, (reason) => {
8552
+ if (aborter.signal.aborted) return;
8553
+ setError(reason instanceof Error ? reason.message : t("askFailed"));
8554
+ setPhase("done");
8555
+ });
8556
+ };
8557
+ const viewportWidth = typeof window === "undefined" ? 1280 : window.innerWidth;
8558
+ const viewportHeight = typeof window === "undefined" ? 800 : window.innerHeight;
8559
+ if (!open) {
8560
+ const position = toolbarPosition(rect, viewportWidth);
8561
+ return /* @__PURE__ */ jsxs("div", {
8562
+ ref: toolbarRef,
8563
+ role: "toolbar",
8564
+ "aria-label": t("askToolbar"),
8565
+ style: {
8566
+ ...askToolbar,
8567
+ top: position.top,
8568
+ left: position.left
8569
+ },
8570
+ children: [
8571
+ /* @__PURE__ */ jsx("style", {
8572
+ "data-dsh-claude-ask-styles": true,
8573
+ children: panelIconButtonCss
8574
+ }),
8575
+ /* @__PURE__ */ jsx(Tooltip, {
8576
+ label: copied === "selection" ? t("askCopied") : t("copyTooltip"),
8577
+ side: "top",
8578
+ delayMs: 300,
8579
+ children: /* @__PURE__ */ jsx("button", {
8580
+ type: "button",
8581
+ className: panelIconButtonClass,
8582
+ "aria-label": t("copyTooltip"),
8583
+ onMouseDown: (event) => {
8584
+ event.preventDefault();
8585
+ },
8586
+ onClick: () => {
8587
+ copy(selection.text, "selection");
8588
+ },
8589
+ children: copied === "selection" ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
8590
+ })
8591
+ }),
8592
+ /* @__PURE__ */ jsx(Tooltip, {
8593
+ label: t("askTooltip"),
8594
+ side: "top",
8595
+ delayMs: 300,
8596
+ children: /* @__PURE__ */ jsx("button", {
8597
+ type: "button",
8598
+ className: panelIconButtonClass,
8599
+ "aria-label": t("askTooltip"),
8600
+ onMouseDown: (event) => {
8601
+ event.preventDefault();
8602
+ },
8603
+ onClick: () => {
8604
+ setSide(popupSide(rect, viewportHeight));
8605
+ setOpen(true);
8606
+ },
8607
+ children: /* @__PURE__ */ jsx(AskIcon, {})
8608
+ })
8609
+ })
8610
+ ]
8611
+ });
8612
+ }
8613
+ const placement = popupPlacement(rect, side, viewportWidth, viewportHeight);
7197
8614
  return /* @__PURE__ */ jsxs("div", {
7198
- style: tasksPanel,
8615
+ ref: popupRef,
8616
+ role: "dialog",
8617
+ "aria-label": t("askTooltip"),
8618
+ style: {
8619
+ ...askPopup,
8620
+ ...placement
8621
+ },
7199
8622
  children: [
7200
- /* @__PURE__ */ jsx("style", {
7201
- "data-dsh-claude-overview-styles": true,
7202
- children: panelIconButtonCss
8623
+ /* @__PURE__ */ jsxs("style", {
8624
+ "data-dsh-claude-ask-styles": true,
8625
+ children: [panelIconButtonCss, askHighlightCss]
7203
8626
  }),
7204
- /* @__PURE__ */ jsxs("header", {
7205
- style: tasksHeader,
7206
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
7207
- style: tasksHeading,
7208
- children: t("overviewTitle")
7209
- }), /* @__PURE__ */ jsx("span", {
7210
- style: tasksTurnMeta,
7211
- children: t("overviewBody")
7212
- })] }), /* @__PURE__ */ jsx("button", {
8627
+ /* @__PURE__ */ jsx("p", {
8628
+ style: askQuote,
8629
+ children: selection.text
8630
+ }),
8631
+ phase === "idle" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("textarea", {
8632
+ autoFocus: true,
8633
+ value: question,
8634
+ placeholder: t("askPlaceholder"),
8635
+ style: askTextarea,
8636
+ onChange: (event) => {
8637
+ setQuestion(event.currentTarget.value);
8638
+ },
8639
+ onKeyDown: (event) => {
8640
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
8641
+ event.preventDefault();
8642
+ submit();
8643
+ }
8644
+ }
8645
+ }), /* @__PURE__ */ jsxs("div", {
8646
+ style: askActions,
8647
+ children: [/* @__PURE__ */ jsx("button", {
7213
8648
  type: "button",
7214
- className: panelIconButtonClass,
7215
- "aria-label": t("diffClose"),
7216
- onClick: closeDetails,
7217
- children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
8649
+ style: askButton,
8650
+ onClick: close,
8651
+ children: t("askClose")
8652
+ }), /* @__PURE__ */ jsx("button", {
8653
+ type: "button",
8654
+ style: {
8655
+ ...askButton,
8656
+ ...askPrimaryButton
8657
+ },
8658
+ disabled: question.trim().length === 0,
8659
+ onClick: submit,
8660
+ children: t("askSubmit")
7218
8661
  })]
7219
- }),
7220
- /* @__PURE__ */ jsx("div", {
7221
- style: overviewBody,
7222
- children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
7223
- style: overviewEmpty,
7224
- children: t("overviewEmpty")
7225
- }) : rows.map((row) => {
7226
- const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
7227
- const pullRequest = repository?.pullRequest;
7228
- const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
7229
- return /* @__PURE__ */ jsxs("button", {
7230
- type: "button",
7231
- style: overviewRow,
7232
- onClick: () => {
7233
- openSession(row.id);
7234
- },
7235
- children: [/* @__PURE__ */ jsxs("span", {
7236
- style: overviewRowTop,
8662
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8663
+ /* @__PURE__ */ jsx("p", {
8664
+ style: askQuestion,
8665
+ children: question
8666
+ }),
8667
+ /* @__PURE__ */ jsxs("div", {
8668
+ style: askAnswer,
8669
+ children: [
8670
+ thinking.length === 0 ? null : /* @__PURE__ */ jsxs("button", {
8671
+ type: "button",
8672
+ style: askThinkingRow,
8673
+ "aria-expanded": thinkingOpen,
8674
+ onClick: () => {
8675
+ setThinkingOpen((value) => !value);
8676
+ },
8677
+ children: [/* @__PURE__ */ jsx("span", {
8678
+ style: askThinkingLabel,
8679
+ children: phase === "answering" && answer.length === 0 ? t("askThinkingLive") : t("askThinkingLabel")
8680
+ }), /* @__PURE__ */ jsx("span", {
8681
+ style: thinkingOpen ? askThinkingFull : askThinkingPreview,
8682
+ children: thinkingOpen ? thinking : thinking.trimEnd().split("\n").filter((line) => line.trim().length > 0).at(-1)
8683
+ })]
8684
+ }),
8685
+ blocks.length === 0 && thinking.length === 0 && error === void 0 ? /* @__PURE__ */ jsx("span", {
8686
+ style: askStatus,
8687
+ children: started ? t("askThinking") : t("askStarting")
8688
+ }) : null,
8689
+ blocks.map((block, index) => block.kind === "text" ? /* @__PURE__ */ jsx(MarkdownText, {
8690
+ text: block.text,
8691
+ streaming: phase === "answering" && index === blocks.length - 1
8692
+ }, `text-${index}`) : /* @__PURE__ */ jsxs("div", {
8693
+ style: askToolRow,
7237
8694
  children: [
7238
- row.running === true ? /* @__PURE__ */ jsx("span", {
7239
- style: overviewRunningDot,
7240
- "aria-label": t("overviewRunning")
7241
- }) : null,
7242
8695
  /* @__PURE__ */ jsx("span", {
7243
- style: overviewTitle,
7244
- children: row.displayTitle ?? row.id
8696
+ style: {
8697
+ ...askToolGlyph,
8698
+ ...block.state === "error" ? askToolGlyphError : block.state === "done" ? askToolGlyphDone : {}
8699
+ },
8700
+ "aria-hidden": "true",
8701
+ children: block.state === "running" ? "…" : block.state === "done" ? "✓" : "×"
7245
8702
  }),
7246
- pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
7247
- label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
7248
- tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
7249
- })
7250
- ]
7251
- }), /* @__PURE__ */ jsxs("span", {
7252
- style: overviewMeta,
7253
- children: [
7254
- repositoryName(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName(repository?.remote) }),
7255
8703
  /* @__PURE__ */ jsx("span", {
7256
- style: overviewBranch,
7257
- children: branch
8704
+ style: askToolName,
8705
+ children: block.name
7258
8706
  }),
7259
- pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
7260
- label: t(`repositoryChecks_${pullRequest.checks}`),
7261
- tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
7262
- }) : null,
7263
- pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
7264
- label: t(`repositoryReview_${pullRequest.review}`),
7265
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
7266
- }) : null,
7267
- autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
7268
- label: t("overviewAutoFix"),
7269
- tone: "success"
7270
- }) : null
8707
+ block.summary === void 0 ? null : /* @__PURE__ */ jsx("span", {
8708
+ style: askToolSummary,
8709
+ children: block.summary
8710
+ })
7271
8711
  ]
7272
- })]
7273
- }, row.id);
8712
+ }, block.id)),
8713
+ error === void 0 ? null : /* @__PURE__ */ jsx("p", {
8714
+ role: "alert",
8715
+ style: askError,
8716
+ children: error
8717
+ })
8718
+ ]
8719
+ }),
8720
+ /* @__PURE__ */ jsxs("div", {
8721
+ style: askActions,
8722
+ children: [
8723
+ answer.length === 0 ? null : /* @__PURE__ */ jsx("button", {
8724
+ type: "button",
8725
+ style: askButton,
8726
+ onClick: () => {
8727
+ copy(answer, "answer");
8728
+ },
8729
+ children: copied === "answer" ? t("askCopied") : t("askCopyAnswer")
8730
+ }),
8731
+ answer.length === 0 || insertIntoChat === void 0 ? null : /* @__PURE__ */ jsx("button", {
8732
+ type: "button",
8733
+ style: askButton,
8734
+ onClick: () => {
8735
+ insertIntoChat(selection.sessionId, `> ${selection.text.replaceAll("\n", "\n> ")}\n\n**${question}**\n\n${answer}`);
8736
+ close();
8737
+ },
8738
+ children: t("askSendToChat")
8739
+ }),
8740
+ /* @__PURE__ */ jsx("button", {
8741
+ type: "button",
8742
+ style: {
8743
+ ...askButton,
8744
+ ...askPrimaryButton
8745
+ },
8746
+ onClick: close,
8747
+ children: t("askClose")
8748
+ })
8749
+ ]
7274
8750
  })
7275
- })
8751
+ ] })
7276
8752
  ]
7277
8753
  });
7278
8754
  }
@@ -7310,9 +8786,15 @@ window.__ModuleLoader__.load({
7310
8786
  row = row.parentElement;
7311
8787
  }
7312
8788
  }
8789
+ /** Reuse the row's existing portal even when the host moved a node between it
8790
+ * and the seat: a fresh element would change React's portal identity and
8791
+ * restart (and abort) every effect keyed on it. */
7313
8792
  function ensureClaudeHeroPortal(seat) {
7314
- const sibling = seat.nextElementSibling;
7315
- if (sibling instanceof HTMLElement && sibling.hasAttribute(PORTAL_ATTRIBUTE)) return sibling;
8793
+ const existing = seat.parentElement?.querySelector(`:scope > [${PORTAL_ATTRIBUTE}]`) ?? null;
8794
+ if (existing !== null) {
8795
+ if (existing.previousElementSibling !== seat) seat.insertAdjacentElement("afterend", existing);
8796
+ return existing;
8797
+ }
7316
8798
  const portal = document.createElement("span");
7317
8799
  portal.setAttribute(PORTAL_ATTRIBUTE, "");
7318
8800
  seat.insertAdjacentElement("afterend", portal);
@@ -7347,12 +8829,18 @@ window.__ModuleLoader__.load({
7347
8829
  const normalized = query.trim().toLocaleLowerCase();
7348
8830
  return normalized.length === 0 ? branches : branches.filter((branch) => branch.toLocaleLowerCase().includes(normalized));
7349
8831
  }
8832
+ /** Toggle a ticket's membership in the multi-select, keyed by ticket key. */
8833
+ function toggleTicketSelection(tickets, ticket) {
8834
+ return tickets.some((item) => item.key === ticket.key) ? tickets.filter((item) => item.key !== ticket.key) : [...tickets, ticket];
8835
+ }
7350
8836
  function branchMenuNavigationIndex(current, count, key) {
7351
8837
  if (count <= 0) return 0;
7352
8838
  if (key === "Home") return 0;
7353
8839
  if (key === "End") return count - 1;
7354
8840
  return (current + (key === "ArrowDown" ? 1 : -1) + count) % count;
7355
8841
  }
8842
+ const BRANCH_LOAD_RETRIES = 2;
8843
+ const BRANCH_LOAD_RETRY_MS = 1e3;
7356
8844
  const WORKTREE_PROGRESS_STAGES = [
7357
8845
  "inspecting",
7358
8846
  "fetching",
@@ -7377,7 +8865,7 @@ window.__ModuleLoader__.load({
7377
8865
  function progressLabelKey(stage) {
7378
8866
  return PROGRESS_LABEL_KEYS[stage];
7379
8867
  }
7380
- function WorktreeProgressCard({ stage, error, t, onDismiss }) {
8868
+ function WorktreeProgressCard({ stage, error, context, t, onDismiss }) {
7381
8869
  const current = WORKTREE_PROGRESS_STAGES.indexOf(stage);
7382
8870
  const visible = WORKTREE_PROGRESS_STAGES.slice(0, Math.max(0, current) + 1);
7383
8871
  return /* @__PURE__ */ jsxs("div", {
@@ -7421,9 +8909,12 @@ window.__ModuleLoader__.load({
7421
8909
  }),
7422
8910
  /* @__PURE__ */ jsxs("div", {
7423
8911
  style: heroWorktreeProgressCopy,
7424
- children: [/* @__PURE__ */ jsx("strong", {
8912
+ children: [/* @__PURE__ */ jsxs("strong", {
7425
8913
  style: heroWorktreeProgressTitle,
7426
- children: error === void 0 ? t("repositoryProgressTitle") : t("repositoryProgressFailed")
8914
+ children: [error === void 0 ? t("repositoryProgressTitle") : t("repositoryProgressFailed"), context === void 0 ? null : /* @__PURE__ */ jsxs("span", {
8915
+ style: heroWorktreeProgressContext,
8916
+ children: [" ", context]
8917
+ })]
7427
8918
  }), /* @__PURE__ */ jsx("span", {
7428
8919
  style: heroWorktreeProgressCurrent,
7429
8920
  children: error ?? t(progressLabelKey(stage))
@@ -7470,7 +8961,7 @@ window.__ModuleLoader__.load({
7470
8961
  })]
7471
8962
  });
7472
8963
  }
7473
- function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, onMenuOpenChange, onSelect, onWorktreeChange }) {
8964
+ function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, worktreeLocked = false, onMenuOpenChange, onSelect, onWorktreeChange }) {
7474
8965
  const pickerRef = useRef(null);
7475
8966
  const searchRef = useRef(null);
7476
8967
  const optionRefs = useRef([]);
@@ -7599,11 +9090,11 @@ window.__ModuleLoader__.load({
7599
9090
  /* @__PURE__ */ jsxs("button", {
7600
9091
  type: "button",
7601
9092
  role: "checkbox",
7602
- "aria-checked": worktree,
7603
- disabled: busy,
9093
+ "aria-checked": worktreeLocked || worktree,
9094
+ disabled: busy || worktreeLocked,
7604
9095
  style: {
7605
9096
  ...heroWorktreeToggle,
7606
- ...worktree || worktreeHovered ? heroWorktreeToggleActive : {},
9097
+ ...worktreeLocked || worktree || worktreeHovered ? heroWorktreeToggleActive : {},
7607
9098
  ...worktreeFocused ? heroWorktreeToggleFocused : {}
7608
9099
  },
7609
9100
  onMouseEnter: () => {
@@ -7612,8 +9103,8 @@ window.__ModuleLoader__.load({
7612
9103
  onMouseLeave: () => {
7613
9104
  setWorktreeHovered(false);
7614
9105
  },
7615
- onFocus: () => {
7616
- setWorktreeFocused(true);
9106
+ onFocus: (event) => {
9107
+ setWorktreeFocused(event.currentTarget.matches(":focus-visible"));
7617
9108
  },
7618
9109
  onBlur: () => {
7619
9110
  setWorktreeFocused(false);
@@ -7625,9 +9116,9 @@ window.__ModuleLoader__.load({
7625
9116
  "aria-hidden": "true",
7626
9117
  style: {
7627
9118
  ...heroWorktreeCheckbox,
7628
- ...worktree ? heroWorktreeCheckboxChecked : {}
9119
+ ...worktreeLocked || worktree ? heroWorktreeCheckboxChecked : {}
7629
9120
  },
7630
- children: worktree ? /* @__PURE__ */ jsx("svg", {
9121
+ children: worktreeLocked || worktree ? /* @__PURE__ */ jsx("svg", {
7631
9122
  viewBox: "0 0 12 12",
7632
9123
  style: heroWorktreeCheckboxIcon,
7633
9124
  children: /* @__PURE__ */ jsx("path", {
@@ -7644,7 +9135,7 @@ window.__ModuleLoader__.load({
7644
9135
  ]
7645
9136
  });
7646
9137
  }
7647
- function ClaudeHeroRepositoryControls({ sessionId, useSessions, useWorkspaces, input, t, prepare }) {
9138
+ function ClaudeHeroRepositoryControls({ sessionId, useSessions, useWorkspaces, input, t, prepare, prepareMany }) {
7648
9139
  const cwd = useSessions((state) => state.byId[sessionId]?.cwd);
7649
9140
  const workspacePath = useWorkspaces((state) => state.items.find((item) => item.sessionIds.includes(sessionId))?.path);
7650
9141
  const [portal, setPortal] = useState();
@@ -7657,7 +9148,9 @@ window.__ModuleLoader__.load({
7657
9148
  const [progressError, setProgressError] = useState();
7658
9149
  const [error, setError] = useState();
7659
9150
  const [jiraConnected, setJiraConnected] = useState(false);
7660
- const [ticket, setTicket] = useState();
9151
+ const [tickets, setTickets] = useState([]);
9152
+ const [batchProgress, setBatchProgress] = useState();
9153
+ const [batchDone, setBatchDone] = useState();
7661
9154
  const [ticketMenuOpen, setTicketMenuOpen] = useState(false);
7662
9155
  const [ticketQuery, setTicketQuery] = useState("");
7663
9156
  const [ticketResults, setTicketResults] = useState();
@@ -7706,21 +9199,35 @@ window.__ModuleLoader__.load({
7706
9199
  setProgressStage(void 0);
7707
9200
  setProgressError(void 0);
7708
9201
  setError(void 0);
7709
- setTicket(void 0);
9202
+ setTickets([]);
9203
+ setBatchProgress(void 0);
9204
+ setBatchDone(void 0);
7710
9205
  setTicketMenuOpen(false);
7711
9206
  setTicketQuery("");
7712
9207
  setTicketResults(void 0);
7713
9208
  setTicketError(void 0);
7714
9209
  if (portal === void 0 || path === void 0) return;
7715
9210
  const controller = new AbortController();
7716
- loadRepositoryBranches(path, controller.signal).then((value) => {
7717
- setBranches(value);
7718
- setSelected(value.current ?? value.branches[0] ?? "");
7719
- }, (reason) => {
7720
- if (!controller.signal.aborted) setError(reason instanceof Error ? reason.message : String(reason));
7721
- });
9211
+ let timer;
9212
+ const load = (remaining) => {
9213
+ loadRepositoryBranches(path, controller.signal).then((value) => {
9214
+ setBranches(value);
9215
+ setSelected(value.current ?? value.branches[0] ?? "");
9216
+ }, (reason) => {
9217
+ if (controller.signal.aborted) return;
9218
+ if (remaining > 0) {
9219
+ timer = setTimeout(() => {
9220
+ load(remaining - 1);
9221
+ }, BRANCH_LOAD_RETRY_MS);
9222
+ return;
9223
+ }
9224
+ setError(reason instanceof Error ? reason.message : String(reason));
9225
+ });
9226
+ };
9227
+ load(BRANCH_LOAD_RETRIES);
7722
9228
  return () => {
7723
9229
  controller.abort();
9230
+ if (timer !== void 0) clearTimeout(timer);
7724
9231
  };
7725
9232
  }, [
7726
9233
  portal,
@@ -7728,19 +9235,46 @@ window.__ModuleLoader__.load({
7728
9235
  sessionId
7729
9236
  ]);
7730
9237
  const availableBranches = branches === void 0 ? [] : repositoryBranchOptions(branches);
7731
- const changed = branches !== void 0 && selected.length > 0 && (worktree || ticket !== void 0 || selected !== branches.current);
9238
+ const changed = branches !== void 0 && selected.length > 0 && (worktree || tickets.length > 0 || selected !== branches.current);
7732
9239
  useEffect(() => {
7733
9240
  if (!changed || portal === void 0 || busy) return;
7734
9241
  const submit = (event) => {
7735
- if (pendingRef.current || input.draft.trim().length === 0 && ticket === void 0) return;
9242
+ if (pendingRef.current || input.draft.trim().length === 0 && tickets.length === 0) return;
7736
9243
  if (!(event instanceof KeyboardEvent ? shouldInterceptKey(event) : shouldInterceptClick(event))) return;
7737
9244
  event.preventDefault();
7738
9245
  event.stopPropagation();
7739
9246
  pendingRef.current = true;
7740
9247
  setBusy(true);
7741
9248
  setProgressError(void 0);
7742
- setProgressStage(worktree ? "inspecting" : void 0);
7743
9249
  setError(void 0);
9250
+ setBatchDone(void 0);
9251
+ if (tickets.length > 1) {
9252
+ const total = tickets.length;
9253
+ setBatchProgress({
9254
+ ticketKey: tickets[0]?.key ?? "",
9255
+ index: 0,
9256
+ total,
9257
+ stage: "inspecting"
9258
+ });
9259
+ setProgressStage("inspecting");
9260
+ prepareMany(branches.root, selected, tickets, (progress) => {
9261
+ setBatchProgress(progress);
9262
+ setProgressStage(progress.stage);
9263
+ }).then(() => {
9264
+ setBatchDone(total);
9265
+ setBatchProgress(void 0);
9266
+ setProgressStage(void 0);
9267
+ setTickets([]);
9268
+ }, (reason) => {
9269
+ setProgressError(reason instanceof Error ? reason.message : String(reason));
9270
+ }).finally(() => {
9271
+ pendingRef.current = false;
9272
+ setBusy(false);
9273
+ });
9274
+ return;
9275
+ }
9276
+ const ticket = tickets[0];
9277
+ setProgressStage(worktree ? "inspecting" : void 0);
7744
9278
  prepare(branches.root, selected, worktree, (stage) => {
7745
9279
  if (worktree) setProgressStage(stage);
7746
9280
  }, ticket).catch((reason) => {
@@ -7765,8 +9299,9 @@ window.__ModuleLoader__.load({
7765
9299
  input.draft,
7766
9300
  portal,
7767
9301
  prepare,
9302
+ prepareMany,
7768
9303
  selected,
7769
- ticket,
9304
+ tickets,
7770
9305
  worktree
7771
9306
  ]);
7772
9307
  useEffect(() => {
@@ -7815,8 +9350,13 @@ window.__ModuleLoader__.load({
7815
9350
  document.removeEventListener("pointerdown", closeOnOutsidePointer);
7816
9351
  };
7817
9352
  }, [ticketMenuOpen]);
7818
- const chooseTicket = (next) => {
7819
- setTicket(next);
9353
+ const toggleTicket = (next) => {
9354
+ setTickets((current) => toggleTicketSelection(current, next));
9355
+ setError(void 0);
9356
+ setBatchDone(void 0);
9357
+ };
9358
+ const clearTickets = () => {
9359
+ setTickets([]);
7820
9360
  setError(void 0);
7821
9361
  setTicketMenuOpen(false);
7822
9362
  };
@@ -7836,6 +9376,7 @@ window.__ModuleLoader__.load({
7836
9376
  worktreeLabel: t("repositoryWorktree"),
7837
9377
  searchPlaceholder: t("repositoryBranchSearch"),
7838
9378
  emptySearchLabel: t("repositoryBranchSearchEmpty"),
9379
+ worktreeLocked: tickets.length > 1,
7839
9380
  onMenuOpenChange: setMenuOpen,
7840
9381
  onSelect: (branch) => {
7841
9382
  setSelected(branch);
@@ -7866,78 +9407,115 @@ window.__ModuleLoader__.load({
7866
9407
  /* @__PURE__ */ jsx(TicketIcon, {}),
7867
9408
  /* @__PURE__ */ jsx("span", {
7868
9409
  style: heroBranchName,
7869
- children: ticket === void 0 ? t("heroTicket") : ticket.key
9410
+ children: tickets.length === 0 ? t("heroTicket") : tickets.length === 1 ? tickets[0]?.key : t("heroTicketCount", { count: tickets.length })
7870
9411
  }),
7871
9412
  /* @__PURE__ */ jsx(IconChevronDownOutline14, {})
7872
9413
  ]
7873
9414
  }), ticketMenuOpen ? /* @__PURE__ */ jsxs("span", {
7874
9415
  role: "menu",
7875
- style: heroBranchMenu,
9416
+ style: {
9417
+ ...heroBranchMenu,
9418
+ ...heroTicketMenu
9419
+ },
7876
9420
  onKeyDown: (event) => {
7877
9421
  if (event.key === "Escape") {
7878
9422
  event.preventDefault();
7879
9423
  setTicketMenuOpen(false);
7880
9424
  }
7881
9425
  },
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,
9426
+ children: [
9427
+ /* @__PURE__ */ jsxs("label", {
9428
+ style: heroBranchSearch,
9429
+ children: [/* @__PURE__ */ jsx(IconSearchOutline16, {}), /* @__PURE__ */ jsx("input", {
9430
+ ref: ticketSearchRef,
9431
+ type: "search",
9432
+ value: ticketQuery,
9433
+ placeholder: t("heroTicketSearch"),
9434
+ "aria-label": t("heroTicketSearch"),
9435
+ style: heroBranchSearchInput,
9436
+ onChange: (event) => {
9437
+ setTicketQuery(event.currentTarget.value);
9438
+ }
9439
+ })]
9440
+ }),
9441
+ tickets.length === 0 ? null : /* @__PURE__ */ jsxs("span", {
9442
+ style: heroTicketChips,
9443
+ children: [tickets.map((item) => /* @__PURE__ */ jsxs("span", {
9444
+ style: heroTicketChip,
9445
+ children: [item.key, /* @__PURE__ */ jsx("button", {
9446
+ type: "button",
9447
+ style: heroTicketChipRemove,
9448
+ "aria-label": t("heroTicketRemove", { key: item.key }),
9449
+ onClick: () => {
9450
+ toggleTicket(item);
9451
+ },
9452
+ children: "×"
9453
+ })]
9454
+ }, item.key)), tickets.length > 1 ? /* @__PURE__ */ jsx("button", {
9455
+ type: "button",
9456
+ style: heroTicketChipsClear,
9457
+ onClick: clearTickets,
7912
9458
  children: t("heroTicketNone")
9459
+ }) : null]
9460
+ }),
9461
+ /* @__PURE__ */ jsx("span", {
9462
+ style: heroBranchList,
9463
+ children: ticketError !== void 0 ? /* @__PURE__ */ jsx("span", {
9464
+ style: heroBranchEmpty,
9465
+ children: ticketError
9466
+ }) : ticketResults === void 0 ? /* @__PURE__ */ jsx("span", {
9467
+ style: heroBranchEmpty,
9468
+ children: t("heroTicketLoading")
9469
+ }) : ticketResults.length === 0 ? /* @__PURE__ */ jsx("span", {
9470
+ style: heroBranchEmpty,
9471
+ children: t("heroTicketEmpty")
9472
+ }) : ticketResults.map((item) => {
9473
+ const picked = tickets.some((entry) => entry.key === item.key);
9474
+ return /* @__PURE__ */ jsxs("button", {
9475
+ type: "button",
9476
+ role: "menuitemcheckbox",
9477
+ "aria-checked": picked,
9478
+ style: heroBranchItem,
9479
+ onClick: () => {
9480
+ toggleTicket(item);
9481
+ },
9482
+ children: [
9483
+ /* @__PURE__ */ jsx("span", {
9484
+ "aria-hidden": "true",
9485
+ style: {
9486
+ ...heroTicketCheckbox,
9487
+ ...picked ? heroWorktreeCheckboxChecked : {}
9488
+ },
9489
+ children: picked ? /* @__PURE__ */ jsx("svg", {
9490
+ viewBox: "0 0 12 12",
9491
+ style: heroWorktreeCheckboxIcon,
9492
+ children: /* @__PURE__ */ jsx("path", {
9493
+ d: "m2.5 6.2 2.1 2.1 4.9-5",
9494
+ fill: "none",
9495
+ stroke: "currentColor",
9496
+ strokeLinecap: "round",
9497
+ strokeLinejoin: "round",
9498
+ strokeWidth: "1.8"
9499
+ })
9500
+ }) : null
9501
+ }),
9502
+ /* @__PURE__ */ jsx("span", {
9503
+ style: heroTicketKey,
9504
+ children: item.key
9505
+ }),
9506
+ /* @__PURE__ */ jsx("span", {
9507
+ style: heroBranchItemName,
9508
+ children: item.summary
9509
+ }),
9510
+ item.status === void 0 ? null : /* @__PURE__ */ jsx("span", {
9511
+ style: heroTicketStatus,
9512
+ children: item.status
9513
+ })
9514
+ ]
9515
+ }, item.key);
7913
9516
  })
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
- })]
9517
+ })
9518
+ ]
7941
9519
  }) : null]
7942
9520
  })
7943
9521
  }),
@@ -7945,14 +9523,21 @@ window.__ModuleLoader__.load({
7945
9523
  role: "alert",
7946
9524
  style: heroRepositoryError,
7947
9525
  children: error
9526
+ }),
9527
+ batchDone === void 0 ? null : /* @__PURE__ */ jsx("span", {
9528
+ role: "status",
9529
+ style: heroRepositoryStatus,
9530
+ children: t("heroBatchStarted", { count: batchDone })
7948
9531
  })
7949
9532
  ] }), progressStage === void 0 || !busy && progressError === void 0 ? null : /* @__PURE__ */ jsx(WorktreeProgressCard, {
7950
9533
  stage: progressStage,
7951
9534
  ...progressError === void 0 ? {} : { error: progressError },
9535
+ ...batchProgress === void 0 ? {} : { context: `${batchProgress.ticketKey} · ${batchProgress.index + 1}/${batchProgress.total}` },
7952
9536
  t,
7953
9537
  onDismiss: () => {
7954
9538
  setProgressStage(void 0);
7955
9539
  setProgressError(void 0);
9540
+ setBatchProgress(void 0);
7956
9541
  }
7957
9542
  })]
7958
9543
  }), portal);
@@ -8042,7 +9627,7 @@ window.__ModuleLoader__.load({
8042
9627
  //#endregion
8043
9628
  //#region src/client/details-resize.ts
8044
9629
  const DETAILS_MIN_WIDTH = 300;
8045
- const DETAILS_DEFAULT_WIDTH = 480;
9630
+ const DETAILS_DEFAULT_WIDTH = 720;
8046
9631
  const DETAILS_MAX_RATIO = .5;
8047
9632
  const DRAG_THRESHOLD = 4;
8048
9633
  function clampDetailsWidth(width, frameWidth) {
@@ -8243,14 +9828,16 @@ window.__ModuleLoader__.load({
8243
9828
  globalSettings: "Claude Code 设置",
8244
9829
  globalSettingsBody: "修改受支持的 Claude Code 与插件设置;其他字段会被保留。",
8245
9830
  globalSettingsLoading: "正在加载全局设置…",
8246
- globalSettingsNewSession: "Output Style 修改仅对新建 Claude 会话生效。",
9831
+ globalSettingsNewSession: "修改仅对新建 Claude 会话生效。",
8247
9832
  globalSettingsError: "全局设置保存失败",
8248
9833
  outputStyle: "Output Style",
8249
9834
  worktreeBranchPrefix: "Worktree 分支前缀",
8250
9835
  worktreeBranchPrefixEffect: "分支前缀修改会应用于之后自动创建的 Worktree 分支;显式指定的完整分支名不会被修改。",
8251
9836
  maxProcessesSetting: "Claude 进程上限",
8252
9837
  idleTimeoutSetting: "闲置回收时长(分钟)",
8253
- limitsSettingEffect: "进程上限在下次启动 Claude 进程时生效;闲置回收时长在下一次回合结束后生效。留空或非法值会被拒绝。",
9838
+ maxProcessesEffect: "进程上限在下次启动 Claude 进程时生效。留空或非法值会被拒绝。",
9839
+ idleTimeoutEffect: "闲置回收时长在下一次回合结束后生效。留空或非法值会被拒绝。",
9840
+ settingHint: "查看说明",
8254
9841
  pluginUpdate: "插件更新",
8255
9842
  pluginUpdateBody: "检查 npm 上的新版本。只有可唯一识别的 npm 安装才能直接更新;本地开发链接不会被替换。",
8256
9843
  updateNotChecked: "尚未检查更新。",
@@ -8288,7 +9875,22 @@ window.__ModuleLoader__.load({
8288
9875
  tasksViewActivity: "查看活动",
8289
9876
  tasksHideActivity: "收起活动",
8290
9877
  tasksRunningCount: "{count} 个运行中任务",
8291
- repositoryRateLimited: "Claude 额度限流中",
9878
+ planUsage: "账号额度",
9879
+ planUsageBody: "当前 Claude 订阅的用量窗口,来自 Claude Code 的 /usage 数据。会话空闲时自动刷新,也可随时手动刷新。",
9880
+ planUsageLoading: "读取中…",
9881
+ planUsageRefresh: "刷新额度",
9882
+ planUsageRefreshing: "刷新中…",
9883
+ planUsageUnavailable: "此账号没有订阅额度(API key、Bedrock、Vertex 等不受套餐限流)。",
9884
+ planUsageNever: "尚未获取额度数据,运行一次对话或点击刷新。",
9885
+ planUsageError: "额度读取失败",
9886
+ planUsageSubscription: "订阅",
9887
+ planUsageUpdated: "更新于 {age}前",
9888
+ planUsageResets: "{age}后重置",
9889
+ planUsageUsed: "已用 {percent}%",
9890
+ planWindow_five_hour: "5 小时",
9891
+ planWindow_seven_day: "全部模型 · 每周",
9892
+ planWindow_seven_day_opus: "Opus · 每周",
9893
+ planWindow_seven_day_sonnet: "Sonnet · 每周",
8292
9894
  tasksTurnRunning: "{count} 个任务执行中",
8293
9895
  tasksTurnCompleted: "{count} 个任务已完成",
8294
9896
  tasksTurnFailed: "{failed} 个任务失败,{completed} 个已完成",
@@ -8436,7 +10038,10 @@ window.__ModuleLoader__.load({
8436
10038
  heroTicketSearch: "搜索 ticket 或输入 key…",
8437
10039
  heroTicketLoading: "搜索中…",
8438
10040
  heroTicketEmpty: "没有匹配的 ticket",
8439
- heroTicketNone: "不关联 ticket",
10041
+ heroTicketNone: "清除已选 ticket",
10042
+ heroTicketRemove: "移除 {key}",
10043
+ heroTicketCount: "{count} 个 ticket",
10044
+ heroBatchStarted: "已为 {count} 个 ticket 各自创建 Worktree 会话并开工",
8440
10045
  heroTicketNotConnected: "请先在 Settings 中连接 Jira",
8441
10046
  heroTicketFailed: "Ticket 搜索失败",
8442
10047
  jiraTitle: "Jira",
@@ -8456,14 +10061,31 @@ window.__ModuleLoader__.load({
8456
10061
  cleanupTitle: "清理已合并分支",
8457
10062
  cleanupDescription: "删除 worktree 与本地分支并移除对应 workspace;普通 checkout 则切回 base 分支并删除已合并分支。",
8458
10063
  cleanupCompleted: "已清理 {branch}",
8459
- overviewOpen: "查看所有 Claude 会话的 Pull Request",
8460
- overviewTitle: "Pull requests",
8461
- overviewBody: "所有 Claude 会话的分支与 PR 状态",
10064
+ overviewOpen: "打开 Claude 会话看板",
10065
+ overviewTitle: "会话看板",
10066
+ overviewBody: "所有 Claude 会话的运行状态、分支与 PR",
8462
10067
  overviewEmpty: "没有正在进行的 Claude 会话",
8463
10068
  overviewNoPr: "无 PR",
8464
10069
  overviewRunning: "运行中",
10070
+ overviewNeedsPermission: "等待权限确认",
10071
+ overviewNeedsAnswer: "等待回答",
10072
+ overviewContextUsage: "上下文 {percentage}%",
8465
10073
  overviewAutoFix: "Auto fix 已开启",
8466
10074
  overviewLoading: "读取中…",
10075
+ askToolbar: "选中文本操作",
10076
+ askTooltip: "针对选中内容追问",
10077
+ copyTooltip: "复制",
10078
+ askCopied: "已复制",
10079
+ askPlaceholder: "针对选中内容提问…",
10080
+ askSubmit: "发送",
10081
+ askStarting: "正在启动 Claude…",
10082
+ askThinking: "思考中…",
10083
+ askThinkingLive: "思考中 ·",
10084
+ askThinkingLabel: "Think ·",
10085
+ askCopyAnswer: "复制回答",
10086
+ askSendToChat: "转发到对话",
10087
+ askClose: "关闭",
10088
+ askFailed: "提问失败",
8467
10089
  reviewCommentAdd: "添加行评论",
8468
10090
  reviewCommentPlaceholder: "请求更改",
8469
10091
  reviewCommentSubmit: "注释",
@@ -8474,7 +10096,13 @@ window.__ModuleLoader__.load({
8474
10096
  reviewCommentsSend: "发送评论",
8475
10097
  reviewCommentsSendDraft: "请处理以上代码评论。",
8476
10098
  reviewCommentOldSide: "旧代码第 {line} 行",
8477
- reviewCommentNewSide: "第 {line} 行"
10099
+ reviewCommentNewSide: "第 {line} 行",
10100
+ reviewCommentOldRange: "旧代码第 {start}–{end} 行",
10101
+ reviewCommentNewRange: "第 {start}–{end} 行",
10102
+ diffUnmodifiedLines: "{count} 行未修改",
10103
+ diffUnmodifiedTail: "文件末尾之前还有未修改的行",
10104
+ diffExpandUp: "向上展开",
10105
+ diffExpandDown: "向下展开"
8478
10106
  };
8479
10107
  const en = {
8480
10108
  nav: "Claude Code",
@@ -8514,14 +10142,16 @@ window.__ModuleLoader__.load({
8514
10142
  globalSettings: "Claude Code settings",
8515
10143
  globalSettingsBody: "Modify supported Claude Code and plugin settings while preserving every other field.",
8516
10144
  globalSettingsLoading: "Loading global settings…",
8517
- globalSettingsNewSession: "Output Style changes apply only to new Claude sessions.",
10145
+ globalSettingsNewSession: "Changes apply only to new Claude sessions.",
8518
10146
  globalSettingsError: "Global settings save failed",
8519
10147
  outputStyle: "Output Style",
8520
10148
  worktreeBranchPrefix: "Worktree branch prefix",
8521
10149
  worktreeBranchPrefixEffect: "Prefix changes apply to subsequently generated Worktree branches. Explicit full branch names remain unchanged.",
8522
10150
  maxProcessesSetting: "Claude process limit",
8523
10151
  idleTimeoutSetting: "Idle timeout (minutes)",
8524
- limitsSettingEffect: "The process limit applies when the next Claude process is admitted; the idle timeout applies after the next completed turn.",
10152
+ maxProcessesEffect: "The process limit applies when the next Claude process is admitted. Blank or invalid values are rejected.",
10153
+ idleTimeoutEffect: "The idle timeout applies after the next completed turn. Blank or invalid values are rejected.",
10154
+ settingHint: "Show details",
8525
10155
  pluginUpdate: "Plugin updates",
8526
10156
  pluginUpdateBody: "Check npm for new releases. Only a uniquely identified npm installation can update in place; local development links are never replaced.",
8527
10157
  updateNotChecked: "Updates have not been checked yet.",
@@ -8559,7 +10189,22 @@ window.__ModuleLoader__.load({
8559
10189
  tasksViewActivity: "View activity",
8560
10190
  tasksHideActivity: "Hide activity",
8561
10191
  tasksRunningCount: "{count} running task(s)",
8562
- repositoryRateLimited: "Rate limited",
10192
+ planUsage: "Plan usage",
10193
+ planUsageBody: "Utilization windows for the signed-in Claude subscription, from Claude Code's /usage data. Refreshes whenever a session goes idle, and on demand.",
10194
+ planUsageLoading: "Loading…",
10195
+ planUsageRefresh: "Refresh usage",
10196
+ planUsageRefreshing: "Refreshing…",
10197
+ planUsageUnavailable: "This account has no plan limits (API key, Bedrock, and Vertex sessions are not rate limited by a subscription).",
10198
+ planUsageNever: "No usage data yet — run a turn or refresh.",
10199
+ planUsageError: "Usage lookup failed",
10200
+ planUsageSubscription: "Subscription",
10201
+ planUsageUpdated: "Updated {age} ago",
10202
+ planUsageResets: "Resets in {age}",
10203
+ planUsageUsed: "{percent}% used",
10204
+ planWindow_five_hour: "5-hour",
10205
+ planWindow_seven_day: "All models · weekly",
10206
+ planWindow_seven_day_opus: "Opus · weekly",
10207
+ planWindow_seven_day_sonnet: "Sonnet · weekly",
8563
10208
  tasksTurnRunning: "{count} task(s) running",
8564
10209
  tasksTurnCompleted: "{count} task(s) completed",
8565
10210
  tasksTurnFailed: "{failed} failed, {completed} completed",
@@ -8707,7 +10352,10 @@ window.__ModuleLoader__.load({
8707
10352
  heroTicketSearch: "Search tickets or enter a key…",
8708
10353
  heroTicketLoading: "Searching…",
8709
10354
  heroTicketEmpty: "No matching tickets",
8710
- heroTicketNone: "No ticket",
10355
+ heroTicketNone: "Clear selected tickets",
10356
+ heroTicketRemove: "Remove {key}",
10357
+ heroTicketCount: "{count} tickets",
10358
+ heroBatchStarted: "Kicked off {count} tickets, each in its own worktree session",
8711
10359
  heroTicketNotConnected: "Connect Jira in Settings first",
8712
10360
  heroTicketFailed: "Ticket search failed",
8713
10361
  jiraTitle: "Jira",
@@ -8727,14 +10375,31 @@ window.__ModuleLoader__.load({
8727
10375
  cleanupTitle: "Clean up merged branch",
8728
10376
  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
10377
  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",
10378
+ overviewOpen: "Open the Claude session board",
10379
+ overviewTitle: "Session board",
10380
+ overviewBody: "Run state, branch, and pull request across Claude sessions",
8733
10381
  overviewEmpty: "No active Claude sessions",
8734
10382
  overviewNoPr: "No PR",
8735
10383
  overviewRunning: "Running",
10384
+ overviewNeedsPermission: "Awaiting approval",
10385
+ overviewNeedsAnswer: "Awaiting answer",
10386
+ overviewContextUsage: "Context {percentage}%",
8736
10387
  overviewAutoFix: "Auto fix on",
8737
10388
  overviewLoading: "Loading…",
10389
+ askToolbar: "Selection actions",
10390
+ askTooltip: "Ask about this",
10391
+ copyTooltip: "Copy",
10392
+ askCopied: "Copied",
10393
+ askPlaceholder: "Ask about the selected text…",
10394
+ askSubmit: "Ask",
10395
+ askStarting: "Starting Claude…",
10396
+ askThinking: "Thinking…",
10397
+ askThinkingLive: "Thinking ·",
10398
+ askThinkingLabel: "Think ·",
10399
+ askCopyAnswer: "Copy answer",
10400
+ askSendToChat: "Send to chat",
10401
+ askClose: "Close",
10402
+ askFailed: "The question could not be answered",
8738
10403
  reviewCommentAdd: "Add line comment",
8739
10404
  reviewCommentPlaceholder: "Request changes",
8740
10405
  reviewCommentSubmit: "Comment",
@@ -8745,7 +10410,13 @@ window.__ModuleLoader__.load({
8745
10410
  reviewCommentsSend: "Send comments",
8746
10411
  reviewCommentsSendDraft: "Please address the attached review comments.",
8747
10412
  reviewCommentOldSide: "Old line {line}",
8748
- reviewCommentNewSide: "Line {line}"
10413
+ reviewCommentNewSide: "Line {line}",
10414
+ reviewCommentOldRange: "Old lines {start}–{end}",
10415
+ reviewCommentNewRange: "Lines {start}–{end}",
10416
+ diffUnmodifiedLines: "{count} unmodified lines",
10417
+ diffUnmodifiedTail: "More unmodified lines below",
10418
+ diffExpandUp: "Expand up",
10419
+ diffExpandDown: "Expand down"
8749
10420
  };
8750
10421
  //#endregion
8751
10422
  //#region src/client/index.tsx
@@ -8880,7 +10551,9 @@ window.__ModuleLoader__.load({
8880
10551
  sessions.open(id);
8881
10552
  },
8882
10553
  loadStatus: loadRepositoryStatusFor,
8883
- sessions: sessions.list
10554
+ sessions: sessions.list,
10555
+ ...workspaces === void 0 ? {} : { workspaces: workspaces.list },
10556
+ projectionFor: (id) => projections.source(id)
8884
10557
  })
8885
10558
  }, ClaudePullRequestsPanel);
8886
10559
  } catch {
@@ -9014,6 +10687,22 @@ window.__ModuleLoader__.load({
9014
10687
  };
9015
10688
  }
9016
10689
  }, ClaudeRepositoryStatus));
10690
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
10691
+ name: "shell.overlay",
10692
+ id: "claude-selection-ask",
10693
+ locale: namespace
10694
+ }, () => /* @__PURE__ */ jsx(ClaudeSelectionAsk, {
10695
+ t,
10696
+ currentSessionId: () => sessions?.list.getSnapshot().current,
10697
+ ownsSession: (sessionId) => projections.source(sessionId).getSnapshot().owned,
10698
+ ...sessions === void 0 || conversation === void 0 ? {} : { insertIntoChat: (sessionId, text) => {
10699
+ const scope = sessions.scope(sessionId);
10700
+ if (scope === void 0) return;
10701
+ const input = conversation.input.for(scope);
10702
+ const current = input.state.getSnapshot().draft;
10703
+ input.setDraft(current.trim() === "" ? text : `${current}\n\n${text}`);
10704
+ } }
10705
+ })));
9017
10706
  if (sessions !== void 0 && conversation !== void 0) ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
9018
10707
  name: "conversation.input.dock",
9019
10708
  id: "queue",
@@ -9077,6 +10766,52 @@ window.__ModuleLoader__.load({
9077
10766
  targetInput.submit();
9078
10767
  sourceInput.setDraft("");
9079
10768
  for (const imageId of imageIds) sourceInput.removeImage(imageId);
10769
+ },
10770
+ prepareMany: async (cwd, branch, tickets, onProgress) => {
10771
+ const sourceScope = sessions.scope(sourceSessionId);
10772
+ if (sourceScope === void 0) throw new Error(t("repositorySessionUnavailable"));
10773
+ const sourceInput = conversation.input.for(sourceScope);
10774
+ const rawDraft = sourceInput.state.getSnapshot().draft;
10775
+ const failures = [];
10776
+ for (const [index, ticket] of tickets.entries()) {
10777
+ const report = (stage) => {
10778
+ onProgress({
10779
+ ticketKey: ticket.key,
10780
+ index,
10781
+ total: tickets.length,
10782
+ stage
10783
+ });
10784
+ };
10785
+ try {
10786
+ report("inspecting");
10787
+ const prepared = await prepareRepository(cwd, branch, true, ticket.key, report);
10788
+ assignJiraTicket(ticket.key).catch((reason) => {
10789
+ console.warn(`dsh-claude: could not assign ${ticket.key}: ${reason instanceof Error ? reason.message : String(reason)}`);
10790
+ });
10791
+ report("creating-workspace");
10792
+ const workspace = await workspaces.create({ path: prepared.path });
10793
+ report("starting-session");
10794
+ const targetSessionId = await workspaces.connectWorkspace(workspace.workspaceId);
10795
+ const targetScope = sessions.scope(targetSessionId);
10796
+ if (targetScope === void 0) throw new Error(t("repositorySessionUnavailable"));
10797
+ const presetResponse = await connection.api.agentPresets.select({
10798
+ sessionId: targetSessionId,
10799
+ agentPreset: "claude"
10800
+ });
10801
+ if (!presetResponse.result.ok) throw new Error(presetResponse.result.error.message);
10802
+ sessions.noteAgentPreset(targetSessionId, presetResponse.result.value.agentPreset);
10803
+ const targetInput = conversation.input.for(targetScope);
10804
+ report("transferring-draft");
10805
+ targetInput.setDraft(rawDraft.trim() === "" ? ticketPrompt(ticket) : `${rawDraft.trimEnd()}\n\n${ticketContext(ticket)}`);
10806
+ if (prepared.leaseId !== void 0) await bindRepositoryLease(prepared.leaseId, targetSessionId);
10807
+ report("submitting");
10808
+ targetInput.submit();
10809
+ } catch (cause) {
10810
+ failures.push(`${ticket.key}: ${cause instanceof Error ? cause.message : String(cause)}`);
10811
+ }
10812
+ }
10813
+ if (failures.length < tickets.length) sourceInput.setDraft("");
10814
+ if (failures.length > 0) throw new Error(failures.join(" · "));
9080
10815
  }
9081
10816
  })
9082
10817
  }, ClaudeHeroRepositoryControls));