@ogpoyraz/wtx 0.9.1 → 0.9.2

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +513 -258
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -25503,20 +25503,22 @@ async function fetchWorktreeData(opts, scope) {
25503
25503
  }
25504
25504
  let repos;
25505
25505
  try {
25506
- repos = resolveRepos(config2);
25506
+ if (scope && scope.length > 0) {
25507
+ repos = resolveRepos(config2, scope);
25508
+ } else {
25509
+ repos = resolveRepos(config2, Object.keys(config2.repos));
25510
+ }
25507
25511
  } catch (err) {
25508
25512
  throw new Error(`Failed to resolve repos: ${errorMessage3(err)}`);
25509
25513
  }
25510
- if (scope) {
25511
- const scopeSet = new Set(scope);
25512
- repos = repos.filter((r) => scopeSet.has(r.name));
25513
- }
25514
25514
  const semaphore = new Semaphore(4);
25515
25515
  const allRows = [];
25516
25516
  const processRepo = async (repo) => {
25517
25517
  await semaphore.acquire();
25518
+ let mainBranch = config2.default_main_branch;
25519
+ let hasMain = false;
25518
25520
  try {
25519
- const mainBranch = await resolveMainBranch(repo, config2);
25521
+ mainBranch = await resolveMainBranch(repo, config2);
25520
25522
  const wts = await getWorktreeList(repo.mainPath);
25521
25523
  let stackMetadata = { version: 1, branches: {} };
25522
25524
  try {
@@ -25531,7 +25533,9 @@ async function fetchWorktreeData(opts, scope) {
25531
25533
  if (wt.isBare)
25532
25534
  continue;
25533
25535
  let branch = wt.branch;
25534
- const isMainCheckout = wt.path === repo.mainPath;
25536
+ const isMainCheckout = safeResolve(wt.path) === safeResolve(repo.mainPath);
25537
+ if (isMainCheckout)
25538
+ hasMain = true;
25535
25539
  if (isMainCheckout && !branch) {
25536
25540
  branch = mainBranch;
25537
25541
  }
@@ -25562,6 +25566,7 @@ async function fetchWorktreeData(opts, scope) {
25562
25566
  };
25563
25567
  if (isMainCheckout) {
25564
25568
  allRows.push(row);
25569
+ prRows.push(row);
25565
25570
  continue;
25566
25571
  }
25567
25572
  await Promise.allSettled([
@@ -25622,8 +25627,56 @@ async function fetchWorktreeData(opts, scope) {
25622
25627
  if (forge && branches.length > 0) {
25623
25628
  prFetchJobs.push({ repo, forge, mainBranch, rows: prRows, branches, stackMetadata });
25624
25629
  }
25630
+ if (!hasMain) {
25631
+ allRows.push({
25632
+ repoName: repo.name,
25633
+ branch: mainBranch,
25634
+ path: repo.mainPath,
25635
+ commitShort: "",
25636
+ isMainCheckout: true,
25637
+ isLocked: false,
25638
+ isPrunable: false,
25639
+ isBare: false,
25640
+ dirtyFiles: [],
25641
+ ahead: null,
25642
+ behind: null,
25643
+ prNumber: null,
25644
+ prState: null,
25645
+ prChecks: null,
25646
+ prUrl: null,
25647
+ owner: null,
25648
+ rebaseStatus: null,
25649
+ depsStrategy: "none",
25650
+ base: undefined,
25651
+ baseChanged: false
25652
+ });
25653
+ }
25625
25654
  } catch (err) {
25626
25655
  warnings.push({ repoName: repo.name, message: `Failed to process repo ${repo.name}: ${errorMessage3(err)}` });
25656
+ if (!hasMain) {
25657
+ allRows.push({
25658
+ repoName: repo.name,
25659
+ branch: mainBranch,
25660
+ path: repo.mainPath,
25661
+ commitShort: "",
25662
+ isMainCheckout: true,
25663
+ isLocked: false,
25664
+ isPrunable: false,
25665
+ isBare: false,
25666
+ dirtyFiles: [],
25667
+ ahead: null,
25668
+ behind: null,
25669
+ prNumber: null,
25670
+ prState: null,
25671
+ prChecks: null,
25672
+ prUrl: null,
25673
+ owner: null,
25674
+ rebaseStatus: null,
25675
+ depsStrategy: "none",
25676
+ base: undefined,
25677
+ baseChanged: false
25678
+ });
25679
+ }
25627
25680
  } finally {
25628
25681
  semaphore.release();
25629
25682
  }
@@ -25650,6 +25703,7 @@ var init_data = __esm(() => {
25650
25703
  init_owner();
25651
25704
  init_types2();
25652
25705
  init_stack();
25706
+ init_path_safety();
25653
25707
  PR_CACHE_TTL_MS = 5 * 60 * 1000;
25654
25708
  prCache = new Map;
25655
25709
  });
@@ -25946,7 +26000,12 @@ var init_useWorktrees = __esm(() => {
25946
26000
  import { RGBA } from "@opentui/core";
25947
26001
  import { createContext, useContext } from "react";
25948
26002
  function useTheme() {
25949
- return useContext(ThemeContext);
26003
+ try {
26004
+ const ctx = useContext(ThemeContext);
26005
+ return ctx ?? tokens;
26006
+ } catch {
26007
+ return tokens;
26008
+ }
25950
26009
  }
25951
26010
  function truncateBranch(name, max = 42) {
25952
26011
  if (name.length <= max)
@@ -26101,18 +26160,18 @@ async function openInBrowser(url2) {
26101
26160
  // src/tui/components/WorktreeTable.tsx
26102
26161
  import { useEffect as useEffect2, useRef as useRef3 } from "react";
26103
26162
  import { jsx, jsxs } from "@opentui/react/jsx-runtime";
26104
- function statusBadge(row) {
26163
+ function statusBadge(row, theme) {
26105
26164
  if (row.isMainCheckout)
26106
- return { text: "[main]", fg: tokens.accent };
26165
+ return { text: "[main]", fg: theme.accent };
26107
26166
  if (row.isPrunable)
26108
- return { text: "missing", fg: tokens.error };
26167
+ return { text: "missing", fg: theme.error };
26109
26168
  if (row.isLocked)
26110
- return { text: "locked", fg: tokens.error };
26169
+ return { text: "locked", fg: theme.error };
26111
26170
  if (row.rebaseStatus)
26112
- return { text: "rebasing", fg: tokens.warning };
26171
+ return { text: "rebasing", fg: theme.warning };
26113
26172
  if (row.dirtyFiles.length > 0)
26114
- return { text: `dirty (${row.dirtyFiles.length})`, fg: tokens.warning };
26115
- return { text: "clean", fg: tokens.dim };
26173
+ return { text: `dirty (${row.dirtyFiles.length})`, fg: theme.warning };
26174
+ return { text: "clean", fg: theme.dim };
26116
26175
  }
26117
26176
  function WorktreeItem({
26118
26177
  row,
@@ -26125,6 +26184,7 @@ function WorktreeItem({
26125
26184
  onRowClick,
26126
26185
  onToggleSelect
26127
26186
  }) {
26187
+ const theme = useTheme();
26128
26188
  const prTap = useTapHandler(() => {
26129
26189
  if (row.prUrl)
26130
26190
  openInBrowser(row.prUrl);
@@ -26136,9 +26196,9 @@ function WorktreeItem({
26136
26196
  e.stopPropagation();
26137
26197
  onToggleSelect?.();
26138
26198
  });
26139
- const badge = indicator ? indicator.running ? { text: `${frame} ${indicator.verb}…`, fg: tokens.accent } : { text: `◌ ${indicator.verb}`, fg: tokens.dim } : row.isPendingCreate ? { text: `${frame} creating…`, fg: tokens.accent } : statusBadge(row);
26199
+ const badge = indicator ? indicator.running ? { text: `${frame} ${indicator.verb}…`, fg: theme.accent } : { text: `◌ ${indicator.verb}`, fg: theme.dim } : row.isPendingCreate ? { text: `${frame} creating…`, fg: theme.accent } : statusBadge(row, theme);
26140
26200
  const disabled = indicator !== undefined || row.isPendingCreate === true;
26141
- const primary = isSelected ? tokens.bright : disabled ? tokens.dim : tokens.fg;
26201
+ const primary = isSelected ? theme.bright : disabled ? theme.dim : theme.fg;
26142
26202
  const divergence = row.ahead !== null && row.behind !== null && (row.ahead > 0 || row.behind > 0) ? ` · ↑${row.ahead} ↓${row.behind}` : "";
26143
26203
  const ownerSegment = row.owner ? ` · by ${row.owner}` : "";
26144
26204
  const baseSegment = row.base ? ` · base ${row.base}` : "";
@@ -26156,7 +26216,7 @@ function WorktreeItem({
26156
26216
  return /* @__PURE__ */ jsxs("box", {
26157
26217
  id,
26158
26218
  flexDirection: "column",
26159
- backgroundColor: isSelected ? tokens.selectionBg : undefined,
26219
+ backgroundColor: isSelected ? theme.selectionBg : undefined,
26160
26220
  style: { paddingRight: 1 },
26161
26221
  ...isClickable && onRowClick ? rowTap : {},
26162
26222
  children: [
@@ -26173,18 +26233,18 @@ function WorktreeItem({
26173
26233
  width: 2,
26174
26234
  ...onToggleSelect ? gutterTap : {},
26175
26235
  children: /* @__PURE__ */ jsx("text", {
26176
- fg: tokens.accent,
26236
+ fg: theme.accent,
26177
26237
  children: isMultiSelected ? "✓ " : " "
26178
26238
  })
26179
26239
  }),
26180
26240
  /* @__PURE__ */ jsxs("text", {
26181
26241
  children: [
26182
26242
  /* @__PURE__ */ jsx("span", {
26183
- fg: tokens.dim,
26243
+ fg: theme.dim,
26184
26244
  children: hierarchyPrefix
26185
26245
  }),
26186
26246
  isWorkspaceMember && /* @__PURE__ */ jsx("span", {
26187
- fg: tokens.accent,
26247
+ fg: theme.accent,
26188
26248
  children: "⬡ "
26189
26249
  }),
26190
26250
  /* @__PURE__ */ jsx("span", {
@@ -26205,11 +26265,11 @@ function WorktreeItem({
26205
26265
  /* @__PURE__ */ jsxs("text", {
26206
26266
  children: [
26207
26267
  /* @__PURE__ */ jsx("span", {
26208
- fg: tokens.dim,
26268
+ fg: theme.dim,
26209
26269
  children: secondary
26210
26270
  }),
26211
26271
  row.prNumber !== null && secondary ? /* @__PURE__ */ jsx("span", {
26212
- fg: tokens.dim,
26272
+ fg: theme.dim,
26213
26273
  children: " · "
26214
26274
  }) : null
26215
26275
  ]
@@ -26219,26 +26279,26 @@ function WorktreeItem({
26219
26279
  children: /* @__PURE__ */ jsxs("text", {
26220
26280
  children: [
26221
26281
  /* @__PURE__ */ jsx("span", {
26222
- fg: tokens.accent,
26282
+ fg: theme.accent,
26223
26283
  children: `#${row.prNumber}`
26224
26284
  }),
26225
26285
  row.prState && /* @__PURE__ */ jsx("span", {
26226
- fg: tokens.dim,
26286
+ fg: theme.dim,
26227
26287
  children: ` ${row.prState}`
26228
26288
  }),
26229
26289
  row.prChecks && /* @__PURE__ */ jsx("span", {
26230
- fg: tokens.dim,
26290
+ fg: theme.dim,
26231
26291
  children: ` (${row.prChecks})`
26232
26292
  }),
26233
26293
  row.prUrl && /* @__PURE__ */ jsx("span", {
26234
- fg: tokens.dim,
26294
+ fg: theme.dim,
26235
26295
  children: " ↗"
26236
26296
  })
26237
26297
  ]
26238
26298
  })
26239
26299
  }),
26240
26300
  row.prNumber === null && row.prState === "FETCHING" && /* @__PURE__ */ jsxs("text", {
26241
- fg: tokens.dim,
26301
+ fg: theme.dim,
26242
26302
  children: [
26243
26303
  secondary ? " · " : "",
26244
26304
  "PR …"
@@ -26247,11 +26307,11 @@ function WorktreeItem({
26247
26307
  /* @__PURE__ */ jsxs("text", {
26248
26308
  children: [
26249
26309
  baseChangedSegment && /* @__PURE__ */ jsx("span", {
26250
- fg: tokens.warning,
26310
+ fg: theme.warning,
26251
26311
  children: baseChangedSegment
26252
26312
  }),
26253
26313
  rebaseSegment && /* @__PURE__ */ jsx("span", {
26254
- fg: tokens.error,
26314
+ fg: theme.error,
26255
26315
  children: rebaseSegment
26256
26316
  })
26257
26317
  ]
@@ -26262,6 +26322,7 @@ function WorktreeItem({
26262
26322
  });
26263
26323
  }
26264
26324
  function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repoVerbs, rowVerbs, favorites = [], workspaceMemberSet = null, activeWorkspaceName = null, onRowClick, onToggleSelect }) {
26325
+ const theme = useTheme();
26265
26326
  const favoriteSet = new Set(favorites);
26266
26327
  const scrollRef = useRef3(null);
26267
26328
  useEffect2(() => {
@@ -26285,8 +26346,8 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
26285
26346
  width: "100%",
26286
26347
  height: "100%",
26287
26348
  border: true,
26288
- borderColor: tokens.border,
26289
- focusedBorderColor: tokens.border,
26349
+ borderColor: theme.border,
26350
+ focusedBorderColor: theme.border,
26290
26351
  focusable: false,
26291
26352
  title: activeWorkspaceName ? `Repositories · workspace: ${activeWorkspaceName}` : "Repositories",
26292
26353
  paddingX: 1,
@@ -26295,7 +26356,7 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
26295
26356
  e.stopPropagation();
26296
26357
  },
26297
26358
  children: blocks.length === 0 ? /* @__PURE__ */ jsx("text", {
26298
- fg: tokens.dim,
26359
+ fg: theme.dim,
26299
26360
  children: "No repositories configured."
26300
26361
  }) : blocks.map((block) => {
26301
26362
  const repoIndicator = repoVerbs.get(block.repoName);
@@ -26308,22 +26369,22 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
26308
26369
  children: /* @__PURE__ */ jsxs("text", {
26309
26370
  children: [
26310
26371
  favoriteSet.has(block.repoName) && /* @__PURE__ */ jsx("span", {
26311
- fg: tokens.accent,
26372
+ fg: theme.accent,
26312
26373
  children: "★ "
26313
26374
  }),
26314
26375
  /* @__PURE__ */ jsx("span", {
26315
- fg: tokens.bright,
26376
+ fg: theme.bright,
26316
26377
  children: block.repoName
26317
26378
  }),
26318
26379
  block.rows.length > 0 && /* @__PURE__ */ jsx("span", {
26319
- fg: tokens.dim,
26380
+ fg: theme.dim,
26320
26381
  children: ` · ${block.rows.filter((r) => !r.isPendingCreate).length}`
26321
26382
  }),
26322
26383
  repoIndicator ? /* @__PURE__ */ jsx("span", {
26323
- fg: repoIndicator.running ? tokens.accent : tokens.dim,
26384
+ fg: repoIndicator.running ? theme.accent : theme.dim,
26324
26385
  children: repoIndicator.running ? ` ${frame} ${repoIndicator.verb}…` : ` ◌ ${repoIndicator.verb}`
26325
26386
  }) : block.rows.length === 0 ? /* @__PURE__ */ jsx("span", {
26326
- fg: tokens.accent,
26387
+ fg: theme.accent,
26327
26388
  children: ` ${frame} refreshing…`
26328
26389
  }) : null
26329
26390
  ]
@@ -26357,6 +26418,7 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
26357
26418
  }
26358
26419
  var SECONDARY_INDENT = " ";
26359
26420
  var init_WorktreeTable = __esm(() => {
26421
+ init_theme();
26360
26422
  init_theme();
26361
26423
  init_use_tap();
26362
26424
  init_utils();
@@ -26372,6 +26434,7 @@ function TabItem({
26372
26434
  onSelect,
26373
26435
  onClose
26374
26436
  }) {
26437
+ const theme = useTheme();
26375
26438
  const tap = useTapHandler(() => onSelect(id));
26376
26439
  const closeTap = useTapHandler((e) => {
26377
26440
  e.stopPropagation();
@@ -26383,15 +26446,15 @@ function TabItem({
26383
26446
  ...tap,
26384
26447
  children: [
26385
26448
  /* @__PURE__ */ jsx2("text", {
26386
- fg: active ? tokens.accent : tokens.dim,
26449
+ fg: active ? theme.accent : theme.dim,
26387
26450
  attributes: active ? 1 : 0,
26388
26451
  children: label
26389
26452
  }),
26390
26453
  closable ? /* @__PURE__ */ jsx2("box", {
26391
26454
  ...closeTap,
26392
- style: { marginLeft: 1 },
26455
+ style: { marginLeft: 1, paddingLeft: 1, paddingRight: 1 },
26393
26456
  children: /* @__PURE__ */ jsx2("text", {
26394
- fg: active ? tokens.accent : tokens.dim,
26457
+ fg: theme.accent,
26395
26458
  children: "✕"
26396
26459
  })
26397
26460
  }) : null,
@@ -26426,6 +26489,7 @@ var init_TabBar = __esm(() => {
26426
26489
  import { useState as useState2, useEffect as useEffect3, useRef as useRef4, useCallback as useCallback3 } from "react";
26427
26490
  import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
26428
26491
  function TerminalView({ session, focused, onFocus, onSend, onResize, registerListener, unregisterListener }) {
26492
+ const theme = useTheme();
26429
26493
  const [draft, setDraft] = useState2("");
26430
26494
  const focusTap = useTapHandler(() => onFocus());
26431
26495
  const termRef = useRef4(null);
@@ -26568,7 +26632,7 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26568
26632
  })
26569
26633
  }),
26570
26634
  session.exited !== null && /* @__PURE__ */ jsxs3("text", {
26571
- fg: tokens.warning,
26635
+ fg: theme.warning,
26572
26636
  children: [
26573
26637
  "— exited with code ",
26574
26638
  session.exited,
@@ -26576,7 +26640,7 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26576
26640
  ]
26577
26641
  }),
26578
26642
  !focused && /* @__PURE__ */ jsxs3("text", {
26579
- fg: tokens.dim,
26643
+ fg: theme.dim,
26580
26644
  children: [
26581
26645
  "Click to focus · Ctrl+G or click table to unfocus · ",
26582
26646
  session.label,
@@ -26584,7 +26648,7 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26584
26648
  ]
26585
26649
  }),
26586
26650
  focused && /* @__PURE__ */ jsx3("text", {
26587
- fg: tokens.accent,
26651
+ fg: theme.accent,
26588
26652
  children: "Focused PTY — all keys go to shell · Ctrl+G to unfocus · scroll wheel to view history"
26589
26653
  })
26590
26654
  ]
@@ -26602,12 +26666,12 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26602
26666
  children: [
26603
26667
  spawnError && /* @__PURE__ */ jsx3("box", {
26604
26668
  border: true,
26605
- borderColor: tokens.error,
26669
+ borderColor: theme.error,
26606
26670
  paddingX: 1,
26607
26671
  paddingY: 0,
26608
26672
  marginBottom: 1,
26609
26673
  children: /* @__PURE__ */ jsx3("text", {
26610
- fg: tokens.error,
26674
+ fg: theme.error,
26611
26675
  children: spawnError
26612
26676
  })
26613
26677
  }),
@@ -26622,14 +26686,14 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26622
26686
  onMouseScroll: handlePipeScroll,
26623
26687
  children: [
26624
26688
  lines.length === 0 ? /* @__PURE__ */ jsx3("text", {
26625
- fg: tokens.dim,
26689
+ fg: theme.dim,
26626
26690
  children: "No output yet — type a command below."
26627
26691
  }) : lines.slice(-500).map((line, idx) => /* @__PURE__ */ jsx3("text", {
26628
- fg: spawnError ? tokens.error : session.exited !== null ? tokens.dim : tokens.fg,
26692
+ fg: spawnError ? theme.error : session.exited !== null ? theme.dim : theme.fg,
26629
26693
  children: line || " "
26630
26694
  }, idx)),
26631
26695
  session.exited !== null && /* @__PURE__ */ jsxs3("text", {
26632
- fg: tokens.warning,
26696
+ fg: theme.warning,
26633
26697
  children: [
26634
26698
  "— exited with code ",
26635
26699
  session.exited,
@@ -26644,7 +26708,7 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26644
26708
  width: "100%",
26645
26709
  children: [
26646
26710
  /* @__PURE__ */ jsx3("text", {
26647
- fg: tokens.dim,
26711
+ fg: theme.dim,
26648
26712
  children: "$"
26649
26713
  }),
26650
26714
  /* @__PURE__ */ jsx3("box", {
@@ -26666,11 +26730,11 @@ function TerminalView({ session, focused, onFocus, onSend, onResize, registerLis
26666
26730
  ]
26667
26731
  }),
26668
26732
  !focused && /* @__PURE__ */ jsx3("text", {
26669
- fg: tokens.dim,
26733
+ fg: theme.dim,
26670
26734
  children: "Click terminal or press 't' to focus · Ctrl+G or click table to unfocus"
26671
26735
  }),
26672
26736
  focused && /* @__PURE__ */ jsx3("text", {
26673
- fg: tokens.accent,
26737
+ fg: theme.accent,
26674
26738
  children: "Focused — typing goes to shell · Ctrl+G to unfocus · max 5 per worktree persist across switches"
26675
26739
  })
26676
26740
  ]
@@ -26694,12 +26758,16 @@ function TabPane({
26694
26758
  onClose,
26695
26759
  allSessionsFlat,
26696
26760
  terminalFocused,
26761
+ changesFocused,
26762
+ onChangesFocus,
26697
26763
  activeTabId,
26698
26764
  recentSessionIds,
26699
26765
  terminalSessions
26700
26766
  }) {
26701
- const borderColor = focused ? tokens.accent : tokens.border;
26767
+ const theme = useTheme();
26768
+ const borderColor = focused ? theme.accent : theme.border;
26702
26769
  const mountedSessions = allSessionsFlat?.filter((s) => activeTabId === s.id || recentSessionIds?.has(s.id));
26770
+ const nonSessionTabs = tabs.filter((t) => !t.closable);
26703
26771
  return /* @__PURE__ */ jsxs4("box", {
26704
26772
  flexDirection: "column",
26705
26773
  flexGrow: 1,
@@ -26724,13 +26792,28 @@ function TabPane({
26724
26792
  width: "100%",
26725
26793
  flexDirection: "column",
26726
26794
  children: [
26727
- tabs.map((t) => /* @__PURE__ */ jsx4("box", {
26728
- flexGrow: 1,
26729
- width: "100%",
26730
- flexDirection: "column",
26731
- visible: t.id === activeId,
26732
- children: t.render({ worktree: selectedRow, isActive: t.id === activeId, focused: focused && t.id === activeId })
26733
- }, t.id)),
26795
+ nonSessionTabs.map((t) => {
26796
+ const isChanges = t.id === "changes";
26797
+ const tabFocused = isChanges ? !!changesFocused : !terminalFocused && !changesFocused && t.id === activeId;
26798
+ const content = t.render({ worktree: selectedRow, isActive: t.id === activeId, focused: tabFocused });
26799
+ if (isChanges) {
26800
+ return /* @__PURE__ */ jsx4("box", {
26801
+ flexGrow: 1,
26802
+ width: "100%",
26803
+ flexDirection: "column",
26804
+ visible: t.id === activeId,
26805
+ onMouseDown: () => onChangesFocus?.(),
26806
+ children: content
26807
+ }, t.id);
26808
+ }
26809
+ return /* @__PURE__ */ jsx4("box", {
26810
+ flexGrow: 1,
26811
+ width: "100%",
26812
+ flexDirection: "column",
26813
+ visible: t.id === activeId,
26814
+ children: content
26815
+ }, t.id);
26816
+ }),
26734
26817
  mountedSessions?.map((s) => /* @__PURE__ */ jsx4("box", {
26735
26818
  flexGrow: 1,
26736
26819
  width: "100%",
@@ -26747,8 +26830,8 @@ function TabPane({
26747
26830
  unregisterListener: terminalSessions?.unregisterListener
26748
26831
  })
26749
26832
  }, s.id)),
26750
- tabs.length === 0 && (!mountedSessions || mountedSessions.length === 0) && /* @__PURE__ */ jsx4("text", {
26751
- fg: tokens.dim,
26833
+ nonSessionTabs.length === 0 && (!mountedSessions || mountedSessions.length === 0) && /* @__PURE__ */ jsx4("text", {
26834
+ fg: theme.dim,
26752
26835
  children: "No tab"
26753
26836
  })
26754
26837
  ]
@@ -26765,6 +26848,7 @@ var init_TabPane = __esm(() => {
26765
26848
  // src/tui/components/DetailsTab.tsx
26766
26849
  import { jsx as jsx5, jsxs as jsxs5, Fragment } from "@opentui/react/jsx-runtime";
26767
26850
  function DetailsContent({ selectedRow }) {
26851
+ const theme = useTheme();
26768
26852
  const prTap = useTapHandler(() => {
26769
26853
  if (selectedRow?.prUrl)
26770
26854
  openInBrowser(selectedRow.prUrl);
@@ -26777,7 +26861,7 @@ function DetailsContent({ selectedRow }) {
26777
26861
  justifyContent: "center",
26778
26862
  alignItems: "center",
26779
26863
  children: /* @__PURE__ */ jsx5("text", {
26780
- fg: tokens.dim,
26864
+ fg: theme.dim,
26781
26865
  children: "No worktree selected"
26782
26866
  })
26783
26867
  });
@@ -26814,11 +26898,11 @@ function DetailsContent({ selectedRow }) {
26814
26898
  /* @__PURE__ */ jsxs5("text", {
26815
26899
  children: [
26816
26900
  /* @__PURE__ */ jsx5("span", {
26817
- fg: tokens.dim,
26901
+ fg: theme.dim,
26818
26902
  children: "Repo:"
26819
26903
  }),
26820
26904
  /* @__PURE__ */ jsxs5("span", {
26821
- fg: tokens.fg,
26905
+ fg: theme.fg,
26822
26906
  children: [
26823
26907
  " ",
26824
26908
  repoName
@@ -26829,11 +26913,11 @@ function DetailsContent({ selectedRow }) {
26829
26913
  /* @__PURE__ */ jsxs5("text", {
26830
26914
  children: [
26831
26915
  /* @__PURE__ */ jsx5("span", {
26832
- fg: tokens.dim,
26916
+ fg: theme.dim,
26833
26917
  children: "Branch:"
26834
26918
  }),
26835
26919
  /* @__PURE__ */ jsxs5("span", {
26836
- fg: tokens.fg,
26920
+ fg: theme.fg,
26837
26921
  children: [
26838
26922
  " ",
26839
26923
  branch,
@@ -26846,11 +26930,11 @@ function DetailsContent({ selectedRow }) {
26846
26930
  /* @__PURE__ */ jsxs5("text", {
26847
26931
  children: [
26848
26932
  /* @__PURE__ */ jsx5("span", {
26849
- fg: tokens.dim,
26933
+ fg: theme.dim,
26850
26934
  children: "Path:"
26851
26935
  }),
26852
26936
  /* @__PURE__ */ jsxs5("span", {
26853
- fg: tokens.fg,
26937
+ fg: theme.fg,
26854
26938
  children: [
26855
26939
  " ",
26856
26940
  path37
@@ -26861,11 +26945,11 @@ function DetailsContent({ selectedRow }) {
26861
26945
  /* @__PURE__ */ jsxs5("text", {
26862
26946
  children: [
26863
26947
  /* @__PURE__ */ jsx5("span", {
26864
- fg: tokens.dim,
26948
+ fg: theme.dim,
26865
26949
  children: "Commit:"
26866
26950
  }),
26867
26951
  /* @__PURE__ */ jsxs5("span", {
26868
- fg: tokens.fg,
26952
+ fg: theme.fg,
26869
26953
  children: [
26870
26954
  " ",
26871
26955
  commitShort
@@ -26876,11 +26960,11 @@ function DetailsContent({ selectedRow }) {
26876
26960
  owner && /* @__PURE__ */ jsxs5("text", {
26877
26961
  children: [
26878
26962
  /* @__PURE__ */ jsx5("span", {
26879
- fg: tokens.dim,
26963
+ fg: theme.dim,
26880
26964
  children: "Owner:"
26881
26965
  }),
26882
26966
  /* @__PURE__ */ jsxs5("span", {
26883
- fg: tokens.fg,
26967
+ fg: theme.fg,
26884
26968
  children: [
26885
26969
  " ",
26886
26970
  owner
@@ -26891,11 +26975,11 @@ function DetailsContent({ selectedRow }) {
26891
26975
  /* @__PURE__ */ jsxs5("text", {
26892
26976
  children: [
26893
26977
  /* @__PURE__ */ jsx5("span", {
26894
- fg: tokens.dim,
26978
+ fg: theme.dim,
26895
26979
  children: "Status:"
26896
26980
  }),
26897
26981
  /* @__PURE__ */ jsxs5("span", {
26898
- fg: tokens.fg,
26982
+ fg: theme.fg,
26899
26983
  children: [
26900
26984
  " ",
26901
26985
  isLocked ? "LOCKED" : isPrunable ? "PRUNABLE" : "Active"
@@ -26906,11 +26990,11 @@ function DetailsContent({ selectedRow }) {
26906
26990
  base2 && /* @__PURE__ */ jsxs5("text", {
26907
26991
  children: [
26908
26992
  /* @__PURE__ */ jsx5("span", {
26909
- fg: tokens.dim,
26993
+ fg: theme.dim,
26910
26994
  children: "Base:"
26911
26995
  }),
26912
26996
  /* @__PURE__ */ jsxs5("span", {
26913
- fg: tokens.accent,
26997
+ fg: theme.accent,
26914
26998
  children: [
26915
26999
  " ",
26916
27000
  base2
@@ -26921,11 +27005,11 @@ function DetailsContent({ selectedRow }) {
26921
27005
  baseChanged && /* @__PURE__ */ jsxs5("text", {
26922
27006
  children: [
26923
27007
  /* @__PURE__ */ jsx5("span", {
26924
- fg: tokens.dim,
27008
+ fg: theme.dim,
26925
27009
  children: "Base state:"
26926
27010
  }),
26927
27011
  /* @__PURE__ */ jsx5("span", {
26928
- fg: tokens.warning,
27012
+ fg: theme.warning,
26929
27013
  children: " moved since recorded"
26930
27014
  })
26931
27015
  ]
@@ -26934,11 +27018,11 @@ function DetailsContent({ selectedRow }) {
26934
27018
  style: { marginTop: 1 },
26935
27019
  children: [
26936
27020
  /* @__PURE__ */ jsx5("span", {
26937
- fg: tokens.dim,
27021
+ fg: theme.dim,
26938
27022
  children: base2 ? "vs base:" : "vs main:"
26939
27023
  }),
26940
27024
  /* @__PURE__ */ jsxs5("span", {
26941
- fg: tokens.fg,
27025
+ fg: theme.fg,
26942
27026
  children: [
26943
27027
  " ",
26944
27028
  aheadBehindStr
@@ -26949,11 +27033,11 @@ function DetailsContent({ selectedRow }) {
26949
27033
  /* @__PURE__ */ jsxs5("text", {
26950
27034
  children: [
26951
27035
  /* @__PURE__ */ jsx5("span", {
26952
- fg: tokens.dim,
27036
+ fg: theme.dim,
26953
27037
  children: "Deps:"
26954
27038
  }),
26955
27039
  /* @__PURE__ */ jsxs5("span", {
26956
- fg: tokens.fg,
27040
+ fg: theme.fg,
26957
27041
  children: [
26958
27042
  " ",
26959
27043
  depsStrategy
@@ -26964,11 +27048,11 @@ function DetailsContent({ selectedRow }) {
26964
27048
  rebaseStatus && /* @__PURE__ */ jsxs5("text", {
26965
27049
  children: [
26966
27050
  /* @__PURE__ */ jsx5("span", {
26967
- fg: tokens.dim,
27051
+ fg: theme.dim,
26968
27052
  children: "Rebase:"
26969
27053
  }),
26970
27054
  /* @__PURE__ */ jsxs5("span", {
26971
- fg: tokens.error,
27055
+ fg: theme.error,
26972
27056
  children: [
26973
27057
  " ",
26974
27058
  rebaseStatus
@@ -26982,7 +27066,7 @@ function DetailsContent({ selectedRow }) {
26982
27066
  style: { marginTop: 1 },
26983
27067
  children: [
26984
27068
  /* @__PURE__ */ jsxs5("span", {
26985
- fg: tokens.accent,
27069
+ fg: theme.accent,
26986
27070
  children: [
26987
27071
  "PR #",
26988
27072
  prNumber,
@@ -26990,7 +27074,7 @@ function DetailsContent({ selectedRow }) {
26990
27074
  ]
26991
27075
  }),
26992
27076
  /* @__PURE__ */ jsxs5("span", {
26993
- fg: tokens.dim,
27077
+ fg: theme.dim,
26994
27078
  children: [
26995
27079
  " ",
26996
27080
  prState
@@ -27001,11 +27085,11 @@ function DetailsContent({ selectedRow }) {
27001
27085
  prChecks && /* @__PURE__ */ jsxs5("text", {
27002
27086
  children: [
27003
27087
  /* @__PURE__ */ jsx5("span", {
27004
- fg: tokens.dim,
27088
+ fg: theme.dim,
27005
27089
  children: "Checks:"
27006
27090
  }),
27007
27091
  /* @__PURE__ */ jsxs5("span", {
27008
- fg: tokens.fg,
27092
+ fg: theme.fg,
27009
27093
  children: [
27010
27094
  " ",
27011
27095
  prChecks
@@ -27019,15 +27103,15 @@ function DetailsContent({ selectedRow }) {
27019
27103
  selectable: false,
27020
27104
  children: [
27021
27105
  /* @__PURE__ */ jsx5("span", {
27022
- fg: tokens.dim,
27106
+ fg: theme.dim,
27023
27107
  children: "URL:"
27024
27108
  }),
27025
27109
  /* @__PURE__ */ jsx5("span", {
27026
- fg: tokens.accent,
27110
+ fg: theme.accent,
27027
27111
  children: ` ${prUrl}`
27028
27112
  }),
27029
27113
  /* @__PURE__ */ jsx5("span", {
27030
- fg: tokens.dim,
27114
+ fg: theme.dim,
27031
27115
  children: " ↗ click"
27032
27116
  })
27033
27117
  ]
@@ -27040,7 +27124,7 @@ function DetailsContent({ selectedRow }) {
27040
27124
  style: { marginTop: 1 },
27041
27125
  children: [
27042
27126
  /* @__PURE__ */ jsxs5("text", {
27043
- fg: tokens.warning,
27127
+ fg: theme.warning,
27044
27128
  children: [
27045
27129
  "Dirty Files (",
27046
27130
  dirtyFiles.length,
@@ -27048,14 +27132,14 @@ function DetailsContent({ selectedRow }) {
27048
27132
  ]
27049
27133
  }),
27050
27134
  dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs5("text", {
27051
- fg: tokens.warning,
27135
+ fg: theme.warning,
27052
27136
  children: [
27053
27137
  " ",
27054
27138
  file2
27055
27139
  ]
27056
27140
  }, idx)),
27057
27141
  dirtyFiles.length > 20 && /* @__PURE__ */ jsxs5("text", {
27058
- fg: tokens.warning,
27142
+ fg: theme.warning,
27059
27143
  children: [
27060
27144
  " ...and ",
27061
27145
  dirtyFiles.length - 20,
@@ -27069,13 +27153,13 @@ function DetailsContent({ selectedRow }) {
27069
27153
  style: { marginTop: 1 },
27070
27154
  children: [
27071
27155
  /* @__PURE__ */ jsx5("text", {
27072
- fg: tokens.dim,
27156
+ fg: theme.dim,
27073
27157
  children: "Actions:"
27074
27158
  }),
27075
27159
  !isMainCheckout && /* @__PURE__ */ jsxs5(Fragment, {
27076
27160
  children: [
27077
27161
  /* @__PURE__ */ jsxs5("text", {
27078
- fg: tokens.dim,
27162
+ fg: theme.dim,
27079
27163
  children: [
27080
27164
  " i install deps (",
27081
27165
  depsStrategy,
@@ -27083,21 +27167,21 @@ function DetailsContent({ selectedRow }) {
27083
27167
  ]
27084
27168
  }),
27085
27169
  /* @__PURE__ */ jsx5("text", {
27086
- fg: tokens.dim,
27170
+ fg: theme.dim,
27087
27171
  children: " m rename worktree"
27088
27172
  })
27089
27173
  ]
27090
27174
  }),
27091
27175
  /* @__PURE__ */ jsx5("text", {
27092
- fg: tokens.dim,
27176
+ fg: theme.dim,
27093
27177
  children: " p pull branch"
27094
27178
  }),
27095
27179
  /* @__PURE__ */ jsx5("text", {
27096
- fg: tokens.dim,
27180
+ fg: theme.dim,
27097
27181
  children: " b rebase · s sync · o open in IDE · d remove"
27098
27182
  }),
27099
27183
  /* @__PURE__ */ jsx5("text", {
27100
- fg: tokens.dim,
27184
+ fg: theme.dim,
27101
27185
  children: " t new terminal session (max 5)"
27102
27186
  })
27103
27187
  ]
@@ -27111,6 +27195,8 @@ var init_DetailsTab = __esm(() => {
27111
27195
  });
27112
27196
 
27113
27197
  // src/lib/changes.ts
27198
+ import fs33 from "node:fs";
27199
+ import path37 from "node:path";
27114
27200
  function cacheKey(opts, filePath) {
27115
27201
  return [opts.repoPath, opts.branch, opts.scope, filePath ?? ""].join("\x00");
27116
27202
  }
@@ -27214,7 +27300,7 @@ async function getChangedFiles(opts) {
27214
27300
  return [];
27215
27301
  const key = cacheKey(opts);
27216
27302
  const cached2 = listCache.get(key);
27217
- if (!opts.invalidate && cached2?.head === head)
27303
+ if (!opts.invalidate && cached2?.head === head && opts.scope !== "worktree")
27218
27304
  return cached2.value;
27219
27305
  try {
27220
27306
  const baseArgs = await diffArgs(opts);
@@ -27231,6 +27317,29 @@ async function getChangedFiles(opts) {
27231
27317
  if (!await isSubmodule(opts.repoPath, file2.path))
27232
27318
  filtered.push(file2);
27233
27319
  }
27320
+ if (opts.scope === "worktree") {
27321
+ try {
27322
+ const untrackedRaw = await gitExec(["-C", opts.repoPath, "ls-files", "--others", "--exclude-standard", "-z"]);
27323
+ const untrackedPaths = untrackedRaw.split("\x00").filter(Boolean);
27324
+ for (const p of untrackedPaths) {
27325
+ if (filtered.some((f) => f.path === p))
27326
+ continue;
27327
+ if (await isSubmodule(opts.repoPath, p))
27328
+ continue;
27329
+ try {
27330
+ const full = path37.join(opts.repoPath, p);
27331
+ const buf = fs33.readFileSync(full);
27332
+ const binary = buf.includes(0);
27333
+ const str = binary ? "" : buf.toString("utf8");
27334
+ const added = binary ? 0 : str.split(`
27335
+ `).length;
27336
+ filtered.push({ path: p, status: "A", added, removed: 0, binary });
27337
+ } catch {
27338
+ filtered.push({ path: p, status: "A", added: 1, removed: 0, binary: false });
27339
+ }
27340
+ }
27341
+ } catch {}
27342
+ }
27234
27343
  listCache.set(key, { head, value: filtered });
27235
27344
  return filtered;
27236
27345
  } catch {
@@ -27266,7 +27375,26 @@ async function getFileDiff(opts) {
27266
27375
  const args = await diffArgs(opts, opts.filePath);
27267
27376
  if (!args)
27268
27377
  return empty;
27269
- const rawDiff = await gitExec(args);
27378
+ let rawDiff = await gitExec(args);
27379
+ if (!rawDiff && opts.scope === "worktree" && changedFile.status === "A") {
27380
+ try {
27381
+ const full = path37.join(opts.repoPath, opts.filePath);
27382
+ const buf = fs33.readFileSync(full);
27383
+ if (!buf.includes(0)) {
27384
+ const content = buf.toString("utf8");
27385
+ const lines = content.split(`
27386
+ `);
27387
+ const header = `diff --git a/${opts.filePath} b/${opts.filePath}
27388
+ new file mode 100644
27389
+ --- /dev/null
27390
+ +++ b/${opts.filePath}
27391
+ @@ -0,0 +1,${lines.length} @@
27392
+ `;
27393
+ rawDiff = header + lines.map((l) => `+${l}`).join(`
27394
+ `);
27395
+ }
27396
+ } catch {}
27397
+ }
27270
27398
  const { diff, truncated } = truncateDiff(rawDiff);
27271
27399
  const value = { ...empty, diff, truncated };
27272
27400
  diffCache.set(key, { head, value });
@@ -27932,7 +28060,9 @@ var init_Divider = __esm(() => {
27932
28060
 
27933
28061
  // src/tui/components/Overlay.tsx
27934
28062
  import { jsx as jsx9 } from "@opentui/react/jsx-runtime";
27935
- function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
28063
+ function Overlay({ title, borderColor, width = 64, children }) {
28064
+ const theme = useTheme();
28065
+ const resolvedBorder = borderColor ?? theme.border;
27936
28066
  return /* @__PURE__ */ jsx9("box", {
27937
28067
  id: "overlay-scrim",
27938
28068
  position: "absolute",
@@ -27940,7 +28070,7 @@ function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
27940
28070
  height: "100%",
27941
28071
  top: 0,
27942
28072
  left: 0,
27943
- backgroundColor: tokens.scrim,
28073
+ backgroundColor: theme.scrim,
27944
28074
  zIndex: 100,
27945
28075
  flexDirection: "row",
27946
28076
  justifyContent: "center",
@@ -27949,13 +28079,13 @@ function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
27949
28079
  id: "overlay-panel",
27950
28080
  width,
27951
28081
  border: true,
27952
- borderColor,
28082
+ borderColor: resolvedBorder,
27953
28083
  title,
27954
- titleColor: borderColor,
28084
+ titleColor: resolvedBorder,
27955
28085
  paddingX: 2,
27956
28086
  paddingY: 1,
27957
28087
  flexDirection: "column",
27958
- backgroundColor: tokens.panelBg,
28088
+ backgroundColor: theme.panelBg,
27959
28089
  children
27960
28090
  })
27961
28091
  });
@@ -27967,85 +28097,132 @@ var init_Overlay = __esm(() => {
27967
28097
  // src/tui/components/HelpOverlay.tsx
27968
28098
  import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
27969
28099
  function HelpOverlay() {
28100
+ const theme = useTheme();
27970
28101
  return /* @__PURE__ */ jsxs8(Overlay, {
27971
28102
  title: "Help",
27972
- borderColor: tokens.border,
27973
- width: 84,
28103
+ borderColor: theme.border,
28104
+ width: 100,
27974
28105
  children: [
27975
28106
  /* @__PURE__ */ jsx10("box", {
27976
28107
  flexDirection: "column",
27977
- style: { maxHeight: 36 },
28108
+ style: { maxHeight: 38 },
27978
28109
  children: /* @__PURE__ */ jsx10("scrollbox", {
27979
28110
  flexGrow: 1,
27980
- children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs8("box", {
27981
- flexDirection: "row",
27982
- gap: 1,
28111
+ children: HELP_GROUPS.map((group) => /* @__PURE__ */ jsxs8("box", {
28112
+ flexDirection: "column",
28113
+ style: { marginBottom: 1 },
27983
28114
  children: [
27984
28115
  /* @__PURE__ */ jsx10("box", {
27985
- width: 18,
27986
- flexShrink: 0,
27987
- justifyContent: "flex-end",
28116
+ style: { marginBottom: 1 },
27988
28117
  children: /* @__PURE__ */ jsx10("text", {
27989
- fg: tokens.accent,
27990
- children: key
28118
+ fg: theme.accent,
28119
+ attributes: 1,
28120
+ children: group.title
27991
28121
  })
27992
28122
  }),
27993
- /* @__PURE__ */ jsx10("text", {
27994
- fg: tokens.dim,
27995
- children: "-"
27996
- }),
27997
- /* @__PURE__ */ jsx10("box", {
27998
- flexGrow: 1,
27999
- children: /* @__PURE__ */ jsx10("text", {
28000
- fg: tokens.fg,
28001
- children: desc
28002
- })
28003
- })
28123
+ group.entries.map(([key, desc]) => /* @__PURE__ */ jsxs8("box", {
28124
+ flexDirection: "row",
28125
+ gap: 1,
28126
+ children: [
28127
+ /* @__PURE__ */ jsx10("box", {
28128
+ width: 22,
28129
+ flexShrink: 0,
28130
+ justifyContent: "flex-end",
28131
+ children: /* @__PURE__ */ jsx10("text", {
28132
+ fg: theme.accent,
28133
+ children: key
28134
+ })
28135
+ }),
28136
+ /* @__PURE__ */ jsx10("text", {
28137
+ fg: theme.dim,
28138
+ children: "-"
28139
+ }),
28140
+ /* @__PURE__ */ jsx10("box", {
28141
+ flexGrow: 1,
28142
+ children: /* @__PURE__ */ jsx10("text", {
28143
+ fg: theme.fg,
28144
+ children: desc
28145
+ })
28146
+ })
28147
+ ]
28148
+ }, key))
28004
28149
  ]
28005
- }, key))
28150
+ }, group.title))
28006
28151
  })
28007
28152
  }),
28008
28153
  /* @__PURE__ */ jsx10("text", {
28009
- style: { marginTop: 1, fg: tokens.dim },
28154
+ style: { marginTop: 1, fg: theme.dim },
28010
28155
  children: "Press any key to close"
28011
28156
  })
28012
28157
  ]
28013
28158
  });
28014
28159
  }
28015
- var HELP_ENTRIES;
28160
+ var HELP_GROUPS;
28016
28161
  var init_HelpOverlay = __esm(() => {
28017
28162
  init_Overlay();
28018
28163
  init_theme();
28019
- HELP_ENTRIES = [
28020
- ["↑/↓, k/j", "Navigate list"],
28021
- ["/", "Filter list"],
28022
- ["Space", "Multi-select"],
28023
- ["c", "Open configuration"],
28024
- ["n", "Create new worktree (pick dependency strategy)"],
28025
- ["f", "Fetch main for selected repo(s)"],
28026
- ["F (shift+f)", "Pin/unpin selected repo (favorites float to top)"],
28027
- ["W (shift+w)", "Scope list to a workspace's members (press again to clear)"],
28028
- ["p", "Pull latest changes for selected branch(es)"],
28029
- ["P (shift+p)", "Pull PR by link (force-override if exists)"],
28030
- ["b", "Rebase selected onto base (or main)"],
28031
- ["s", "Sync selected (env files + hooks)"],
28032
- ["s / Tab (Changes)", "Cycle changes scope: worktree staged → base"],
28033
- ["i", "Install dependencies in selection (worktrees and main)"],
28034
- ["m", "Rename selected worktree (branch + directory)"],
28035
- ["o", "Open selected in IDE"],
28036
- ["t", "New terminal session (max 5 per worktree, Details ↔ Session tabs)"],
28037
- ["T (shift+t)", "Cycle theme presets"],
28038
- ["click tab", "Switch Details Session (professional tabs with ▎ active)"],
28039
- ["Ctrl+G / click table", "Leave terminal focus focused terminal gets all keys"],
28040
- ["d", "Remove selected worktree(s)"],
28041
- ["e", "View data warnings (when count > 0)"],
28042
- ["r", "Refresh data"],
28043
- ["H", "Action history"],
28044
- ["mouse drag", "Select text — copied to clipboard automatically"],
28045
- ["ctrl+shift+c", "Copy selection again (terminals reserve cmd+c)"],
28046
- ["click PR #/URL", "Open pull request in browser"],
28047
- ["?", "Toggle help"],
28048
- ["q/esc", "Quit"]
28164
+ HELP_GROUPS = [
28165
+ {
28166
+ title: "Navigation & Filtering",
28167
+ entries: [
28168
+ ["↑/↓, k/j", "Navigate list"],
28169
+ ["/", "Filter list"],
28170
+ ["Space", "Multi-select"],
28171
+ ["q/esc", "Quit"]
28172
+ ]
28173
+ },
28174
+ {
28175
+ title: "Worktree Lifecycle",
28176
+ entries: [
28177
+ ["n", "Create new worktree (pick dependency strategy)"],
28178
+ ["d", "Remove selected worktree(s)"],
28179
+ ["m", "Rename selected worktree (branch + directory)"],
28180
+ ["o", "Open selected in IDE"],
28181
+ ["f", "Fetch main for selected repo(s)"],
28182
+ ["p", "Pull latest changes for selected branch(es)"],
28183
+ ["P (shift+p)", "Pull PR by link (force-override if exists)"],
28184
+ ["b", "Rebase selected onto base (or main)"],
28185
+ ["s", "Sync selected (env files + hooks)"],
28186
+ ["i", "Install dependencies in selection (worktrees and main)"]
28187
+ ]
28188
+ },
28189
+ {
28190
+ title: "Favorites & Workspaces",
28191
+ entries: [
28192
+ ["F (shift+f)", "Pin/unpin selected repo (favorites float to top)"],
28193
+ ["W (shift+w)", "Scope list to a workspace's members (press again to clear)"]
28194
+ ]
28195
+ },
28196
+ {
28197
+ title: "Terminal & Tabs",
28198
+ entries: [
28199
+ ["t", "New terminal session (max 5 per worktree)"],
28200
+ ["click tab", "Switch Details / Changes / Session tabs"],
28201
+ ["Ctrl+G / click table", "Leave terminal focus — focused terminal gets all keys"]
28202
+ ]
28203
+ },
28204
+ {
28205
+ title: "Changes Explorer (only for dirty worktrees)",
28206
+ entries: [
28207
+ ["click Changes", "Focus pane — j/k navigates files, s/Tab cycles scope"],
28208
+ ["s / Tab", "Cycle scope: worktree (dirty+untracked) → staged → base (vs base branch)"],
28209
+ ["j / k, ↑/↓", "Navigate files (when Changes focused, Ctrl+G/Esc to unfocus)"]
28210
+ ]
28211
+ },
28212
+ {
28213
+ title: "View & System",
28214
+ entries: [
28215
+ ["T (shift+t)", "Cycle theme presets"],
28216
+ ["c", "Open configuration"],
28217
+ ["e", "View data warnings (when count > 0)"],
28218
+ ["r", "Refresh data"],
28219
+ ["H", "Action history"],
28220
+ ["?", "Toggle help"],
28221
+ ["mouse drag", "Select text — copied to clipboard automatically"],
28222
+ ["ctrl+shift+c", "Copy selection again (terminals reserve cmd+c)"],
28223
+ ["click PR #/URL", "Open pull request in browser"]
28224
+ ]
28225
+ }
28049
28226
  ];
28050
28227
  });
28051
28228
 
@@ -28231,38 +28408,38 @@ var init_ActionLogModal = __esm(() => {
28231
28408
  });
28232
28409
 
28233
28410
  // src/lib/history.ts
28234
- import fs33 from "fs";
28235
- import path37 from "path";
28411
+ import fs34 from "fs";
28412
+ import path38 from "path";
28236
28413
  import os4 from "os";
28237
28414
  function getHistoryDir() {
28238
- const stateHome = process.env.XDG_STATE_HOME ?? path37.join(os4.homedir(), ".local", "state");
28239
- return path37.join(stateHome, "wtx");
28415
+ const stateHome = process.env.XDG_STATE_HOME ?? path38.join(os4.homedir(), ".local", "state");
28416
+ return path38.join(stateHome, "wtx");
28240
28417
  }
28241
28418
  function getHistoryPath() {
28242
- return path37.join(getHistoryDir(), "history.jsonl");
28419
+ return path38.join(getHistoryDir(), "history.jsonl");
28243
28420
  }
28244
28421
  function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_KEEP_LINES) {
28245
28422
  const historyPath = getHistoryPath();
28246
28423
  let size = 0;
28247
28424
  try {
28248
- size = fs33.statSync(historyPath).size;
28425
+ size = fs34.statSync(historyPath).size;
28249
28426
  } catch {
28250
28427
  return;
28251
28428
  }
28252
28429
  if (size <= maxBytes)
28253
28430
  return;
28254
- const lines = fs33.readFileSync(historyPath, "utf-8").split(`
28431
+ const lines = fs34.readFileSync(historyPath, "utf-8").split(`
28255
28432
  `).filter(Boolean);
28256
28433
  const kept = lines.slice(-keepLines);
28257
28434
  const tmpPath = `${historyPath}.tmp.${process.pid}`;
28258
28435
  try {
28259
- fs33.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
28436
+ fs34.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
28260
28437
  `)}
28261
28438
  ` : "", "utf-8");
28262
- fs33.renameSync(tmpPath, historyPath);
28439
+ fs34.renameSync(tmpPath, historyPath);
28263
28440
  } catch (err) {
28264
- if (fs33.existsSync(tmpPath)) {
28265
- fs33.unlinkSync(tmpPath);
28441
+ if (fs34.existsSync(tmpPath)) {
28442
+ fs34.unlinkSync(tmpPath);
28266
28443
  }
28267
28444
  throw err;
28268
28445
  }
@@ -28270,18 +28447,18 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
28270
28447
  function appendHistory(entry) {
28271
28448
  try {
28272
28449
  const dir = getHistoryDir();
28273
- if (!fs33.existsSync(dir)) {
28274
- fs33.mkdirSync(dir, { recursive: true });
28450
+ if (!fs34.existsSync(dir)) {
28451
+ fs34.mkdirSync(dir, { recursive: true });
28275
28452
  }
28276
28453
  rotateHistory();
28277
- fs33.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
28454
+ fs34.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
28278
28455
  `, "utf-8");
28279
28456
  } catch {}
28280
28457
  }
28281
28458
  function readRecentHistory(limit = 50) {
28282
28459
  let content;
28283
28460
  try {
28284
- content = fs33.readFileSync(getHistoryPath(), "utf-8");
28461
+ content = fs34.readFileSync(getHistoryPath(), "utf-8");
28285
28462
  } catch {
28286
28463
  return [];
28287
28464
  }
@@ -28419,13 +28596,19 @@ function InputModal({ title, placeholder, initialValue, errorMessage: errorMessa
28419
28596
  borderColor: tokens.accent,
28420
28597
  children: /* @__PURE__ */ jsxs12("box", {
28421
28598
  flexDirection: "column",
28599
+ width: "100%",
28422
28600
  children: [
28423
- /* @__PURE__ */ jsx14("input", {
28424
- focused: true,
28425
- value,
28426
- placeholder,
28427
- onInput: (val) => setValue(val),
28428
- onSubmit: () => onSubmit(value)
28601
+ /* @__PURE__ */ jsx14("box", {
28602
+ flexGrow: 1,
28603
+ width: "100%",
28604
+ children: /* @__PURE__ */ jsx14("input", {
28605
+ focused: true,
28606
+ value,
28607
+ placeholder,
28608
+ onInput: (val) => setValue(val),
28609
+ onSubmit: () => onSubmit(value),
28610
+ width: "100%"
28611
+ })
28429
28612
  }),
28430
28613
  errorMessage4 ? /* @__PURE__ */ jsx14("text", {
28431
28614
  fg: tokens.error,
@@ -28992,8 +29175,8 @@ function hasDirectStdinWrite(proc) {
28992
29175
  function hasWritableStreamStdin(proc) {
28993
29176
  return !!proc && proc.stdin instanceof WritableStream;
28994
29177
  }
28995
- function worktreeKeyFor(repoName, branch, path38) {
28996
- return path38 || `${repoName}:${branch}`;
29178
+ function worktreeKeyFor(repoName, branch, path39) {
29179
+ return path39 || `${repoName}:${branch}`;
28997
29180
  }
28998
29181
  function nextTerminalSessionLabel(existingCount) {
28999
29182
  return `Session ${existingCount + 1}`;
@@ -29011,13 +29194,13 @@ function useTerminalSessions() {
29011
29194
  const [sessionsByKey, setSessionsByKey] = useState10(() => new Map);
29012
29195
  const procsRef = useRef7(new Map);
29013
29196
  const listenersRef = useRef7(new Map);
29014
- const getSessions = useCallback6((repoName, branch, path38) => {
29015
- const key = worktreeKeyFor(repoName, branch, path38);
29197
+ const getSessions = useCallback6((repoName, branch, path39) => {
29198
+ const key = worktreeKeyFor(repoName, branch, path39);
29016
29199
  return sessionsByKey.get(key) ?? [];
29017
29200
  }, [sessionsByKey]);
29018
- const getKey = useCallback6((repoName, branch, path38) => worktreeKeyFor(repoName, branch, path38), []);
29019
- const createSession = useCallback6((repoName, branch, path38, opts) => {
29020
- const key = worktreeKeyFor(repoName, branch, path38);
29201
+ const getKey = useCallback6((repoName, branch, path39) => worktreeKeyFor(repoName, branch, path39), []);
29202
+ const createSession = useCallback6((repoName, branch, path39, opts) => {
29203
+ const key = worktreeKeyFor(repoName, branch, path39);
29021
29204
  const existing = sessionsByKey.get(key) ?? [];
29022
29205
  if (existing.length >= MAX_TERMINAL_SESSIONS) {
29023
29206
  return { session: null, error: `Max ${MAX_TERMINAL_SESSIONS} sessions per worktree` };
@@ -29030,7 +29213,7 @@ function useTerminalSessions() {
29030
29213
  id,
29031
29214
  label,
29032
29215
  worktreeKey: key,
29033
- worktreePath: path38,
29216
+ worktreePath: path39,
29034
29217
  repoName,
29035
29218
  branch,
29036
29219
  lines: [],
@@ -29115,7 +29298,7 @@ function useTerminalSessions() {
29115
29298
  throw new Error("pty not available");
29116
29299
  }
29117
29300
  const proc = Bun.spawn([shell], {
29118
- cwd: path38 || process.cwd(),
29301
+ cwd: path39 || process.cwd(),
29119
29302
  env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor", WTX_SESSION: label },
29120
29303
  terminal: {
29121
29304
  cols,
@@ -29150,7 +29333,7 @@ function useTerminalSessions() {
29150
29333
  session.usePty = false;
29151
29334
  try {
29152
29335
  const proc = Bun.spawn([shell], {
29153
- cwd: path38 || process.cwd(),
29336
+ cwd: path39 || process.cwd(),
29154
29337
  stdin: "pipe",
29155
29338
  stdout: "pipe",
29156
29339
  stderr: "pipe",
@@ -29198,9 +29381,9 @@ function useTerminalSessions() {
29198
29381
  } catch {}
29199
29382
  }
29200
29383
  if (session.usePty) {
29201
- session.lines = [`${label} — ${path38} (${shell}) [pty]`, ""];
29384
+ session.lines = [`${label} — ${path39} (${shell}) [pty]`, ""];
29202
29385
  } else if (!session.spawnError && session.lines.length === 0) {
29203
- session.lines = [`${label} — ${path38} (${shell})`, ""];
29386
+ session.lines = [`${label} — ${path39} (${shell})`, ""];
29204
29387
  }
29205
29388
  setSessionsByKey((prev) => {
29206
29389
  const next = new Map(prev);
@@ -29210,8 +29393,8 @@ function useTerminalSessions() {
29210
29393
  });
29211
29394
  return { session };
29212
29395
  }, [sessionsByKey]);
29213
- const removeSession = useCallback6((repoName, branch, path38, id) => {
29214
- const key = worktreeKeyFor(repoName, branch, path38);
29396
+ const removeSession = useCallback6((repoName, branch, path39, id) => {
29397
+ const key = worktreeKeyFor(repoName, branch, path39);
29215
29398
  setSessionsByKey((prev) => {
29216
29399
  const next = new Map(prev);
29217
29400
  const list = next.get(key);
@@ -29240,8 +29423,8 @@ function useTerminalSessions() {
29240
29423
  return next;
29241
29424
  });
29242
29425
  }, []);
29243
- const sendInput = useCallback6((repoName, branch, path38, id, data) => {
29244
- const key = worktreeKeyFor(repoName, branch, path38);
29426
+ const sendInput = useCallback6((repoName, branch, path39, id, data) => {
29427
+ const key = worktreeKeyFor(repoName, branch, path39);
29245
29428
  const list = sessionsByKey.get(key) ?? [];
29246
29429
  const sess = list.find((s) => s.id === id);
29247
29430
  if (!sess?.proc)
@@ -29289,8 +29472,8 @@ function useTerminalSessions() {
29289
29472
  }
29290
29473
  }
29291
29474
  }, [sessionsByKey]);
29292
- const resizeSession = useCallback6((repoName, branch, path38, id, cols, rows) => {
29293
- const key = worktreeKeyFor(repoName, branch, path38);
29475
+ const resizeSession = useCallback6((repoName, branch, path39, id, cols, rows) => {
29476
+ const key = worktreeKeyFor(repoName, branch, path39);
29294
29477
  setSessionsByKey((prev) => {
29295
29478
  const list = prev.get(key);
29296
29479
  if (!list)
@@ -29682,6 +29865,7 @@ function App({ opts }) {
29682
29865
  const [activeTabByWorktree, setActiveTabByWorktree] = useState11(() => new Map);
29683
29866
  const [recentTerminalSessionIds, setRecentTerminalSessionIds] = useState11([]);
29684
29867
  const [terminalFocused, setTerminalFocused] = useState11(false);
29868
+ const [changesFocused, setChangesFocused] = useState11(false);
29685
29869
  const { width: termW, height: termH } = useTerminalDimensions();
29686
29870
  const totalWidth = termW || renderer.width || 80;
29687
29871
  const totalHeight = termH || renderer.terminalHeight || 24;
@@ -29799,7 +29983,17 @@ function App({ opts }) {
29799
29983
  const baseFiltered = useMemo3(() => blocks.map((b) => ({
29800
29984
  ...b,
29801
29985
  rows: b.rows.filter((r) => matchesFilter(r, filterText) && matchesWorkspace(r, workspaceMemberSet))
29802
- })).filter((b) => b.rows.length > 0 || workspaceMemberSet === null && pendingRepos.includes(b.repoName)), [blocks, filterText, pendingRepos, workspaceMemberSet]);
29986
+ })).filter((b) => {
29987
+ if (b.rows.length > 0)
29988
+ return true;
29989
+ if (workspaceMemberSet !== null)
29990
+ return false;
29991
+ if (pendingRepos.includes(b.repoName))
29992
+ return true;
29993
+ if (!filterText)
29994
+ return true;
29995
+ return matchesFilter({ repoName: b.repoName, branch: "" }, filterText);
29996
+ }), [blocks, filterText, pendingRepos, workspaceMemberSet]);
29803
29997
  const missingMemberWarnings = useMemo3(() => {
29804
29998
  if (!activeWorkspace)
29805
29999
  return [];
@@ -29818,7 +30012,7 @@ function App({ opts }) {
29818
30012
  const displayBlocks = useMemo3(() => sortBlocks([
29819
30013
  ...withCreatePlaceholders(baseFiltered, ops.filter((o2) => o2.branch !== undefined).map((o2) => ({ repoName: o2.repoNames[0], branch: o2.branch }))),
29820
30014
  ...pendingBlocks
29821
- ]), [baseFiltered, ops, pendingBlocks]);
30015
+ ], favorites), [baseFiltered, ops, pendingBlocks, favorites]);
29822
30016
  const flatRows = useMemo3(() => displayBlocks.flatMap((b) => b.rows.filter((r) => !r.isPendingCreate)), [displayBlocks]);
29823
30017
  const totalRows = blocks.flatMap((b) => b.rows).length;
29824
30018
  const maxIndex = Math.max(0, flatRows.length - 1);
@@ -29843,7 +30037,15 @@ function App({ opts }) {
29843
30037
  }
29844
30038
  }, [worktreeKey, activeTabByWorktree]);
29845
30039
  useEffect9(() => {
29846
- if (sessionsForSelected.length === 0 && activeTabId !== "details") {
30040
+ if (activeTabId === "changes" && selectedRow && (selectedRow.isMainCheckout || selectedRow.dirtyFiles.length === 0)) {
30041
+ setActiveTabByWorktree((prev) => {
30042
+ const next = new Map(prev);
30043
+ next.set(worktreeKey, "details");
30044
+ return next;
30045
+ });
30046
+ return;
30047
+ }
30048
+ if (sessionsForSelected.length === 0 && activeTabId !== "details" && activeTabId !== "changes") {
29847
30049
  setActiveTabByWorktree((prev) => {
29848
30050
  const next = new Map(prev);
29849
30051
  next.set(worktreeKey, "details");
@@ -29851,7 +30053,7 @@ function App({ opts }) {
29851
30053
  });
29852
30054
  setTerminalFocused(false);
29853
30055
  } else if (sessionsForSelected.length > 0) {
29854
- const exists = sessionsForSelected.some((s) => s.id === activeTabId) || activeTabId === "details";
30056
+ const exists = sessionsForSelected.some((s) => s.id === activeTabId) || activeTabId === "details" || selectedRow && !selectedRow.isMainCheckout && selectedRow.dirtyFiles.length > 0 && activeTabId === "changes";
29855
30057
  if (!exists) {
29856
30058
  setActiveTabByWorktree((prev) => {
29857
30059
  const next = new Map(prev);
@@ -29860,7 +30062,15 @@ function App({ opts }) {
29860
30062
  });
29861
30063
  }
29862
30064
  }
29863
- }, [sessionsForSelected, activeTabId, worktreeKey]);
30065
+ }, [sessionsForSelected, activeTabId, worktreeKey, selectedRow]);
30066
+ useEffect9(() => {
30067
+ setChangesFocused(false);
30068
+ setTerminalFocused(false);
30069
+ }, [worktreeKey]);
30070
+ useEffect9(() => {
30071
+ if (activeTabId !== "changes")
30072
+ setChangesFocused(false);
30073
+ }, [activeTabId]);
29864
30074
  usePaste((event) => {
29865
30075
  if (!terminalFocused)
29866
30076
  return;
@@ -29884,7 +30094,8 @@ function App({ opts }) {
29884
30094
  return next;
29885
30095
  });
29886
30096
  setTerminalFocused(isSession);
29887
- }, [activeTabId, renderer, worktreeKey, sessionsForSelected]);
30097
+ setChangesFocused(id === "changes" && selectedRow != null && !selectedRow.isMainCheckout && selectedRow.dirtyFiles.length > 0);
30098
+ }, [activeTabId, renderer, worktreeKey, sessionsForSelected, selectedRow]);
29888
30099
  const handleAddSession = useCallback7(() => {
29889
30100
  if (!selectedRow) {
29890
30101
  flash("No worktree selected");
@@ -29908,13 +30119,31 @@ function App({ opts }) {
29908
30119
  return next;
29909
30120
  });
29910
30121
  setTerminalFocused(true);
30122
+ setChangesFocused(false);
29911
30123
  flash(`Session ${session.label} created`, 2000);
29912
30124
  }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight, activeTabId, sessionsForSelected]);
29913
30125
  const handleCloseSession = useCallback7((id) => {
29914
- if (!selectedRow)
29915
- return;
29916
- terminalSessions.removeSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, id);
29917
- const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
30126
+ let targetRepo;
30127
+ let targetBranch;
30128
+ let targetPath;
30129
+ for (const sessions of terminalSessions.sessionsByKey.values()) {
30130
+ const found = sessions.find((s) => s.id === id);
30131
+ if (found) {
30132
+ targetRepo = found.repoName;
30133
+ targetBranch = found.branch;
30134
+ targetPath = found.worktreePath;
30135
+ break;
30136
+ }
30137
+ }
30138
+ if (!targetRepo || !targetBranch || !targetPath) {
30139
+ if (!selectedRow)
30140
+ return;
30141
+ targetRepo = selectedRow.repoName;
30142
+ targetBranch = selectedRow.branch;
30143
+ targetPath = selectedRow.path;
30144
+ }
30145
+ terminalSessions.removeSession(targetRepo, targetBranch, targetPath, id);
30146
+ const key = terminalSessions.getKey(targetRepo, targetBranch, targetPath);
29918
30147
  setActiveTabByWorktree((prev) => {
29919
30148
  const next = new Map(prev);
29920
30149
  const current = next.get(key);
@@ -29950,8 +30179,10 @@ function App({ opts }) {
29950
30179
  render: ({ worktree }) => /* @__PURE__ */ jsx18(DetailsContent, {
29951
30180
  selectedRow: worktree
29952
30181
  })
29953
- },
29954
- {
30182
+ }
30183
+ ];
30184
+ if (selectedRow != null && !selectedRow.isMainCheckout && selectedRow.dirtyFiles.length > 0) {
30185
+ base2.push({
29955
30186
  id: "changes",
29956
30187
  label: "Changes",
29957
30188
  render: ({ worktree, isActive, focused }) => /* @__PURE__ */ jsx18(ChangesContent, {
@@ -29960,8 +30191,8 @@ function App({ opts }) {
29960
30191
  focused,
29961
30192
  worktreeKey
29962
30193
  })
29963
- }
29964
- ];
30194
+ });
30195
+ }
29965
30196
  for (const s of sessionsForSelected) {
29966
30197
  base2.push({
29967
30198
  id: s.id,
@@ -29975,7 +30206,7 @@ function App({ opts }) {
29975
30206
  });
29976
30207
  }
29977
30208
  return base2;
29978
- }, [sessionsForSelected, worktreeKey]);
30209
+ }, [selectedRow, sessionsForSelected, worktreeKey]);
29979
30210
  const { repoVerbs, rowVerbs } = useMemo3(() => {
29980
30211
  const rv = new Map;
29981
30212
  const nv = new Map;
@@ -30279,6 +30510,15 @@ function App({ opts }) {
30279
30510
  }
30280
30511
  }, [selectedRow, selection, combinedWarnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions]);
30281
30512
  useKeyboard4((key) => {
30513
+ if (changesFocused) {
30514
+ if (key.ctrl && key.name === "g" || key.name === "escape") {
30515
+ setChangesFocused(false);
30516
+ return;
30517
+ }
30518
+ if (["j", "k", "down", "up", "s", "tab"].includes(key.name)) {
30519
+ return;
30520
+ }
30521
+ }
30282
30522
  if (terminalFocused) {
30283
30523
  if (key.ctrl && key.name === "g") {
30284
30524
  setTerminalFocused(false);
@@ -30720,6 +30960,7 @@ function App({ opts }) {
30720
30960
  useEffect9(() => () => terminalResizeSchedulerRef.current?.flush(), []);
30721
30961
  useEffect9(() => {
30722
30962
  setTerminalFocused(false);
30963
+ setChangesFocused(false);
30723
30964
  }, [selectedIndex]);
30724
30965
  return /* @__PURE__ */ jsx18(ThemeContext.Provider, {
30725
30966
  value: currentTheme,
@@ -30737,7 +30978,10 @@ function App({ opts }) {
30737
30978
  width: leftCols,
30738
30979
  height: "100%",
30739
30980
  flexDirection: "column",
30740
- onMouseDown: () => setTerminalFocused(false),
30981
+ onMouseDown: () => {
30982
+ setTerminalFocused(false);
30983
+ setChangesFocused(false);
30984
+ },
30741
30985
  children: /* @__PURE__ */ jsx18(WorktreeTable, {
30742
30986
  blocks: displayBlocks,
30743
30987
  selectedIndex,
@@ -30750,9 +30994,10 @@ function App({ opts }) {
30750
30994
  activeWorkspaceName: activeWorkspace?.name ?? null,
30751
30995
  onRowClick: (idx) => {
30752
30996
  setTerminalFocused(false);
30997
+ setChangesFocused(false);
30753
30998
  setSelectedIndex(idx);
30754
30999
  },
30755
- onToggleSelect: (path38) => setSelection((prev) => toggleSelection(prev, path38))
31000
+ onToggleSelect: (path39) => setSelection((prev) => toggleSelection(prev, path39))
30756
31001
  })
30757
31002
  }),
30758
31003
  /* @__PURE__ */ jsx18(Divider, {
@@ -30769,13 +31014,18 @@ function App({ opts }) {
30769
31014
  selectedRow,
30770
31015
  tabs: tabsForSelected,
30771
31016
  activeId: activeTabId,
30772
- focused: terminalFocused && tabsForSelected.some((t) => t.id === activeTabId && t.id !== "details"),
31017
+ focused: (terminalFocused || changesFocused) && activeTabId !== "details",
30773
31018
  canAdd: sessionsForSelected.length < MAX_TERMINAL_SESSIONS,
30774
31019
  onSelect: handleSelectTab,
30775
31020
  onAdd: handleAddSession,
30776
31021
  onClose: handleCloseSession,
30777
31022
  allSessionsFlat,
30778
31023
  terminalFocused,
31024
+ changesFocused,
31025
+ onChangesFocus: () => {
31026
+ setChangesFocused(true);
31027
+ setTerminalFocused(false);
31028
+ },
30779
31029
  activeTabId,
30780
31030
  recentSessionIds: mountedRecentSessionIds,
30781
31031
  terminalSessions
@@ -30788,15 +31038,20 @@ function App({ opts }) {
30788
31038
  paddingX: 1,
30789
31039
  border: true,
30790
31040
  borderColor: "magenta",
31041
+ width: "100%",
30791
31042
  children: [
30792
31043
  /* @__PURE__ */ jsx18("text", {
30793
31044
  children: "filter: "
30794
31045
  }),
30795
- /* @__PURE__ */ jsx18("input", {
30796
- focused: true,
30797
- value: filterText,
30798
- placeholder: "Type to filter...",
30799
- onInput: (v) => setFilterText(v)
31046
+ /* @__PURE__ */ jsx18("box", {
31047
+ flexGrow: 1,
31048
+ children: /* @__PURE__ */ jsx18("input", {
31049
+ focused: true,
31050
+ value: filterText,
31051
+ placeholder: "Type to filter...",
31052
+ onInput: (v) => setFilterText(v),
31053
+ width: "100%"
31054
+ })
30800
31055
  })
30801
31056
  ]
30802
31057
  }),
@@ -36145,8 +36400,8 @@ function registerSkillCommand(program2) {
36145
36400
 
36146
36401
  // src/commands/terminal.ts
36147
36402
  import { spawnSync as spawnSync3 } from "node:child_process";
36148
- import fs34 from "node:fs";
36149
- import path38 from "node:path";
36403
+ import fs35 from "node:fs";
36404
+ import path39 from "node:path";
36150
36405
  import { fileURLToPath as fileURLToPath4 } from "node:url";
36151
36406
  function findEntry() {
36152
36407
  const candidates = [];
@@ -36155,26 +36410,26 @@ function findEntry() {
36155
36410
  try {
36156
36411
  const thisPath = fileURLToPath4(import.meta.url);
36157
36412
  candidates.push(thisPath);
36158
- candidates.push(path38.resolve(path38.dirname(thisPath), "../../dist/cli.mjs"));
36159
- candidates.push(path38.resolve(path38.dirname(thisPath), "../cli.mjs"));
36413
+ candidates.push(path39.resolve(path39.dirname(thisPath), "../../dist/cli.mjs"));
36414
+ candidates.push(path39.resolve(path39.dirname(thisPath), "../cli.mjs"));
36160
36415
  } catch {}
36161
36416
  for (const p of candidates) {
36162
36417
  try {
36163
36418
  if (p.endsWith(".mjs") || p.endsWith(".js")) {
36164
- if (fs34.existsSync(p) && fs34.statSync(p).isFile())
36419
+ if (fs35.existsSync(p) && fs35.statSync(p).isFile())
36165
36420
  return p;
36166
- } else if (fs34.existsSync(p)) {
36167
- const stat = fs34.statSync(p);
36421
+ } else if (fs35.existsSync(p)) {
36422
+ const stat = fs35.statSync(p);
36168
36423
  if (stat.isFile()) {
36169
- const head = fs34.readFileSync(p, "utf8").slice(0, 1024);
36424
+ const head = fs35.readFileSync(p, "utf8").slice(0, 1024);
36170
36425
  if (head.includes("cli.mjs") || head.includes("#!/usr/bin/env node")) {
36171
36426
  if (p.endsWith("wtx") || p.endsWith("cli.mjs")) {
36172
- const dir = path38.dirname(fs34.realpathSync(p));
36173
- const cli = path38.join(dir, "cli.mjs");
36174
- if (fs34.existsSync(cli))
36427
+ const dir = path39.dirname(fs35.realpathSync(p));
36428
+ const cli = path39.join(dir, "cli.mjs");
36429
+ if (fs35.existsSync(cli))
36175
36430
  return cli;
36176
- const cli2 = path38.resolve(dir, "../dist/cli.mjs");
36177
- if (fs34.existsSync(cli2))
36431
+ const cli2 = path39.resolve(dir, "../dist/cli.mjs");
36432
+ if (fs35.existsSync(cli2))
36178
36433
  return cli2;
36179
36434
  if (head.includes("import") || head.includes("commander"))
36180
36435
  return p;
@@ -36186,9 +36441,9 @@ function findEntry() {
36186
36441
  }
36187
36442
  try {
36188
36443
  const thisPath = fileURLToPath4(import.meta.url);
36189
- const pkgRoot = path38.resolve(path38.dirname(thisPath), "../..");
36190
- const cli = path38.join(pkgRoot, "dist", "cli.mjs");
36191
- if (fs34.existsSync(cli))
36444
+ const pkgRoot = path39.resolve(path39.dirname(thisPath), "../..");
36445
+ const cli = path39.join(pkgRoot, "dist", "cli.mjs");
36446
+ if (fs35.existsSync(cli))
36192
36447
  return cli;
36193
36448
  } catch {}
36194
36449
  return null;
@@ -36292,7 +36547,7 @@ init_deps();
36292
36547
  import readline3 from "readline";
36293
36548
  init_path_safety();
36294
36549
  init_stack();
36295
- import fs35 from "fs";
36550
+ import fs36 from "fs";
36296
36551
  async function runMcpServer(opts = {}) {
36297
36552
  const input = opts.input ?? process.stdin;
36298
36553
  const output = opts.output ?? process.stdout;
@@ -36475,7 +36730,7 @@ async function handleToolCall(name, args, config2, _opts) {
36475
36730
  for (const wt of wts) {
36476
36731
  if (!wt.path.startsWith(repo.wtRoot))
36477
36732
  continue;
36478
- const dirtyCount = fs35.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
36733
+ const dirtyCount = fs36.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
36479
36734
  items.push({
36480
36735
  repo: repo.name,
36481
36736
  branch: wt.branch || null,
@@ -36503,7 +36758,7 @@ async function handleToolCall(name, args, config2, _opts) {
36503
36758
  }
36504
36759
  const repo = resolveRepos(config2, [args.repo])[0];
36505
36760
  const wtPath = getWorktreePath(repo, args.branch);
36506
- if (!fs35.existsSync(wtPath)) {
36761
+ if (!fs36.existsSync(wtPath)) {
36507
36762
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
36508
36763
  }
36509
36764
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -36622,7 +36877,7 @@ async function handleToolCall(name, args, config2, _opts) {
36622
36877
  if (children.length > 0 && args.force !== true) {
36623
36878
  throw { isToolError: true, message: `Branch has dependent worktrees: ${children.join(", ")}. Use force:true to override.` };
36624
36879
  }
36625
- if (!args.force && fs35.existsSync(wtPath)) {
36880
+ if (!args.force && fs36.existsSync(wtPath)) {
36626
36881
  const dirty = await getDirtyFiles(wtPath);
36627
36882
  if (dirty.length > 0) {
36628
36883
  throw { isToolError: true, message: `Worktree is dirty. Use force:true to remove it.` };
@@ -36641,7 +36896,7 @@ async function handleToolCall(name, args, config2, _opts) {
36641
36896
  }
36642
36897
  const repo = resolveRepos(config2, [args.repo])[0];
36643
36898
  const wtPath = getWorktreePath(repo, args.branch);
36644
- if (!fs35.existsSync(wtPath)) {
36899
+ if (!fs36.existsSync(wtPath)) {
36645
36900
  throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
36646
36901
  }
36647
36902
  const mainBranch = await resolveMainBranch(repo, config2);
@@ -36863,8 +37118,8 @@ init_config();
36863
37118
  init_log();
36864
37119
  init_git();
36865
37120
  init_resolver();
36866
- import fs36 from "fs";
36867
- import path39 from "path";
37121
+ import fs37 from "fs";
37122
+ import path40 from "path";
36868
37123
  init_workspace();
36869
37124
  var DEPS_STRATEGIES2 = ["auto", "link", "symlink", "install", "off"];
36870
37125
  function errorMessage4(err) {
@@ -36874,7 +37129,7 @@ function workspaceRootFor(config2) {
36874
37129
  return getWorkspaceRoot(config2);
36875
37130
  }
36876
37131
  function workspacePathFor2(config2, name) {
36877
- return path39.join(workspaceRootFor(config2), name);
37132
+ return path40.join(workspaceRootFor(config2), name);
36878
37133
  }
36879
37134
  async function resolveWorktreePathForMember(config2, repoName, branch) {
36880
37135
  const repos = resolveRepos(config2, [repoName]);
@@ -36886,7 +37141,7 @@ async function resolveWorktreePathForMember(config2, repoName, branch) {
36886
37141
  if (match)
36887
37142
  return match.path;
36888
37143
  const candidate = getWorktreePath(repo, branch);
36889
- if (fs36.existsSync(candidate))
37144
+ if (fs37.existsSync(candidate))
36890
37145
  return candidate;
36891
37146
  return null;
36892
37147
  }
@@ -36944,7 +37199,7 @@ function registerCreateSub(workspace) {
36944
37199
  process.exit(1);
36945
37200
  }
36946
37201
  const wsPath = workspacePathFor2(config2, name);
36947
- if (fs36.existsSync(wsPath)) {
37202
+ if (fs37.existsSync(wsPath)) {
36948
37203
  stepError("Workspace already exists", wsPath);
36949
37204
  process.exit(1);
36950
37205
  }
@@ -37045,7 +37300,7 @@ function registerAddSub(workspace) {
37045
37300
  const globalOpts = workspace.parent.opts();
37046
37301
  const config2 = loadConfig();
37047
37302
  const wsPath = workspacePathFor2(config2, name);
37048
- if (!fs36.existsSync(wsPath)) {
37303
+ if (!fs37.existsSync(wsPath)) {
37049
37304
  stepError("Workspace not found", wsPath);
37050
37305
  process.exit(1);
37051
37306
  }
@@ -37081,7 +37336,7 @@ function registerRmSub(workspace) {
37081
37336
  const globalOpts = workspace.parent.opts();
37082
37337
  const config2 = loadConfig();
37083
37338
  const wsPath = workspacePathFor2(config2, name);
37084
- if (!fs36.existsSync(wsPath)) {
37339
+ if (!fs37.existsSync(wsPath)) {
37085
37340
  stepError("Workspace not found", wsPath);
37086
37341
  process.exit(1);
37087
37342
  }
@@ -37103,7 +37358,7 @@ function registerRemoveSub(workspace) {
37103
37358
  const globalOpts = workspace.parent.opts();
37104
37359
  const config2 = loadConfig();
37105
37360
  const wsPath = workspacePathFor2(config2, name);
37106
- if (!fs36.existsSync(wsPath)) {
37361
+ if (!fs37.existsSync(wsPath)) {
37107
37362
  stepError("Workspace not found", wsPath);
37108
37363
  process.exit(1);
37109
37364
  }
@@ -37117,7 +37372,7 @@ function registerRemoveSub(workspace) {
37117
37372
  return;
37118
37373
  }
37119
37374
  try {
37120
- fs36.rmSync(wsPath, { recursive: true, force: true });
37375
+ fs37.rmSync(wsPath, { recursive: true, force: true });
37121
37376
  stepSuccess("Workspace removed", wsPath);
37122
37377
  summary(`Done — workspace ${name} deleted (member worktrees preserved)`);
37123
37378
  } catch (err) {
@@ -37130,7 +37385,7 @@ function registerVerifySub(workspace) {
37130
37385
  workspace.command("verify <name>").description("Verify that every workspace member link resolves").action(async (name) => {
37131
37386
  const config2 = loadConfig();
37132
37387
  const wsPath = workspacePathFor2(config2, name);
37133
- if (!fs36.existsSync(wsPath)) {
37388
+ if (!fs37.existsSync(wsPath)) {
37134
37389
  stepError("Workspace not found", wsPath);
37135
37390
  process.exit(1);
37136
37391
  }
@@ -37165,7 +37420,7 @@ init_config();
37165
37420
  init_resolver();
37166
37421
  init_log();
37167
37422
  init_history();
37168
- import fs37 from "fs";
37423
+ import fs38 from "fs";
37169
37424
  var program2 = new Command;
37170
37425
  program2.name("wtx").description("Multi-repo git worktree manager").version(VERSION).option("-q, --quiet", "Suppress progress indicators", false).option("--verbose", "Show git commands as they run", false).option("--dry-run", "Show what would happen", false);
37171
37426
  var MUTATING_COMMANDS = new Set([
@@ -37275,7 +37530,7 @@ program2.command("_resolve-path <repo> <branch>", { hidden: true }).description(
37275
37530
  }
37276
37531
  const repo = repos[0];
37277
37532
  const wtPath = getWorktreePath(repo, branch);
37278
- if (!fs37.existsSync(wtPath)) {
37533
+ if (!fs38.existsSync(wtPath)) {
37279
37534
  process.stderr.write(`✗ No worktree at ${wtPath}
37280
37535
  `);
37281
37536
  process.exit(1);