@norman-else/dsh-claude 0.1.47 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  module.exports;
6
6
  var { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } = require("react");
7
- var { Button, DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconFullscreenOutline16, IconListPenOutline16, IconLoadingOutline16, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconSearchOutline16, IconSendOutline14, IconSparkle16, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Toast, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
7
+ var { Button, DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconListPenOutline16, IconLoadingOutline16, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconSearchOutline16, IconSendOutline14, IconSparkle16, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Toast, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
8
8
  var { Fragment: Fragment$1, jsx, jsxs } = require("react/jsx-runtime");
9
9
  var { createPortal } = require("react-dom");
10
10
  /** Claude's subagent dispatch tools; rendered as plugin-owned group cards
@@ -1957,14 +1957,59 @@ window.__ModuleLoader__.load({
1957
1957
  borderColor: "color-mix(in srgb, #a78bfa 35%, transparent)",
1958
1958
  background: "color-mix(in srgb, #a78bfa 5%, var(--dsw-alias-bg-layer-1))"
1959
1959
  };
1960
+ /** The linked bars as one stack, the fold control floating centred above. */
1961
+ const linkedStack = { position: "relative" };
1962
+ /** Two chevron glyphs stacked into one double chevron: 14px tall in all,
1963
+ * the second pulled up over the first. */
1964
+ const linkedFoldGlyph = {
1965
+ display: "flex",
1966
+ flexDirection: "column",
1967
+ alignItems: "center",
1968
+ height: 14,
1969
+ marginTop: 2
1970
+ };
1971
+ const linkedFoldGlyphClass = "dshClaudeLinkedFoldGlyph";
1972
+ /** The second chevron rides 7px up over the first. */
1973
+ const linkedFoldGlyphCss = `
1974
+ .${linkedFoldGlyphClass} > svg + svg { margin-top: -7px; }
1975
+ `;
1976
+ /** The fold control, drawn to the Host's jump-to-latest recipe exactly: a
1977
+ * 34px circle on the floating fill, no border of its own -- the stroke is
1978
+ * the elevation's, set through the same variable the Host sets -- and the
1979
+ * circle asked for by name, since the Host's theme rounds as squircles.
1980
+ * Where it sits comes from the component. Holds only the double chevron;
1981
+ * the count lives in its tooltip. */
1982
+ const linkedFoldChip = {
1983
+ position: "absolute",
1984
+ zIndex: 8,
1985
+ boxSizing: "border-box",
1986
+ display: "flex",
1987
+ alignItems: "center",
1988
+ justifyContent: "center",
1989
+ width: 34,
1990
+ height: 34,
1991
+ padding: 0,
1992
+ border: 0,
1993
+ borderRadius: 100,
1994
+ cornerShape: "round",
1995
+ "--dsw-elevation-stroke-color": "var(--dsw-alias-border-l3)",
1996
+ background: "var(--dsw-alias-button-floating-fill)",
1997
+ color: "var(--dsw-alias-label-primary)",
1998
+ boxShadow: "var(--dsw-elevation-panel)",
1999
+ font: "inherit",
2000
+ cursor: "pointer",
2001
+ transition: "right .15s ease"
2002
+ };
2003
+ /** The folded count beside the chevrons. */
1960
2004
  /** A checkout the session wrote into besides its own: lighter, dashed, and
1961
- * stacked above the session bar so it reads as attached rather than primary. */
2005
+ * stacked above the session bar so it reads as attached rather than primary.
2006
+ * It keeps the bar's opaque fill: the dock floats over the end of the
2007
+ * transcript, and a transparent bar let the prose show through. */
1962
2008
  const repositoryBarLinked = {
1963
2009
  marginBottom: 6,
1964
2010
  minHeight: 34,
1965
2011
  padding: "5px 11px",
1966
2012
  borderStyle: "dashed",
1967
- background: "transparent",
1968
2013
  boxShadow: "none",
1969
2014
  color: "var(--dsw-alias-label-secondary)"
1970
2015
  };
@@ -2100,9 +2145,11 @@ window.__ModuleLoader__.load({
2100
2145
  color: "var(--dsw-alias-label-tertiary)",
2101
2146
  fontWeight: 500
2102
2147
  };
2148
+ /** The repository name reads whole while there is room; it is the first
2149
+ * thing to give way when the controls need it, and never wider than its text. */
2103
2150
  const repositoryRemote = {
2104
2151
  minWidth: 0,
2105
- maxWidth: 116,
2152
+ flex: "0 1 auto",
2106
2153
  overflow: "hidden",
2107
2154
  color: "var(--dsw-alias-label-tertiary)",
2108
2155
  fontSize: 12,
@@ -2211,28 +2258,38 @@ window.__ModuleLoader__.load({
2211
2258
  background: "transparent",
2212
2259
  color: "var(--dsw-alias-label-tertiary)"
2213
2260
  };
2214
- const repositoryAutoFix = {
2215
- width: 26,
2216
- minHeight: 26,
2217
- flex: "none",
2218
- display: "inline-flex",
2219
- alignItems: "center",
2220
- justifyContent: "center",
2221
- padding: 0,
2222
- border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
2223
- borderRadius: 7,
2224
- outline: "none",
2225
- background: "var(--dsw-alias-bg-layer-2)",
2226
- color: "var(--dsw-alias-label-tertiary)",
2227
- font: "inherit",
2228
- lineHeight: "18px",
2229
- cursor: "pointer"
2230
- };
2231
- const repositoryAutoFixActive = {
2232
- borderColor: "color-mix(in srgb, var(--dsw-static-blue-450) 45%, transparent)",
2233
- background: "color-mix(in srgb, var(--dsw-static-blue-450) 12%, transparent)",
2234
- color: "var(--dsw-static-blue-450)"
2235
- };
2261
+ const repositoryAutoFixClass = "dshClaudeAutoFix";
2262
+ /** The switch is styled entirely from a stylesheet: its base look, its on
2263
+ * state off `aria-checked`, its ring off `:focus-visible`. Nothing is set
2264
+ * inline, because an inline border, background or colour would outrank the
2265
+ * on-state rule and the switch would toggle without ever looking switched. */
2266
+ const repositoryAutoFixCss = `
2267
+ .${repositoryAutoFixClass} {
2268
+ width: 26px;
2269
+ min-height: 26px;
2270
+ flex: none;
2271
+ display: inline-flex;
2272
+ align-items: center;
2273
+ justify-content: center;
2274
+ padding: 0;
2275
+ border: 1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent));
2276
+ border-radius: 7px;
2277
+ outline: none;
2278
+ background: var(--dsw-alias-bg-layer-2);
2279
+ color: var(--dsw-alias-label-tertiary);
2280
+ font: inherit;
2281
+ line-height: 18px;
2282
+ cursor: pointer;
2283
+ }
2284
+ .${repositoryAutoFixClass}:focus-visible {
2285
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-static-blue-450) 28%, transparent);
2286
+ }
2287
+ .${repositoryAutoFixClass}[aria-checked="true"] {
2288
+ border-color: var(--dsw-static-blue-450);
2289
+ background: var(--dsw-static-blue-450);
2290
+ color: var(--dsw-alias-label-on-accent, #fff);
2291
+ }
2292
+ `;
2236
2293
  const repositoryUpdateTrigger = {
2237
2294
  flex: "none",
2238
2295
  display: "inline-flex",
@@ -3234,11 +3291,6 @@ window.__ModuleLoader__.load({
3234
3291
  background: "var(--dsw-alias-bg-layer-1)",
3235
3292
  boxShadow: "0 4px 16px color-mix(in srgb, #000 12%, transparent)"
3236
3293
  };
3237
- const diffPanelMaximized = {
3238
- width: "100%",
3239
- height: "100%",
3240
- margin: 0
3241
- };
3242
3294
  const diffHeader = {
3243
3295
  boxSizing: "border-box",
3244
3296
  height: 49,
@@ -4453,7 +4505,7 @@ window.__ModuleLoader__.load({
4453
4505
  "merge",
4454
4506
  "cherry-pick",
4455
4507
  "revert"
4456
- ].includes(String(repository.operation)) || repository.conflicts !== void 0 && (!Array.isArray(repository.conflicts) || repository.conflicts.length > MAX_CONFLICT_PATHS || repository.conflicts.some((path) => typeof path !== "string" || path.length > MAX_REPOSITORY_TEXT_CHARS))) return false;
4508
+ ].includes(String(repository.operation)) || repository.pullRequestOnly !== void 0 && typeof repository.pullRequestOnly !== "boolean" || repository.conflicts !== void 0 && (!Array.isArray(repository.conflicts) || repository.conflicts.length > MAX_CONFLICT_PATHS || repository.conflicts.some((path) => typeof path !== "string" || path.length > MAX_REPOSITORY_TEXT_CHARS))) return false;
4457
4509
  if (repository.diff !== void 0) {
4458
4510
  const diff = record$7(repository.diff);
4459
4511
  if (diff === void 0 || !nonNegativeInteger(diff.additions) || !nonNegativeInteger(diff.deletions) || !nonNegativeInteger(diff.files) || typeof diff.truncated !== "boolean" || diff.elided !== void 0 && !(Array.isArray(diff.elided) && diff.elided.length <= 1e3 && diff.elided.every((item) => typeof item === "string")) || diff.patch !== void 0 && (typeof diff.patch !== "string" || diff.patch.length > MAX_DIFF_CHARS)) return false;
@@ -6072,8 +6124,8 @@ window.__ModuleLoader__.load({
6072
6124
  }
6073
6125
  return users;
6074
6126
  }
6075
- async function loadFailingChecks(sessionId, pullNumber, signal) {
6076
- const body = await loadJson("/checks", sessionId, pullNumber, signal);
6127
+ async function loadFailingChecks(sessionId, pullNumber, signal, root) {
6128
+ const body = await loadJson("/checks", sessionId, pullNumber, signal, root);
6077
6129
  if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
6078
6130
  const checks = [];
6079
6131
  for (const item of body.checks) {
@@ -7602,18 +7654,6 @@ window.__ModuleLoader__.load({
7602
7654
  toolUseId: key.slice(cut + 1)
7603
7655
  };
7604
7656
  }
7605
- /** Restore-from-maximized: four corners pulling inward. Mirrors the diff
7606
- * panel's own, which the primitives set has no counterpart for. */
7607
- function RestorePanelIcon$1() {
7608
- return /* @__PURE__ */ jsx("svg", {
7609
- width: "16",
7610
- height: "16",
7611
- viewBox: "0 0 16 16",
7612
- fill: "currentColor",
7613
- "aria-hidden": "true",
7614
- children: /* @__PURE__ */ jsx("path", { d: "M1.5 5h3V2h1.4v4.4H1.5V5Zm9.9-3h1.4v3h3v1.4h-4.4V2ZM1.5 9.6h4.4V14H4.5v-3h-3V9.6Zm9.9 0h4.4V11h-3v3h-1.4V9.6Z" })
7615
- });
7616
- }
7617
7657
  /** Card and rows reproduce the primitives' menu surface, like the session
7618
7658
  * menu next door: r7, inverted hairline, shadow-lv3, 2px inset. */
7619
7659
  const PICKER_CSS = [
@@ -7662,7 +7702,7 @@ window.__ModuleLoader__.load({
7662
7702
  /** The plan behind an `ExitPlanMode` approval, as the document it was written
7663
7703
  * as. The decision itself stays with the Host's approval dialog; this panel
7664
7704
  * is where the plan is actually read, under the reader's own prose palette. */
7665
- function ClaudePlanPanel({ useClaudeProjection, t, sessionId, closeDetails, maximized, toggleMaximized, submitChanges = sendPlanForChanges }) {
7705
+ function ClaudePlanPanel({ useClaudeProjection, t, sessionId, closeDetails, submitChanges = sendPlanForChanges }) {
7666
7706
  const markdownLabels = useClaudeMarkdownLabels(t);
7667
7707
  const owned = useClaudeProjection((projection) => projection.owned);
7668
7708
  const reviews = planReviews(useClaudeProjection((projection) => projection.activities));
@@ -7754,10 +7794,7 @@ window.__ModuleLoader__.load({
7754
7794
  });
7755
7795
  return /* @__PURE__ */ jsxs("div", {
7756
7796
  className: detailsCardClass,
7757
- style: {
7758
- ...tasksPanel,
7759
- ...maximized ? diffPanelMaximized : {}
7760
- },
7797
+ style: tasksPanel,
7761
7798
  children: [
7762
7799
  /* @__PURE__ */ jsxs("style", {
7763
7800
  "data-dsh-claude-panel-icon-styles": true,
@@ -7833,21 +7870,15 @@ window.__ModuleLoader__.load({
7833
7870
  style: badge(review.state),
7834
7871
  children: t(STATE_LABEL[review.state])
7835
7872
  })]
7836
- }), /* @__PURE__ */ jsxs("div", {
7873
+ }), /* @__PURE__ */ jsx("div", {
7837
7874
  style: planHeaderEnd,
7838
- children: [/* @__PURE__ */ jsx("button", {
7839
- type: "button",
7840
- className: panelIconButtonClass,
7841
- "aria-label": maximized ? t("planRestore") : t("planMaximize"),
7842
- onClick: toggleMaximized,
7843
- children: maximized ? /* @__PURE__ */ jsx(RestorePanelIcon$1, {}) : /* @__PURE__ */ jsx(IconFullscreenOutline16, {})
7844
- }), /* @__PURE__ */ jsx("button", {
7875
+ children: /* @__PURE__ */ jsx("button", {
7845
7876
  type: "button",
7846
7877
  className: panelIconButtonClass,
7847
7878
  "aria-label": t("planClose"),
7848
7879
  onClick: closeDetails,
7849
7880
  children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
7850
- })]
7881
+ })
7851
7882
  })]
7852
7883
  }),
7853
7884
  /* @__PURE__ */ jsx("div", {
@@ -8228,10 +8259,16 @@ window.__ModuleLoader__.load({
8228
8259
  sessionId
8229
8260
  } });
8230
8261
  }
8231
- async function cleanupMergedRepository(path, baseBranch) {
8262
+ /** `branch` names the merged branch when the checkout is no longer on it (a
8263
+ * linked pull request whose clone is back on base). Without a pull request
8264
+ * there is no base, and `requirePushed` has the Host refuse a branch whose
8265
+ * commits are on no remote. */
8266
+ async function cleanupMergedRepository(path, baseBranch, branch, requirePushed) {
8232
8267
  const body = await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/cleanup`, "remote", void 0, { json: {
8233
8268
  path,
8234
- baseBranch
8269
+ ...baseBranch === void 0 ? {} : { baseBranch },
8270
+ ...branch === void 0 ? {} : { branch },
8271
+ ...requirePushed === true ? { requirePushed: true } : {}
8235
8272
  } });
8236
8273
  if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
8237
8274
  return body;
@@ -8599,24 +8636,32 @@ window.__ModuleLoader__.load({
8599
8636
  }
8600
8637
  /** Watches an open pull request and hands new review comments and failing
8601
8638
  * CI runs to Claude automatically until the user switches it off. */
8602
- function AutoFixControl({ sessionId, repository, running, t, submitPrompt }) {
8639
+ /** A prompt about a linked checkout says which one, so Claude does not
8640
+ * apply it to the session's own. */
8641
+ function linkedPreamble(repository, root, t) {
8642
+ return root === void 0 ? "" : `${t("linkedRepositoryPrompt", {
8643
+ root,
8644
+ branch: repository.branch ?? ""
8645
+ })}\n\n`;
8646
+ }
8647
+ function AutoFixControl({ sessionId, repository, root, running, t, submitPrompt }) {
8603
8648
  const pullRequest = repository.pullRequest;
8604
8649
  const open = pullRequest?.state === "open";
8605
8650
  const number = pullRequest?.number;
8606
8651
  const checks = pullRequest?.checks;
8607
- const [enabled, setEnabled] = useState(() => autoFixEnabled(sessionId));
8608
- const [focused, setFocused] = useState(false);
8652
+ const scope = root === void 0 ? sessionId : `${sessionId}#${root}`;
8653
+ const [enabled, setEnabled] = useState(() => autoFixEnabled(scope));
8609
8654
  useEffect(() => {
8610
- setEnabled(autoFixEnabled(sessionId));
8611
- }, [sessionId]);
8655
+ setEnabled(autoFixEnabled(scope));
8656
+ }, [scope]);
8612
8657
  useEffect(() => {
8613
8658
  if (!enabled || !open || running || number === void 0 || submitPrompt === void 0) return;
8614
8659
  let cancelled = false;
8615
8660
  const tick = async () => {
8616
- const [comments, failing] = await Promise.all([loadPullRequestThreads(sessionId, number).catch(() => []), checks === "failing" ? loadFailingChecks(sessionId, number).catch(() => []) : Promise.resolve([])]);
8661
+ const [comments, failing] = await Promise.all([loadPullRequestThreads(sessionId, number, void 0, root).catch(() => []), checks === "failing" ? loadFailingChecks(sessionId, number, void 0, root).catch(() => []) : Promise.resolve([])]);
8617
8662
  if (cancelled) return;
8618
- const plan = planAutoFix(autoFixMemory(sessionId), comments, failing);
8619
- if (plan.prompt !== void 0 && submitPrompt(plan.prompt, "idle")) rememberAutoFix(sessionId, plan.memory);
8663
+ const plan = planAutoFix(autoFixMemory(scope), comments, failing);
8664
+ if (plan.prompt !== void 0 && submitPrompt(`${linkedPreamble(repository, root, t)}${plan.prompt}`, "idle")) rememberAutoFix(scope, plan.memory);
8620
8665
  };
8621
8666
  tick();
8622
8667
  const timer = setInterval(() => {
@@ -8631,15 +8676,19 @@ window.__ModuleLoader__.load({
8631
8676
  enabled,
8632
8677
  number,
8633
8678
  open,
8679
+ repository,
8680
+ root,
8634
8681
  running,
8682
+ scope,
8635
8683
  sessionId,
8636
- submitPrompt
8684
+ submitPrompt,
8685
+ t
8637
8686
  ]);
8638
8687
  if (!open || submitPrompt === void 0) return null;
8639
8688
  const toggle = () => {
8640
8689
  const next = !enabled;
8641
8690
  setEnabled(next);
8642
- setAutoFixEnabled(sessionId, next);
8691
+ setAutoFixEnabled(scope, next);
8643
8692
  };
8644
8693
  return /* @__PURE__ */ jsx(Tooltip, {
8645
8694
  label: `${t("autoFixLabel")} · ${t("autoFixTitle")}`,
@@ -8649,19 +8698,9 @@ window.__ModuleLoader__.load({
8649
8698
  children: /* @__PURE__ */ jsx("button", {
8650
8699
  type: "button",
8651
8700
  role: "switch",
8701
+ className: repositoryAutoFixClass,
8652
8702
  "aria-checked": enabled,
8653
8703
  "aria-label": t("autoFixLabel"),
8654
- style: {
8655
- ...repositoryAutoFix,
8656
- ...enabled ? repositoryAutoFixActive : {},
8657
- ...focused ? heroWorktreeToggleFocused : {}
8658
- },
8659
- onFocus: () => {
8660
- setFocused(true);
8661
- },
8662
- onBlur: () => {
8663
- setFocused(false);
8664
- },
8665
8704
  onClick: (event) => {
8666
8705
  toggle();
8667
8706
  event.currentTarget.blur();
@@ -8681,7 +8720,8 @@ window.__ModuleLoader__.load({
8681
8720
  })
8682
8721
  });
8683
8722
  }
8684
- function FailingChecksControl({ sessionId, pullNumber, t, submitPrompt }) {
8723
+ function FailingChecksControl({ sessionId, repository, root, t, submitPrompt }) {
8724
+ const pullNumber = repository.pullRequest.number;
8685
8725
  const [open, setOpen] = useState(false);
8686
8726
  const [loading, setLoading] = useState(false);
8687
8727
  const [checks, setChecks] = useState([]);
@@ -8708,7 +8748,7 @@ window.__ModuleLoader__.load({
8708
8748
  controller.current = aborter;
8709
8749
  setLoading(true);
8710
8750
  setError(void 0);
8711
- loadFailingChecks(sessionId, pullNumber, aborter.signal).then((value) => {
8751
+ loadFailingChecks(sessionId, pullNumber, aborter.signal, root).then((value) => {
8712
8752
  setChecks(value);
8713
8753
  setLoading(false);
8714
8754
  }, (reason) => {
@@ -8774,7 +8814,7 @@ window.__ModuleLoader__.load({
8774
8814
  type: "button",
8775
8815
  style: repositoryChecksFix,
8776
8816
  onClick: () => {
8777
- submitPrompt(composeChecksPrompt(checks));
8817
+ submitPrompt(`${linkedPreamble(repository, root, t)}${composeChecksPrompt(checks)}`);
8778
8818
  setOpen(false);
8779
8819
  },
8780
8820
  children: t("checksCardFix")
@@ -8787,7 +8827,7 @@ window.__ModuleLoader__.load({
8787
8827
  * hides every other control on this bar, and the update-branch dialog that
8788
8828
  * started it takes its conflict list along when it closes -- so this one is
8789
8829
  * mounted from repository state instead, and survives being dismissed. */
8790
- function ConflictControl({ sessionId, repository, t, report, submitPrompt }) {
8830
+ function ConflictControl({ sessionId, repository, root, t, report, submitPrompt }) {
8791
8831
  const [dialog, setDialog] = useState();
8792
8832
  const operation = repository.operation;
8793
8833
  if (operation === void 0) return null;
@@ -8809,7 +8849,7 @@ window.__ModuleLoader__.load({
8809
8849
  message: "",
8810
8850
  includeUnstaged: false,
8811
8851
  push: dialog.push
8812
- }).then((result) => {
8852
+ }, root).then((result) => {
8813
8853
  if (result.conflicts !== void 0 && result.conflicts.length > 0) {
8814
8854
  setDialog({
8815
8855
  ...pending,
@@ -8911,7 +8951,7 @@ window.__ModuleLoader__.load({
8911
8951
  type: "button",
8912
8952
  style: diffModalConflictResolve,
8913
8953
  onClick: () => {
8914
- submitPrompt(composeConflictsPrompt(conflicts, operation, repository.pullRequest?.baseBranch));
8954
+ submitPrompt(`${linkedPreamble(repository, root, t)}${composeConflictsPrompt(conflicts, operation, repository.pullRequest?.baseBranch)}`);
8915
8955
  closeDialog();
8916
8956
  },
8917
8957
  children: t("conflictResolve")
@@ -8954,7 +8994,7 @@ window.__ModuleLoader__.load({
8954
8994
  if (pullRequest?.baseBranch === void 0) return false;
8955
8995
  return dialogOpen || pullRequest.state === "open" && repository.detached !== true && repository.dirty !== true && (repository.baseBehind ?? 0) > 0;
8956
8996
  }
8957
- function UpdateBranchControl({ sessionId, repository, t, report, submitPrompt }) {
8997
+ function UpdateBranchControl({ sessionId, repository, root, t, report, submitPrompt }) {
8958
8998
  const [dialog, setDialog] = useState();
8959
8999
  const [method, setMethod] = useState("rebase");
8960
9000
  const controller = useRef();
@@ -8972,7 +9012,7 @@ window.__ModuleLoader__.load({
8972
9012
  });
8973
9013
  const aborter = new AbortController();
8974
9014
  controller.current = aborter;
8975
- loadRepositoryActionPreview(sessionId, aborter.signal).then((preview) => {
9015
+ loadRepositoryActionPreview(sessionId, aborter.signal, root).then((preview) => {
8976
9016
  setDialog({
8977
9017
  loading: false,
8978
9018
  submitting: false,
@@ -9006,7 +9046,7 @@ window.__ModuleLoader__.load({
9006
9046
  includeUnstaged: false,
9007
9047
  baseBranch: base,
9008
9048
  mergeMethod: method
9009
- }).then((result) => {
9049
+ }, root).then((result) => {
9010
9050
  if (result.conflicts !== void 0 && result.conflicts.length > 0) {
9011
9051
  setDialog({
9012
9052
  ...pending,
@@ -9117,7 +9157,7 @@ window.__ModuleLoader__.load({
9117
9157
  type: "button",
9118
9158
  style: diffModalConflictResolve,
9119
9159
  onClick: () => {
9120
- submitPrompt(composeConflictsPrompt(dialog.conflicts ?? [], method, base));
9160
+ submitPrompt(`${linkedPreamble(repository, root, t)}${composeConflictsPrompt(dialog.conflicts ?? [], method, base)}`);
9121
9161
  closeDialog();
9122
9162
  },
9123
9163
  children: t("diffUpdateBranchResolve")
@@ -9140,14 +9180,17 @@ window.__ModuleLoader__.load({
9140
9180
  const [dialog, setDialog] = useState();
9141
9181
  const pullRequest = repository.pullRequest;
9142
9182
  const base = pullRequest?.baseBranch;
9143
- if (pullRequest?.state !== "merged" || base === void 0 || repository.root === void 0 || repository.detached === true) return null;
9183
+ const merged = pullRequest?.state === "merged" && base !== void 0;
9184
+ const unmerged = pullRequest === void 0 && repository.worktree === true && repository.dirty !== true;
9185
+ if (!merged && !unmerged || repository.root === void 0 || repository.detached === true) return null;
9144
9186
  const root = repository.root;
9145
9187
  const closeDialog = () => {
9146
9188
  if (dialog?.submitting !== true) setDialog(void 0);
9147
9189
  };
9148
9190
  const confirm = () => {
9149
9191
  setDialog({ submitting: true });
9150
- cleanupMergedRepository(root, base).then(async (result) => {
9192
+ const named = repository.pullRequestOnly === true || unmerged ? repository.branch : void 0;
9193
+ cleanupMergedRepository(root, base, named, unmerged).then(async (result) => {
9151
9194
  report(t("cleanupCompleted", { branch: result.branch }));
9152
9195
  setDialog(void 0);
9153
9196
  if (result.mode === "worktree" && deleteWorkspace !== void 0) await deleteWorkspace();
@@ -9179,7 +9222,7 @@ window.__ModuleLoader__.load({
9179
9222
  onClose: closeDialog,
9180
9223
  title: t("cleanupTitle"),
9181
9224
  closeLabel: t("diffCancel"),
9182
- description: t("cleanupDescription"),
9225
+ description: t(unmerged ? "cleanupDescriptionUnmerged" : "cleanupDescription"),
9183
9226
  footer: /* @__PURE__ */ jsxs("div", {
9184
9227
  style: diffModalFooter,
9185
9228
  children: [/* @__PURE__ */ jsx("button", {
@@ -9208,11 +9251,7 @@ window.__ModuleLoader__.load({
9208
9251
  style: diffModalMeta,
9209
9252
  children: [/* @__PURE__ */ jsxs("strong", {
9210
9253
  style: diffModalMetaText,
9211
- children: [
9212
- repository.branch ?? t("repositoryUnknownBranch"),
9213
- " → ",
9214
- base
9215
- ]
9254
+ children: [repository.branch ?? t("repositoryUnknownBranch"), base === void 0 ? "" : ` → ${base}`]
9216
9255
  }), /* @__PURE__ */ jsx("span", {
9217
9256
  style: diffModalFileState,
9218
9257
  children: repository.worktree === true ? t("repositoryWorktree") : t("repositoryLocal")
@@ -9231,7 +9270,7 @@ window.__ModuleLoader__.load({
9231
9270
  "squash",
9232
9271
  "rebase"
9233
9272
  ];
9234
- function MergePullRequestControl({ sessionId, repository, t, report }) {
9273
+ function MergePullRequestControl({ sessionId, repository, root, t, report }) {
9235
9274
  const [menuOpen, setMenuOpen] = useState(false);
9236
9275
  const [dialog, setDialog] = useState();
9237
9276
  const controller = useRef();
@@ -9249,7 +9288,7 @@ window.__ModuleLoader__.load({
9249
9288
  });
9250
9289
  const aborter = new AbortController();
9251
9290
  controller.current = aborter;
9252
- loadRepositoryActionPreview(sessionId, aborter.signal).then((preview) => {
9291
+ loadRepositoryActionPreview(sessionId, aborter.signal, root).then((preview) => {
9253
9292
  setDialog((current) => ({
9254
9293
  method,
9255
9294
  admin: current?.admin ?? false,
@@ -9286,8 +9325,9 @@ window.__ModuleLoader__.load({
9286
9325
  message: "",
9287
9326
  includeUnstaged: false,
9288
9327
  mergeMethod: dialog.method,
9289
- ...dialog.admin ? { admin: true } : {}
9290
- }).then(() => {
9328
+ ...dialog.admin ? { admin: true } : {},
9329
+ ...root === void 0 ? {} : { pullNumber: pullRequest.number }
9330
+ }, root).then(() => {
9291
9331
  report(t("diffMergeCompleted", { number: pullRequest.number }));
9292
9332
  setDialog(void 0);
9293
9333
  }, (error) => {
@@ -9415,12 +9455,121 @@ window.__ModuleLoader__.load({
9415
9455
  children: [/* @__PURE__ */ jsx("path", { d: "M6.8 9.2a3 3 0 0 0 4.2 0l2-2a3 3 0 0 0-4.2-4.2l-1 1" }), /* @__PURE__ */ jsx("path", { d: "M9.2 6.8a3 3 0 0 0-4.2 0l-2 2a3 3 0 0 0 4.2 4.2l1-1" })]
9416
9456
  });
9417
9457
  }
9458
+ /** Everything to the right of the branch: the diff counts and the pull
9459
+ * request's controls. One row for the session checkout and each linked one;
9460
+ * `root` scopes the requests to a linked checkout. */
9461
+ function RepositoryControls({ sessionId, repository, root, running, t, report, openDiff, submitPrompt, deleteWorkspace }) {
9462
+ const pullRequest = repository.pullRequest;
9463
+ const merged = pullRequest?.state === "merged";
9464
+ const mergedAge = merged ? relativeAge(pullRequest.mergedAt) : void 0;
9465
+ const aheadCount = repository.ahead ?? 0;
9466
+ const pushable = repository.remote !== void 0 && repository.detached !== true && (aheadCount > 0 || repository.upstream === false);
9467
+ const hasDiff = repository.diff !== void 0 && (repository.diff.additions > 0 || repository.diff.deletions > 0);
9468
+ const actionable = repository.pullRequestOnly !== true || root !== void 0;
9469
+ const prompt = submitPrompt === void 0 ? {} : { submitPrompt };
9470
+ return /* @__PURE__ */ jsxs("span", {
9471
+ style: repositoryStatusItems,
9472
+ children: [
9473
+ /* @__PURE__ */ jsx(ConflictControl, {
9474
+ sessionId,
9475
+ repository,
9476
+ root,
9477
+ t,
9478
+ report,
9479
+ ...prompt
9480
+ }),
9481
+ hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
9482
+ type: "button",
9483
+ style: {
9484
+ ...diffTrigger,
9485
+ ...merged ? diffTriggerMuted : {}
9486
+ },
9487
+ onClick: openDiff,
9488
+ "aria-label": t("diffOpen"),
9489
+ children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9490
+ style: merged ? diffAddMuted : diffAdd,
9491
+ children: ["+", repository.diff.additions]
9492
+ }), /* @__PURE__ */ jsxs("span", {
9493
+ style: merged ? diffDeleteMuted : diffDelete,
9494
+ children: ["−", repository.diff.deletions]
9495
+ })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
9496
+ style: merged ? diffAheadMuted : diffAhead,
9497
+ children: ["↑", aheadCount > 0 ? aheadCount : ""]
9498
+ }) : null]
9499
+ }) : null,
9500
+ pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9501
+ style: repositoryMergedStatus,
9502
+ children: [
9503
+ /* @__PURE__ */ jsx("span", {
9504
+ style: repositoryMergedDot,
9505
+ "aria-hidden": "true"
9506
+ }),
9507
+ t("repositoryState_merged"),
9508
+ mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
9509
+ style: repositoryMergedAge,
9510
+ children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
9511
+ })
9512
+ ]
9513
+ }), /* @__PURE__ */ jsx(CleanupControl, {
9514
+ repository,
9515
+ t,
9516
+ report,
9517
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
9518
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9519
+ pullRequest.checks === "failing" && actionable ? /* @__PURE__ */ jsx(FailingChecksControl, {
9520
+ sessionId,
9521
+ repository: {
9522
+ ...repository,
9523
+ pullRequest
9524
+ },
9525
+ root,
9526
+ t,
9527
+ ...prompt
9528
+ }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9529
+ label: t(`repositoryChecks_${pullRequest.checks}`),
9530
+ tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning",
9531
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
9532
+ }),
9533
+ pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9534
+ label: t(`repositoryReview_${pullRequest.review}`),
9535
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
9536
+ children: /* @__PURE__ */ jsx(ReviewGlyph, {})
9537
+ }),
9538
+ actionable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
9539
+ /* @__PURE__ */ jsx(AutoFixControl, {
9540
+ sessionId,
9541
+ repository,
9542
+ root,
9543
+ running,
9544
+ t,
9545
+ ...prompt
9546
+ }),
9547
+ /* @__PURE__ */ jsx(UpdateBranchControl, {
9548
+ sessionId,
9549
+ repository,
9550
+ root,
9551
+ t,
9552
+ report,
9553
+ ...prompt
9554
+ }),
9555
+ /* @__PURE__ */ jsx(MergePullRequestControl, {
9556
+ sessionId,
9557
+ repository,
9558
+ root,
9559
+ t,
9560
+ report
9561
+ })
9562
+ ] }) : null
9563
+ ] })
9564
+ ]
9565
+ });
9566
+ }
9418
9567
  /** A checkout the session wrote into besides its own, stacked above the
9419
9568
  * session bar so that one stays put next to the composer. The same readout
9420
- * as the session bar, minus the controls that act on the session (auto-fix, update,
9421
- * merge, cleanup), which live with the checkout the session was opened on. */
9422
- function LinkedRepositoryBar({ repository, t, openDiff, report }) {
9423
- const key = repository.root ?? repository.cwd;
9569
+ * and controls as the session bar, scoped to that checkout; only the
9570
+ * workspace deletion after a clean-up stays with the session's own. */
9571
+ function LinkedRepositoryBar({ sessionId, repository, running, t, openDiff, report, submitPrompt }) {
9572
+ const key = repository.root ?? `${repository.remote ?? repository.cwd}#${repository.pullRequest?.number ?? ""}`;
9424
9573
  if (repository.status !== "ready") return /* @__PURE__ */ jsxs("div", {
9425
9574
  style: {
9426
9575
  ...repositoryBar,
@@ -9441,9 +9590,7 @@ window.__ModuleLoader__.load({
9441
9590
  })]
9442
9591
  });
9443
9592
  const branch = branchLabel(repository, t);
9444
- const pullRequest = repository.pullRequest;
9445
- const merged = pullRequest?.state === "merged";
9446
- const hasDiff = repository.diff !== void 0 && (repository.diff.additions > 0 || repository.diff.deletions > 0);
9593
+ const merged = repository.pullRequest?.state === "merged";
9447
9594
  return /* @__PURE__ */ jsxs("div", {
9448
9595
  style: {
9449
9596
  ...repositoryBar,
@@ -9461,9 +9608,15 @@ window.__ModuleLoader__.load({
9461
9608
  repository,
9462
9609
  t
9463
9610
  }),
9464
- /* @__PURE__ */ jsx("span", {
9465
- style: repositoryRemote,
9466
- children: repositoryLabel(repository)
9611
+ /* @__PURE__ */ jsx(Tooltip, {
9612
+ label: repository.remote ?? repositoryLabel(repository),
9613
+ side: "top",
9614
+ delayMs: 250,
9615
+ maxWidth: 420,
9616
+ children: /* @__PURE__ */ jsx("span", {
9617
+ style: repositoryRemote,
9618
+ children: repositoryLabel(repository)
9619
+ })
9467
9620
  }),
9468
9621
  /* @__PURE__ */ jsx(Tooltip, {
9469
9622
  label: branch,
@@ -9479,66 +9632,154 @@ window.__ModuleLoader__.load({
9479
9632
  style: repositoryWorktree,
9480
9633
  children: t("repositoryWorktree")
9481
9634
  }) : null,
9482
- /* @__PURE__ */ jsxs("span", {
9483
- style: repositoryStatusItems,
9484
- children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs("button", {
9485
- type: "button",
9486
- style: {
9487
- ...diffTrigger,
9488
- ...merged ? diffTriggerMuted : {}
9489
- },
9490
- onClick: () => openDiff(repository.root),
9491
- "aria-label": t("diffOpen"),
9492
- children: [/* @__PURE__ */ jsxs("span", {
9493
- style: merged ? diffAddMuted : diffAdd,
9494
- children: ["+", repository.diff.additions]
9495
- }), /* @__PURE__ */ jsxs("span", {
9496
- style: merged ? diffDeleteMuted : diffDelete,
9497
- children: ["−", repository.diff.deletions]
9498
- })]
9499
- }) : null, pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9500
- style: repositoryMergedStatus,
9501
- children: [/* @__PURE__ */ jsx("span", {
9502
- style: repositoryMergedDot,
9503
- "aria-hidden": "true"
9504
- }), t("repositoryState_merged")]
9505
- }), /* @__PURE__ */ jsx(CleanupControl, {
9506
- repository,
9507
- t,
9508
- report
9509
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9510
- label: t(`repositoryChecks_${pullRequest.checks}`),
9511
- tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning",
9512
- children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
9513
- }), pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9514
- label: t(`repositoryReview_${pullRequest.review}`),
9515
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
9516
- children: /* @__PURE__ */ jsx(ReviewGlyph, {})
9517
- })] })]
9635
+ /* @__PURE__ */ jsx(RepositoryControls, {
9636
+ sessionId,
9637
+ repository,
9638
+ root: repository.root,
9639
+ running,
9640
+ t,
9641
+ report,
9642
+ openDiff: () => openDiff(repository.root),
9643
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
9518
9644
  })
9519
9645
  ]
9520
9646
  });
9521
9647
  }
9648
+ const linkedExpanded = /* @__PURE__ */ new Map();
9649
+ /** Where the Host's jump-to-latest button sits relative to the linked
9650
+ * stack, remembered from the last time it was on screen: the seat the fold
9651
+ * control takes while the Host's button is away. Until it has been seen
9652
+ * once, the Host's own geometry: 16px in from the column's edge, 16px above
9653
+ * the composer. */
9654
+ let hostSeat = {
9655
+ top: -50,
9656
+ rightInset: 16
9657
+ };
9658
+ const HOST_JUMP_GAP = 8;
9659
+ /** The fold control's place: the Host's seat, or 8px left of the Host's
9660
+ * button while that is showing. Found by the Host's class stem; one cheap
9661
+ * query and two rects per DOM change or resize, on the next frame. */
9662
+ function useHostJumpSeat(stack) {
9663
+ const [seat, setSeat] = useState({
9664
+ shown: false,
9665
+ top: hostSeat.top,
9666
+ right: hostSeat.rightInset
9667
+ });
9668
+ useEffect(() => {
9669
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return;
9670
+ let frame;
9671
+ const check = () => {
9672
+ frame = void 0;
9673
+ const native = document.querySelector("button[class*=\"toBottom\"]");
9674
+ const frameRect = stack.current?.getBoundingClientRect();
9675
+ if (native === null || frameRect === void 0) {
9676
+ setSeat({
9677
+ shown: false,
9678
+ top: hostSeat.top,
9679
+ right: hostSeat.rightInset
9680
+ });
9681
+ return;
9682
+ }
9683
+ const rect = native.getBoundingClientRect();
9684
+ if (rect.width > 0) hostSeat = {
9685
+ top: rect.top - frameRect.top,
9686
+ rightInset: frameRect.right - rect.right
9687
+ };
9688
+ setSeat({
9689
+ shown: true,
9690
+ top: hostSeat.top,
9691
+ right: frameRect.right - rect.left + HOST_JUMP_GAP
9692
+ });
9693
+ };
9694
+ const schedule = () => {
9695
+ if (frame === void 0) frame = requestAnimationFrame(check);
9696
+ };
9697
+ check();
9698
+ const observer = new MutationObserver(schedule);
9699
+ observer.observe(document.body, {
9700
+ childList: true,
9701
+ subtree: true
9702
+ });
9703
+ window.addEventListener("resize", schedule);
9704
+ return () => {
9705
+ observer.disconnect();
9706
+ window.removeEventListener("resize", schedule);
9707
+ if (frame !== void 0) cancelAnimationFrame(frame);
9708
+ };
9709
+ }, [stack]);
9710
+ return seat;
9711
+ }
9712
+ /** The fold control floats above the dock in the Host's jump-to-latest seat,
9713
+ * stepping left of that button while the Host shows it: a circle holding a
9714
+ * double chevron, down while folded and up once open. The count and the
9715
+ * words live in its tooltip and accessible name. */
9716
+ function LinkedFoldChip({ sessionId, hidden, expanded, onToggle, t, seat }) {
9717
+ const label = expanded ? t("linkedCollapse") : `${t("linkedMore", { count: hidden })} · ${t("linkedShowAll")}`;
9718
+ return /* @__PURE__ */ jsx(Tooltip, {
9719
+ label,
9720
+ side: "top",
9721
+ delayMs: 250,
9722
+ children: /* @__PURE__ */ jsx("button", {
9723
+ type: "button",
9724
+ style: {
9725
+ ...linkedFoldChip,
9726
+ top: seat.top,
9727
+ right: seat.right
9728
+ },
9729
+ "aria-expanded": expanded,
9730
+ "aria-label": label,
9731
+ "data-dsh-claude-linked-fold": sessionId,
9732
+ onClick: onToggle,
9733
+ children: /* @__PURE__ */ jsx("span", {
9734
+ className: linkedFoldGlyphClass,
9735
+ style: linkedFoldGlyph,
9736
+ "aria-hidden": "true",
9737
+ children: expanded ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconChevronUpOutline14, {}), /* @__PURE__ */ jsx(IconChevronUpOutline14, {})] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconChevronDownOutline14, {}), /* @__PURE__ */ jsx(IconChevronDownOutline14, {})] })
9738
+ })
9739
+ })
9740
+ });
9741
+ }
9522
9742
  function ClaudeRepositoryStatus({ sessionId, useSessions, useClaudeProjection, t, openDiff, submitPrompt, openOverview, deleteWorkspace }) {
9523
9743
  const blank = useSessions((value) => value.byId[sessionId]?.blank === true);
9524
9744
  const running = useSessions((value) => value.byId[sessionId]?.running === true);
9525
9745
  const projection = useClaudeProjection((value) => value);
9526
9746
  const repository = projection.repository;
9527
9747
  const { toast, report } = useActionToast();
9748
+ const [expanded, setExpanded] = useState(() => linkedExpanded.get(sessionId) ?? false);
9749
+ useEffect(() => {
9750
+ setExpanded(linkedExpanded.get(sessionId) ?? false);
9751
+ }, [sessionId]);
9752
+ const stackRef = useRef(null);
9753
+ const seat = useHostJumpSeat(stackRef);
9528
9754
  if (blank || !projection.owned || repository === void 0) return null;
9529
9755
  const branch = branchLabel(repository, t);
9530
- const pullRequest = repository.pullRequest;
9531
- const merged = pullRequest?.state === "merged";
9532
- const mergedAge = merged ? relativeAge(pullRequest.mergedAt) : void 0;
9533
- const aheadCount = repository.ahead ?? 0;
9534
- const pushable = repository.remote !== void 0 && repository.detached !== true && (aheadCount > 0 || repository.upstream === false);
9535
- const hasDiff = repository.diff !== void 0 && (repository.diff.additions > 0 || repository.diff.deletions > 0);
9536
- const linked = (projection.repositories ?? []).map((item) => /* @__PURE__ */ jsx(LinkedRepositoryBar, {
9537
- repository: item,
9538
- t,
9539
- openDiff,
9540
- report
9541
- }, item.root ?? item.cwd));
9756
+ const merged = repository.pullRequest?.state === "merged";
9757
+ const all = projection.repositories ?? [];
9758
+ const shown = expanded ? all : all.slice(0, 3);
9759
+ const linked = all.length === 0 ? null : /* @__PURE__ */ jsxs("div", {
9760
+ ref: stackRef,
9761
+ style: linkedStack,
9762
+ children: [all.length > 3 ? /* @__PURE__ */ jsx(LinkedFoldChip, {
9763
+ sessionId,
9764
+ hidden: all.length - 3,
9765
+ expanded,
9766
+ seat,
9767
+ t,
9768
+ onToggle: () => {
9769
+ const next = !expanded;
9770
+ setExpanded(next);
9771
+ linkedExpanded.set(sessionId, next);
9772
+ }
9773
+ }) : null, shown.map((item) => /* @__PURE__ */ jsx(LinkedRepositoryBar, {
9774
+ sessionId,
9775
+ repository: item,
9776
+ running,
9777
+ t,
9778
+ openDiff,
9779
+ report,
9780
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
9781
+ }, item.root ?? `${item.remote ?? item.cwd}#${item.pullRequest?.number ?? ""}`))]
9782
+ });
9542
9783
  if (repository.status !== "ready") return /* @__PURE__ */ jsxs("div", {
9543
9784
  style: repositoryBarFrame,
9544
9785
  [CLAUDE_COMPOSER_BAR_ATTRIBUTE]: "",
@@ -9557,6 +9798,10 @@ window.__ModuleLoader__.load({
9557
9798
  style: repositoryBarFrame,
9558
9799
  [CLAUDE_COMPOSER_BAR_ATTRIBUTE]: "",
9559
9800
  children: [
9801
+ /* @__PURE__ */ jsxs("style", {
9802
+ "data-dsh-claude-repository-bar-styles": true,
9803
+ children: [repositoryAutoFixCss, linkedFoldGlyphCss]
9804
+ }),
9560
9805
  toast,
9561
9806
  linked,
9562
9807
  /* @__PURE__ */ jsxs("div", {
@@ -9587,9 +9832,15 @@ window.__ModuleLoader__.load({
9587
9832
  repository,
9588
9833
  t
9589
9834
  }),
9590
- repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
9591
- style: repositoryRemote,
9592
- children: repositoryName(repository.remote)
9835
+ repository.remote === void 0 ? null : /* @__PURE__ */ jsx(Tooltip, {
9836
+ label: repository.remote,
9837
+ side: "top",
9838
+ delayMs: 250,
9839
+ maxWidth: 420,
9840
+ children: /* @__PURE__ */ jsx("span", {
9841
+ style: repositoryRemote,
9842
+ children: repositoryName(repository.remote)
9843
+ })
9593
9844
  }),
9594
9845
  /* @__PURE__ */ jsx(Tooltip, {
9595
9846
  label: branch,
@@ -9605,91 +9856,15 @@ window.__ModuleLoader__.load({
9605
9856
  style: repositoryWorktree,
9606
9857
  children: t("repositoryWorktree")
9607
9858
  }) : null,
9608
- /* @__PURE__ */ jsxs("span", {
9609
- style: repositoryStatusItems,
9610
- children: [
9611
- /* @__PURE__ */ jsx(ConflictControl, {
9612
- sessionId,
9613
- repository,
9614
- t,
9615
- report,
9616
- ...submitPrompt === void 0 ? {} : { submitPrompt }
9617
- }),
9618
- hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
9619
- type: "button",
9620
- style: {
9621
- ...diffTrigger,
9622
- ...merged ? diffTriggerMuted : {}
9623
- },
9624
- onClick: () => openDiff(),
9625
- "aria-label": t("diffOpen"),
9626
- children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9627
- style: merged ? diffAddMuted : diffAdd,
9628
- children: ["+", repository.diff.additions]
9629
- }), /* @__PURE__ */ jsxs("span", {
9630
- style: merged ? diffDeleteMuted : diffDelete,
9631
- children: ["−", repository.diff.deletions]
9632
- })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
9633
- style: merged ? diffAheadMuted : diffAhead,
9634
- children: ["↑", aheadCount > 0 ? aheadCount : ""]
9635
- }) : null]
9636
- }) : null,
9637
- pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9638
- style: repositoryMergedStatus,
9639
- children: [
9640
- /* @__PURE__ */ jsx("span", {
9641
- style: repositoryMergedDot,
9642
- "aria-hidden": "true"
9643
- }),
9644
- t("repositoryState_merged"),
9645
- mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
9646
- style: repositoryMergedAge,
9647
- children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
9648
- })
9649
- ]
9650
- }), /* @__PURE__ */ jsx(CleanupControl, {
9651
- repository,
9652
- t,
9653
- report,
9654
- ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
9655
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9656
- pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
9657
- sessionId,
9658
- pullNumber: pullRequest.number,
9659
- t,
9660
- ...submitPrompt === void 0 ? {} : { submitPrompt }
9661
- }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9662
- label: t(`repositoryChecks_${pullRequest.checks}`),
9663
- tone: pullRequest.checks === "passing" ? "success" : "warning",
9664
- children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
9665
- }),
9666
- pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9667
- label: t(`repositoryReview_${pullRequest.review}`),
9668
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
9669
- children: /* @__PURE__ */ jsx(ReviewGlyph, {})
9670
- }),
9671
- /* @__PURE__ */ jsx(AutoFixControl, {
9672
- sessionId,
9673
- repository,
9674
- running,
9675
- t,
9676
- ...submitPrompt === void 0 ? {} : { submitPrompt }
9677
- }),
9678
- /* @__PURE__ */ jsx(UpdateBranchControl, {
9679
- sessionId,
9680
- repository,
9681
- t,
9682
- report,
9683
- ...submitPrompt === void 0 ? {} : { submitPrompt }
9684
- }),
9685
- /* @__PURE__ */ jsx(MergePullRequestControl, {
9686
- sessionId,
9687
- repository,
9688
- t,
9689
- report
9690
- })
9691
- ] })
9692
- ]
9859
+ /* @__PURE__ */ jsx(RepositoryControls, {
9860
+ sessionId,
9861
+ repository,
9862
+ running,
9863
+ t,
9864
+ report,
9865
+ openDiff: () => openDiff(),
9866
+ ...submitPrompt === void 0 ? {} : { submitPrompt },
9867
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
9693
9868
  })
9694
9869
  ]
9695
9870
  })
@@ -15072,24 +15247,15 @@ window.__ModuleLoader__.load({
15072
15247
  "update-branch": ready && hasOpenPullRequest && repository?.dirty !== true && (repository?.baseBehind ?? 0) > 0
15073
15248
  };
15074
15249
  }
15075
- function RestorePanelIcon() {
15076
- return /* @__PURE__ */ jsx("svg", {
15077
- width: "16",
15078
- height: "16",
15079
- viewBox: "0 0 16 16",
15080
- fill: "currentColor",
15081
- "aria-hidden": "true",
15082
- children: /* @__PURE__ */ jsx("path", { d: "M1.5 5h3V2h1.4v4.4H1.5V5Zm9.9-3h1.4v3h3v1.4h-4.4V2ZM1.5 9.6h4.4V14H4.5v-3h-3V9.6Zm9.9 0h4.4V11h-3v3h-1.4V9.6Z" })
15083
- });
15084
- }
15085
15250
  function hasChanges(repository) {
15086
15251
  return (repository?.diff?.additions ?? 0) > 0 || (repository?.diff?.deletions ?? 0) > 0;
15087
15252
  }
15088
- /** The checkouts a session can show: its own first, then the ones it wrote into. */
15253
+ /** The checkouts a session can show: its own first, then the ones it wrote
15254
+ * into. A linked pull request with no local checkout has no diff to show. */
15089
15255
  function panelRepositories(projection) {
15090
- return [projection.repository, ...projection.repositories ?? []].filter((item) => item !== void 0);
15256
+ return [projection.repository, ...(projection.repositories ?? []).filter((item) => item.diff !== void 0)].filter((item) => item !== void 0);
15091
15257
  }
15092
- function ClaudeDiffPanel({ useClaudeProjection, t, sessionId, maximized, closeDetails, toggleMaximized, submitPrompt, initialRoot }) {
15258
+ function ClaudeDiffPanel({ useClaudeProjection, t, sessionId, closeDetails, submitPrompt, initialRoot }) {
15093
15259
  const projection = useClaudeProjection((value) => value);
15094
15260
  const repositories = useMemo(() => panelRepositories(projection), [projection]);
15095
15261
  const [selectedRoot, setSelectedRoot] = useState(() => initialRoot ?? (hasChanges(projection.repository) ? void 0 : repositories.find(hasChanges)?.root));
@@ -15518,10 +15684,7 @@ window.__ModuleLoader__.load({
15518
15684
  }),
15519
15685
  /* @__PURE__ */ jsxs("div", {
15520
15686
  className: detailsCardClass,
15521
- style: {
15522
- ...diffPanel,
15523
- ...maximized ? diffPanelMaximized : {}
15524
- },
15687
+ style: diffPanel,
15525
15688
  children: [
15526
15689
  /* @__PURE__ */ jsxs("header", {
15527
15690
  style: diffHeader,
@@ -15645,13 +15808,6 @@ window.__ModuleLoader__.load({
15645
15808
  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" })
15646
15809
  }), openThreadCount]
15647
15810
  }) : null,
15648
- /* @__PURE__ */ jsx("button", {
15649
- type: "button",
15650
- className: panelIconButtonClass,
15651
- "aria-label": maximized ? t("diffRestore") : t("diffMaximize"),
15652
- onClick: toggleMaximized,
15653
- children: maximized ? /* @__PURE__ */ jsx(RestorePanelIcon, {}) : /* @__PURE__ */ jsx(IconFullscreenOutline16, {})
15654
- }),
15655
15811
  /* @__PURE__ */ jsx("button", {
15656
15812
  type: "button",
15657
15813
  className: panelIconButtonClass,
@@ -15711,7 +15867,7 @@ window.__ModuleLoader__.load({
15711
15867
  children: t("diffEmpty")
15712
15868
  }) : files.map((file, index) => /* @__PURE__ */ jsx(DiffFileSection, {
15713
15869
  file,
15714
- root: repository.root,
15870
+ root: repository.pullRequestOnly === true ? void 0 : repository.root,
15715
15871
  open: fileOpen(file.path, index),
15716
15872
  onOpenChange: (open) => {
15717
15873
  setFileOpen(file.path, open);
@@ -15901,9 +16057,9 @@ window.__ModuleLoader__.load({
15901
16057
  * focuses that tab and re-delivers the navigation params, so a second "show
15902
16058
  * tasks" press for another turn lands in the same tab.
15903
16059
  *
15904
- * The Host owns fullscreen and closing now, so the maximized overlays this
15905
- * package used to draw are gone: a panel's maximize button toggles the
15906
- * sidebar's own expanded mode, and its close button closes the tab.
16060
+ * The Host owns fullscreen and closing now: the sidebar chrome carries its
16061
+ * own fullscreen toggle, so the panels draw none, and a panel's close button
16062
+ * closes its tab.
15907
16063
  */
15908
16064
  const CLAUDE_TAB_KINDS = {
15909
16065
  diff: "claude-diff",
@@ -15916,7 +16072,7 @@ window.__ModuleLoader__.load({
15916
16072
  #byKey = /* @__PURE__ */ new Map();
15917
16073
  #listeners = /* @__PURE__ */ new Map();
15918
16074
  static key(kind, sessionId) {
15919
- return `${kind}${sessionId}`;
16075
+ return `${kind}\0${sessionId}`;
15920
16076
  }
15921
16077
  tabId(kind, sessionId) {
15922
16078
  return this.#byKey.get(TabOccupancy.key(kind, sessionId));
@@ -15963,27 +16119,22 @@ window.__ModuleLoader__.load({
15963
16119
  closeDetails: useCallback(() => {
15964
16120
  actions.close();
15965
16121
  }, [actions]),
15966
- maximized: info.sidebar.fullscreen,
15967
16122
  params: info.tab.navigation.params ?? {}
15968
16123
  };
15969
16124
  }
15970
- function ClaudeDiffTab({ useTabInfo, noteTab, toggleMaximized, ...panel }) {
15971
- const { closeDetails, maximized, params } = useClaudeTab(useTabInfo, noteTab);
16125
+ function ClaudeDiffTab({ useTabInfo, noteTab, ...panel }) {
16126
+ const { closeDetails, params } = useClaudeTab(useTabInfo, noteTab);
15972
16127
  return /* @__PURE__ */ jsx(ClaudeDiffPanel, {
15973
16128
  ...panel,
15974
16129
  closeDetails,
15975
- maximized,
15976
- toggleMaximized,
15977
16130
  ...params.initialRoot === void 0 ? {} : { initialRoot: params.initialRoot }
15978
16131
  });
15979
16132
  }
15980
- function ClaudePlanTab({ useTabInfo, noteTab, toggleMaximized, ...panel }) {
15981
- const { closeDetails, maximized } = useClaudeTab(useTabInfo, noteTab);
16133
+ function ClaudePlanTab({ useTabInfo, noteTab, ...panel }) {
16134
+ const { closeDetails } = useClaudeTab(useTabInfo, noteTab);
15982
16135
  return /* @__PURE__ */ jsx(ClaudePlanPanel, {
15983
16136
  ...panel,
15984
- closeDetails,
15985
- maximized,
15986
- toggleMaximized
16137
+ closeDetails
15987
16138
  });
15988
16139
  }
15989
16140
  function ClaudeTasksTab({ useTabInfo, noteTab, ...panel }) {
@@ -16007,15 +16158,9 @@ window.__ModuleLoader__.load({
16007
16158
  function registerClaudeSidebarTabs(ctx, options) {
16008
16159
  const { t, namespace } = options;
16009
16160
  const occupancy = new TabOccupancy();
16010
- const toggleMaximized = () => {
16011
- ctx.sidebarRight.toggleExpanded();
16012
- };
16013
- const faceFor = (kind, sessionId) => ({
16014
- noteTab: (tabId) => {
16015
- occupancy.note(kind, sessionId, tabId);
16016
- },
16017
- toggleMaximized
16018
- });
16161
+ const faceFor = (kind, sessionId) => ({ noteTab: (tabId) => {
16162
+ occupancy.note(kind, sessionId, tabId);
16163
+ } });
16019
16164
  const titles = {
16020
16165
  [CLAUDE_TAB_KINDS.diff]: () => t("diffTabTitle"),
16021
16166
  [CLAUDE_TAB_KINDS.plan]: () => t("planPanelTitle"),
@@ -19022,8 +19167,6 @@ window.__ModuleLoader__.load({
19022
19167
  planPanelTitle: "Plan",
19023
19168
  planClose: "关闭方案面板",
19024
19169
  planOpen: "查看方案",
19025
- planMaximize: "最大化方案面板",
19026
- planRestore: "还原方案面板",
19027
19170
  planNth: "{index} / {total}",
19028
19171
  planHistory: "本会话的方案",
19029
19172
  planNotePlaceholder: "写下要改的地方,Enter 记一条,⌘/Ctrl+Enter 发送",
@@ -19058,12 +19201,15 @@ window.__ModuleLoader__.load({
19058
19201
  presetHeaderHint: "当前会话运行的 Agent 预设",
19059
19202
  diffRepository: "仓库",
19060
19203
  repositoryLinked: "关联仓库:本会话改动过",
19204
+ linkedMore: "还有 {count} 个关联仓库",
19205
+ linkedShowAll: "全部展开",
19206
+ linkedCollapse: "收起关联仓库",
19207
+ linkedRepositoryPrompt: "以下内容针对关联仓库 {root}(分支 {branch}),不是本会话自己的仓库。",
19061
19208
  diffFiles: "{count} 个已修改文件",
19062
19209
  diffFilesShort: "{count} 个文件",
19063
19210
  diffTruncated: "Diff 超出安全显示上限。请在终端中查看完整内容。",
19064
19211
  diffEmpty: "没有可显示的 tracked 文件改动",
19065
19212
  diffElided: "{count} 个文件过大未展示:{files}",
19066
- diffMaximize: "最大化 Diff 面板",
19067
19213
  diffCommentPrevious: "上一条评论",
19068
19214
  diffCommentNext: "下一条评论",
19069
19215
  diffCommentPosition: "第 {index} 条,共 {total} 条",
@@ -19073,7 +19219,6 @@ window.__ModuleLoader__.load({
19073
19219
  diffCardFiles: "{count} 个文件",
19074
19220
  diffExpandAll: "展开全部文件",
19075
19221
  diffCollapseAll: "收起全部文件",
19076
- diffRestore: "还原 Diff 面板",
19077
19222
  diffCommit: "Commit",
19078
19223
  diffCommitMenu: "打开提交操作菜单",
19079
19224
  diffCommitPush: "Commit & Push…",
@@ -19186,6 +19331,7 @@ window.__ModuleLoader__.load({
19186
19331
  cleanupTitle: "清理已合并分支",
19187
19332
  cleanupDescription: "删除 worktree 与本地分支,并归档对应 workspace 的会话后移除 workspace;普通 checkout 则切回 base 分支并删除已合并分支。",
19188
19333
  cleanupCompleted: "已清理 {branch}",
19334
+ cleanupDescriptionUnmerged: "这个 worktree 没有对应的 PR。删除 worktree 与本地分支,并归档对应 workspace 的会话后移除 workspace;分支上有任何未推送到远端的提交时会拒绝。",
19189
19335
  overviewOpen: "打开 Claude 会话看板",
19190
19336
  overviewTitle: "会话看板",
19191
19337
  overviewBody: "所有 Claude 会话的运行状态、分支与 PR",
@@ -19465,8 +19611,6 @@ window.__ModuleLoader__.load({
19465
19611
  planPanelTitle: "Plan",
19466
19612
  planClose: "Close plan panel",
19467
19613
  planOpen: "View plan",
19468
- planMaximize: "Maximize plan panel",
19469
- planRestore: "Restore plan panel",
19470
19614
  planNth: "{index} / {total}",
19471
19615
  planHistory: "Plans in this session",
19472
19616
  planNotePlaceholder: "What should change? Enter files a note, ⌘/Ctrl+Enter sends",
@@ -19501,12 +19645,15 @@ window.__ModuleLoader__.load({
19501
19645
  presetHeaderHint: "The agent preset this session runs",
19502
19646
  diffRepository: "Repository",
19503
19647
  repositoryLinked: "Linked repository: changed by this session",
19648
+ linkedMore: "{count} more linked repositories",
19649
+ linkedShowAll: "Show all",
19650
+ linkedCollapse: "Collapse linked repositories",
19651
+ linkedRepositoryPrompt: "The following concerns the linked repository at {root} (branch {branch}), not this session's own checkout.",
19504
19652
  diffFiles: "{count} modified file(s)",
19505
19653
  diffFilesShort: "{count} files",
19506
19654
  diffTruncated: "The diff exceeds the safe display limit. Use the terminal to inspect the complete content.",
19507
19655
  diffEmpty: "No tracked file changes to display",
19508
19656
  diffElided: "Skipped {count} oversized file(s): {files}",
19509
- diffMaximize: "Maximize diff panel",
19510
19657
  diffCommentPrevious: "Previous comment",
19511
19658
  diffCommentNext: "Next comment",
19512
19659
  diffCommentPosition: "Comment {index} of {total}",
@@ -19516,7 +19663,6 @@ window.__ModuleLoader__.load({
19516
19663
  diffCardFiles: "{count} files",
19517
19664
  diffExpandAll: "Expand all files",
19518
19665
  diffCollapseAll: "Collapse all files",
19519
- diffRestore: "Restore diff panel",
19520
19666
  diffCommit: "Commit",
19521
19667
  diffCommitMenu: "Open commit actions",
19522
19668
  diffCommitPush: "Commit & Push…",
@@ -19629,6 +19775,7 @@ window.__ModuleLoader__.load({
19629
19775
  cleanupTitle: "Clean up merged branch",
19630
19776
  cleanupDescription: "Remove the worktree and local branch, then archive the workspace sessions and delete the workspace; a plain checkout switches back to the base branch and deletes the merged branch.",
19631
19777
  cleanupCompleted: "Cleaned up {branch}",
19778
+ cleanupDescriptionUnmerged: "This worktree has no pull request. Remove the worktree and local branch, then archive the workspace sessions and delete the workspace; refused while the branch holds commits that are on no remote.",
19632
19779
  overviewOpen: "Open the Claude session board",
19633
19780
  overviewTitle: "Session board",
19634
19781
  overviewBody: "Run state, branch, and pull request across Claude sessions",