@norman-else/dsh-claude 0.1.37 → 0.1.38

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
@@ -6674,6 +6674,12 @@ window.__ModuleLoader__.load({
6674
6674
  if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
6675
6675
  return body;
6676
6676
  }
6677
+ /** Ask the Host to reconcile worktrees against the workspace registry now.
6678
+ * Deleting a workspace only touches the registry, so this kick is what makes
6679
+ * the worktree and its sessions go without waiting out the sweep interval. */
6680
+ async function sweepWorktrees() {
6681
+ await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/sweep`, "fast");
6682
+ }
6677
6683
  /** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
6678
6684
  async function loadRepositoryFileLines(cwd, path, from, to, signal) {
6679
6685
  const body = await pluginRead(CLAUDE_REPOSITORY_FILE_PATH, "git", signal, { query: {
@@ -7398,6 +7404,14 @@ window.__ModuleLoader__.load({
7398
7404
  }) : null]
7399
7405
  });
7400
7406
  }
7407
+ /** The trigger only shows on a clean branch that is behind its base -- but a
7408
+ * conflicted rebase leaves the tree dirty on a detached HEAD, so an open
7409
+ * dialog (and its resolve button) has to outlive that. */
7410
+ function updateBranchMounted(repository, dialogOpen) {
7411
+ const pullRequest = repository.pullRequest;
7412
+ if (pullRequest?.baseBranch === void 0) return false;
7413
+ return dialogOpen || pullRequest.state === "open" && repository.detached !== true && repository.dirty !== true && (repository.baseBehind ?? 0) > 0;
7414
+ }
7401
7415
  function UpdateBranchControl({ sessionId, repository, t, report, submitPrompt }) {
7402
7416
  const [dialog, setDialog] = useState();
7403
7417
  const [method, setMethod] = useState("rebase");
@@ -7406,7 +7420,8 @@ window.__ModuleLoader__.load({
7406
7420
  const base = pullRequest?.baseBranch;
7407
7421
  const behind = repository.baseBehind ?? 0;
7408
7422
  useEffect(() => () => controller.current?.abort(), []);
7409
- if (pullRequest === void 0 || pullRequest.state !== "open" || base === void 0 || repository.detached === true || repository.dirty === true || behind <= 0) return null;
7423
+ if (pullRequest === void 0 || base === void 0) return null;
7424
+ if (!updateBranchMounted(repository, dialog !== void 0)) return null;
7410
7425
  const openDialog = () => {
7411
7426
  controller.current?.abort();
7412
7427
  setDialog({
@@ -15338,13 +15353,37 @@ window.__ModuleLoader__.load({
15338
15353
  function directChildContaining(parent, descendant) {
15339
15354
  return Array.from(parent.children).find((child) => child === descendant || child.contains(descendant));
15340
15355
  }
15341
- /** Locate rc.8's hero preset seat without relying on hashed CSS module names. */
15342
- function locateClaudePresetSeat(root = document) {
15356
+ /** The hero's preset buttons: menu buttons without an `aria-label`, which is
15357
+ * what separates the preset seat from the labelled workspace picker. The
15358
+ * portal's own branch trigger answers that description too, so it is excluded
15359
+ * -- counting it made the row look like it held two staged presets from the
15360
+ * moment these controls mounted. */
15361
+ function heroPresetButtons(root) {
15343
15362
  const heroes = Array.from(root.querySelectorAll(HERO_SELECTOR));
15344
15363
  if (heroes.length !== 1) return void 0;
15345
15364
  const hero = heroes[0];
15346
15365
  if (hero === void 0) return void 0;
15347
- const presetButtons = Array.from(hero.querySelectorAll("button[aria-haspopup=\"menu\"]")).filter((button) => button.getAttribute("aria-label") === null && isClaudePresetText(normalizedText(button)));
15366
+ return {
15367
+ hero,
15368
+ presets: Array.from(hero.querySelectorAll("button[aria-haspopup=\"menu\"]")).filter((button) => button.getAttribute("aria-label") === null && button.closest(`[${PORTAL_ATTRIBUTE}]`) === null)
15369
+ };
15370
+ }
15371
+ /** True once the hero has settled on a preset that is not Claude.
15372
+ * `locateClaudePresetSeat` reports that the same way it reports a hero
15373
+ * mid-render -- by missing -- so without this the retention rule below reads a
15374
+ * deliberate switch away from Claude as a re-render and leaves the controls up. */
15375
+ function showsOtherPresetSeat(root = document) {
15376
+ const found = heroPresetButtons(root);
15377
+ if (found === void 0 || found.presets.length !== 1) return false;
15378
+ const preset = found.presets[0];
15379
+ return preset !== void 0 && !isClaudePresetText(normalizedText(preset));
15380
+ }
15381
+ /** Locate rc.8's hero preset seat without relying on hashed CSS module names. */
15382
+ function locateClaudePresetSeat(root = document) {
15383
+ const found = heroPresetButtons(root);
15384
+ if (found === void 0) return void 0;
15385
+ const { hero } = found;
15386
+ const presetButtons = found.presets.filter((button) => isClaudePresetText(normalizedText(button)));
15348
15387
  if (presetButtons.length !== 1) return void 0;
15349
15388
  const preset = presetButtons[0];
15350
15389
  if (preset === void 0) return void 0;
@@ -15803,7 +15842,7 @@ window.__ModuleLoader__.load({
15803
15842
  scheduled = false;
15804
15843
  const target = locateClaudePresetSeat();
15805
15844
  if (target === void 0) {
15806
- if (retainsClaudeHeroPortal(current)) return;
15845
+ if (retainsClaudeHeroPortal(current) && !showsOtherPresetSeat()) return;
15807
15846
  current = void 0;
15808
15847
  removeClaudeHeroPortals();
15809
15848
  setPortal(void 0);
@@ -17800,6 +17839,15 @@ window.__ModuleLoader__.load({
17800
17839
  const workspaces = ctx.get("workspaces");
17801
17840
  const uiWorkspace = ctx.get("uiWorkspace");
17802
17841
  const connectWorkspace = uiWorkspace?.connectWorkspace?.bind(uiWorkspace) ?? (typeof workspaces?.connectWorkspace === "function" ? workspaces.connectWorkspace.bind(workspaces) : void 0);
17842
+ if (workspaces !== void 0) ctx.effect(() => {
17843
+ let known = new Set(workspaces.list.getSnapshot().items.map((item) => item.workspaceId));
17844
+ return workspaces.list.subscribe(() => {
17845
+ const next = new Set(workspaces.list.getSnapshot().items.map((item) => item.workspaceId));
17846
+ const removed = [...known].some((id) => !next.has(id));
17847
+ known = next;
17848
+ if (removed) sweepWorktrees().catch(() => void 0);
17849
+ });
17850
+ }, "dsh-claude: worktree sweep on workspace deletion");
17803
17851
  const conversation = ctx.get("conversation");
17804
17852
  const connection = ctx.get("connection");
17805
17853
  const remote = ctx.get("remote");