@ogpoyraz/wtx 0.8.7 → 0.8.9

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 +1355 -424
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -14672,7 +14672,7 @@ function getVersion() {
14672
14672
  return "0.0.0";
14673
14673
  }
14674
14674
  }
14675
- var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14675
+ var DepsManagerEnum, DepsStrategyEnum, DepsConfigSchema, AgentConfigSchema, PortsConfigSchema, TuiConfigSchema, LEGACY_REPO_KEYS, RepoConfigSchema, ConfigSchema, VERSION;
14676
14676
  var init_types = __esm(() => {
14677
14677
  init_v4();
14678
14678
  DepsManagerEnum = exports_external.enum(["auto", "npm", "bun", "pnpm", "yarn", "go", "python", "cargo"]);
@@ -14688,6 +14688,10 @@ var init_types = __esm(() => {
14688
14688
  min: exports_external.number().int().min(1024).max(65535).default(4100),
14689
14689
  max: exports_external.number().int().min(1024).max(65535).default(4999)
14690
14690
  }).refine((v) => v.min <= v.max, { message: "ports.min must be less than or equal to ports.max" });
14691
+ TuiConfigSchema = exports_external.object({
14692
+ leftPaneWidthWeight: exports_external.number().int().min(1).max(10).default(3),
14693
+ rightPaneWidthWeight: exports_external.number().int().min(1).max(10).default(7)
14694
+ });
14691
14695
  LEGACY_REPO_KEYS = {
14692
14696
  pr: "check_prs",
14693
14697
  forge: "forge_provider",
@@ -14730,7 +14734,8 @@ var init_types = __esm(() => {
14730
14734
  user: exports_external.string().trim().min(1, { message: "must not be empty if provided" }).nullable().default(null),
14731
14735
  repos: exports_external.record(exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "Invalid repo key format"), RepoConfigSchema),
14732
14736
  agents: exports_external.record(exports_external.string().regex(/^[a-z][a-z0-9_-]*$/, "Agent names must be lowercase alphanumeric with dashes/underscores"), AgentConfigSchema).optional(),
14733
- ports: PortsConfigSchema.default({ min: 4100, max: 4999 })
14737
+ ports: PortsConfigSchema.default({ min: 4100, max: 4999 }),
14738
+ tui: TuiConfigSchema.default({ leftPaneWidthWeight: 3, rightPaneWidthWeight: 7 })
14734
14739
  });
14735
14740
  VERSION = getVersion();
14736
14741
  });
@@ -25010,80 +25015,6 @@ var init_owner = __esm(() => {
25010
25015
  init_git();
25011
25016
  });
25012
25017
 
25013
- // src/lib/agents.ts
25014
- function resolveAgentCommand(name, configured) {
25015
- return configured?.[name]?.command ?? DEFAULT_AGENTS[name] ?? null;
25016
- }
25017
- function listAvailableAgents(configured) {
25018
- return [...new Set([...Object.keys(DEFAULT_AGENTS), ...Object.keys(configured ?? {})])];
25019
- }
25020
- async function isTmuxAvailable() {
25021
- if (tmuxAvailable !== undefined)
25022
- return tmuxAvailable;
25023
- try {
25024
- await execa("tmux", ["-V"]);
25025
- tmuxAvailable = true;
25026
- } catch {
25027
- tmuxAvailable = false;
25028
- }
25029
- return tmuxAvailable;
25030
- }
25031
- function tmuxSessionName(repoName, branch) {
25032
- const sanitized = `${repoName}-${branch}`.replace(/[\/.:]/g, "-");
25033
- return `wtx-${sanitized}`.substring(0, 60);
25034
- }
25035
- function buildTmuxArgs(session, wtPath, cmd) {
25036
- return ["new-session", "-d", "-s", session, "-c", wtPath, cmd];
25037
- }
25038
- async function spawnAgentInWorktree(commandTemplate, wtPath, opts = {}) {
25039
- const cmd = buildAgentCommand(commandTemplate, wtPath, opts.prompt, {
25040
- branch: opts.branch,
25041
- repo: opts.repoName
25042
- });
25043
- const session = opts.repoName && opts.branch ? tmuxSessionName(opts.repoName, opts.branch) : undefined;
25044
- if (opts.dryRun) {
25045
- if (session && await isTmuxAvailable()) {
25046
- return { mode: "tmux", session };
25047
- }
25048
- return { mode: "direct" };
25049
- }
25050
- if (session && await isTmuxAvailable()) {
25051
- try {
25052
- const args = buildTmuxArgs(session, wtPath, cmd);
25053
- await execa("tmux", args, { stdio: "inherit" });
25054
- return { mode: "tmux", session };
25055
- } catch {}
25056
- }
25057
- await execa(cmd, { shell: true, cwd: wtPath, stdio: "inherit" });
25058
- return { mode: "direct" };
25059
- }
25060
- function shellQuote(value) {
25061
- return `'${value.replaceAll("'", `'''`)}'`;
25062
- }
25063
- function buildAgentCommand(commandTemplate, wtPath, prompt, vars) {
25064
- let cmd = commandTemplate.replaceAll("{wt}", shellQuote(wtPath));
25065
- if (vars?.branch) {
25066
- cmd = cmd.replaceAll("{branch}", shellQuote(vars.branch));
25067
- }
25068
- if (vars?.repo) {
25069
- cmd = cmd.replaceAll("{repo}", shellQuote(vars.repo));
25070
- }
25071
- if (prompt) {
25072
- cmd += ` ${shellQuote(prompt)}`;
25073
- }
25074
- return cmd;
25075
- }
25076
- var DEFAULT_AGENTS, tmuxAvailable = undefined;
25077
- var init_agents = __esm(() => {
25078
- init_execa();
25079
- DEFAULT_AGENTS = {
25080
- claude: "claude",
25081
- codex: "codex",
25082
- opencode: "opencode",
25083
- cursor: "cursor"
25084
- };
25085
- });
25086
-
25087
25018
  // src/lib/forge/types.ts
25088
25019
  function displayStateRank(state) {
25089
25020
  return DISPLAY_PRIORITY.indexOf(state);
@@ -25561,10 +25492,11 @@ function useWorktrees(opts) {
25561
25492
  }
25562
25493
  }
25563
25494
  }, [opts]);
25495
+ const clearWarnings = useCallback(() => setWarnings([]), []);
25564
25496
  useEffect(() => {
25565
25497
  refresh();
25566
25498
  }, [refresh]);
25567
- return { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, refresh };
25499
+ return { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, refresh, clearWarnings };
25568
25500
  }
25569
25501
  var init_useWorktrees = __esm(() => {
25570
25502
  init_data();
@@ -25865,6 +25797,8 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
25865
25797
  height: "100%",
25866
25798
  border: true,
25867
25799
  borderColor: tokens.border,
25800
+ focusedBorderColor: tokens.border,
25801
+ focusable: false,
25868
25802
  title: "Worktrees",
25869
25803
  paddingX: 1,
25870
25804
  focused: false,
@@ -25932,26 +25866,133 @@ var init_WorktreeTable = __esm(() => {
25932
25866
  init_use_tap();
25933
25867
  });
25934
25868
 
25935
- // src/tui/components/DetailPane.tsx
25936
- import { jsx as jsx2, jsxs as jsxs2, Fragment } from "@opentui/react/jsx-runtime";
25937
- function DetailPane({ selectedRow }) {
25869
+ // src/tui/tabs/TabBar.tsx
25870
+ import { jsx as jsx2, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
25871
+ function TabItem({
25872
+ id,
25873
+ label,
25874
+ closable,
25875
+ active,
25876
+ onSelect,
25877
+ onClose
25878
+ }) {
25879
+ const tap = useTapHandler(() => onSelect(id));
25880
+ const closeTap = useTapHandler((e) => {
25881
+ e.stopPropagation();
25882
+ onClose?.(id);
25883
+ });
25884
+ return /* @__PURE__ */ jsxs2("box", {
25885
+ flexDirection: "row",
25886
+ paddingRight: 1,
25887
+ ...tap,
25888
+ children: [
25889
+ /* @__PURE__ */ jsx2("text", {
25890
+ fg: active ? tokens.accent : tokens.dim,
25891
+ attributes: active ? 1 : 0,
25892
+ children: label
25893
+ }),
25894
+ closable ? /* @__PURE__ */ jsx2("box", {
25895
+ ...closeTap,
25896
+ style: { marginLeft: 1 },
25897
+ children: /* @__PURE__ */ jsx2("text", {
25898
+ fg: active ? tokens.accent : tokens.dim,
25899
+ children: "✕"
25900
+ })
25901
+ }) : null,
25902
+ /* @__PURE__ */ jsx2("text", {
25903
+ children: " "
25904
+ })
25905
+ ]
25906
+ });
25907
+ }
25908
+ function TabBar({ tabs, activeId, onSelect, onClose }) {
25909
+ return /* @__PURE__ */ jsx2("box", {
25910
+ flexDirection: "row",
25911
+ width: "100%",
25912
+ alignItems: "center",
25913
+ paddingLeft: 1,
25914
+ children: tabs.map((tab) => /* @__PURE__ */ jsx2(TabItem, {
25915
+ id: tab.id,
25916
+ label: tab.label,
25917
+ closable: tab.closable,
25918
+ active: tab.id === activeId,
25919
+ onSelect,
25920
+ onClose
25921
+ }, tab.id))
25922
+ });
25923
+ }
25924
+ var init_TabBar = __esm(() => {
25925
+ init_theme();
25926
+ init_use_tap();
25927
+ });
25928
+
25929
+ // src/tui/tabs/TabPane.tsx
25930
+ import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
25931
+ function TabPane({
25932
+ selectedRow,
25933
+ tabs,
25934
+ activeId,
25935
+ focused,
25936
+ canAdd,
25937
+ onSelect,
25938
+ onAdd,
25939
+ onClose,
25940
+ onFocusTerminal: _onFocusTerminal,
25941
+ onBlurTerminal: _onBlurTerminal
25942
+ }) {
25943
+ const activeTab = tabs.find((t) => t.id === activeId) ?? tabs[0] ?? null;
25944
+ const borderColor = focused ? tokens.accent : tokens.border;
25945
+ return /* @__PURE__ */ jsxs3("box", {
25946
+ flexDirection: "column",
25947
+ flexGrow: 1,
25948
+ width: "100%",
25949
+ height: "100%",
25950
+ border: true,
25951
+ borderColor,
25952
+ focusedBorderColor: borderColor,
25953
+ focusable: false,
25954
+ id: "detail-pane",
25955
+ children: [
25956
+ /* @__PURE__ */ jsx3(TabBar, {
25957
+ tabs,
25958
+ activeId,
25959
+ canAdd,
25960
+ onSelect,
25961
+ onAdd,
25962
+ onClose
25963
+ }),
25964
+ /* @__PURE__ */ jsx3("box", {
25965
+ flexGrow: 1,
25966
+ width: "100%",
25967
+ flexDirection: "column",
25968
+ children: activeTab ? activeTab.render({ worktree: selectedRow, isActive: true }) : /* @__PURE__ */ jsx3("text", {
25969
+ fg: tokens.dim,
25970
+ children: "No tab"
25971
+ })
25972
+ })
25973
+ ]
25974
+ });
25975
+ }
25976
+ var init_TabPane = __esm(() => {
25977
+ init_theme();
25978
+ init_TabBar();
25979
+ });
25980
+
25981
+ // src/tui/components/DetailsTab.tsx
25982
+ import { jsx as jsx4, jsxs as jsxs4, Fragment } from "@opentui/react/jsx-runtime";
25983
+ function DetailsContent({ selectedRow }) {
25938
25984
  const prTap = useTapHandler(() => {
25939
25985
  if (selectedRow?.prUrl)
25940
25986
  openInBrowser(selectedRow.prUrl);
25941
25987
  });
25942
25988
  if (!selectedRow) {
25943
- return /* @__PURE__ */ jsx2("box", {
25944
- id: "detail-pane",
25989
+ return /* @__PURE__ */ jsx4("box", {
25945
25990
  flexGrow: 1,
25946
25991
  width: "100%",
25947
25992
  height: "100%",
25948
- border: true,
25949
- borderColor: tokens.border,
25950
- title: "Details",
25951
- padding: 1,
25952
25993
  justifyContent: "center",
25953
25994
  alignItems: "center",
25954
- children: /* @__PURE__ */ jsx2("text", {
25995
+ children: /* @__PURE__ */ jsx4("text", {
25955
25996
  fg: tokens.dim,
25956
25997
  children: "No worktree selected"
25957
25998
  })
@@ -25979,24 +26020,20 @@ function DetailPane({ selectedRow }) {
25979
26020
  baseChanged
25980
26021
  } = selectedRow;
25981
26022
  const aheadBehindStr = ahead !== null && behind !== null ? `${ahead} ahead, ${behind} behind` : "unknown";
25982
- return /* @__PURE__ */ jsxs2("scrollbox", {
25983
- id: "detail-pane",
26023
+ return /* @__PURE__ */ jsxs4("scrollbox", {
25984
26024
  flexGrow: 1,
25985
26025
  width: "100%",
25986
26026
  height: "100%",
25987
- border: true,
25988
- borderColor: tokens.border,
25989
- title: "Details",
25990
- padding: 1,
25991
26027
  focused: false,
26028
+ padding: 1,
25992
26029
  children: [
25993
- /* @__PURE__ */ jsxs2("text", {
26030
+ /* @__PURE__ */ jsxs4("text", {
25994
26031
  children: [
25995
- /* @__PURE__ */ jsx2("span", {
26032
+ /* @__PURE__ */ jsx4("span", {
25996
26033
  fg: tokens.dim,
25997
26034
  children: "Repo:"
25998
26035
  }),
25999
- /* @__PURE__ */ jsxs2("span", {
26036
+ /* @__PURE__ */ jsxs4("span", {
26000
26037
  fg: tokens.fg,
26001
26038
  children: [
26002
26039
  " ",
@@ -26005,13 +26042,13 @@ function DetailPane({ selectedRow }) {
26005
26042
  })
26006
26043
  ]
26007
26044
  }),
26008
- /* @__PURE__ */ jsxs2("text", {
26045
+ /* @__PURE__ */ jsxs4("text", {
26009
26046
  children: [
26010
- /* @__PURE__ */ jsx2("span", {
26047
+ /* @__PURE__ */ jsx4("span", {
26011
26048
  fg: tokens.dim,
26012
26049
  children: "Branch:"
26013
26050
  }),
26014
- /* @__PURE__ */ jsxs2("span", {
26051
+ /* @__PURE__ */ jsxs4("span", {
26015
26052
  fg: tokens.fg,
26016
26053
  children: [
26017
26054
  " ",
@@ -26022,13 +26059,13 @@ function DetailPane({ selectedRow }) {
26022
26059
  })
26023
26060
  ]
26024
26061
  }),
26025
- /* @__PURE__ */ jsxs2("text", {
26062
+ /* @__PURE__ */ jsxs4("text", {
26026
26063
  children: [
26027
- /* @__PURE__ */ jsx2("span", {
26064
+ /* @__PURE__ */ jsx4("span", {
26028
26065
  fg: tokens.dim,
26029
26066
  children: "Path:"
26030
26067
  }),
26031
- /* @__PURE__ */ jsxs2("span", {
26068
+ /* @__PURE__ */ jsxs4("span", {
26032
26069
  fg: tokens.fg,
26033
26070
  children: [
26034
26071
  " ",
@@ -26037,13 +26074,13 @@ function DetailPane({ selectedRow }) {
26037
26074
  })
26038
26075
  ]
26039
26076
  }),
26040
- /* @__PURE__ */ jsxs2("text", {
26077
+ /* @__PURE__ */ jsxs4("text", {
26041
26078
  children: [
26042
- /* @__PURE__ */ jsx2("span", {
26079
+ /* @__PURE__ */ jsx4("span", {
26043
26080
  fg: tokens.dim,
26044
26081
  children: "Commit:"
26045
26082
  }),
26046
- /* @__PURE__ */ jsxs2("span", {
26083
+ /* @__PURE__ */ jsxs4("span", {
26047
26084
  fg: tokens.fg,
26048
26085
  children: [
26049
26086
  " ",
@@ -26052,13 +26089,13 @@ function DetailPane({ selectedRow }) {
26052
26089
  })
26053
26090
  ]
26054
26091
  }),
26055
- owner && /* @__PURE__ */ jsxs2("text", {
26092
+ owner && /* @__PURE__ */ jsxs4("text", {
26056
26093
  children: [
26057
- /* @__PURE__ */ jsx2("span", {
26094
+ /* @__PURE__ */ jsx4("span", {
26058
26095
  fg: tokens.dim,
26059
26096
  children: "Owner:"
26060
26097
  }),
26061
- /* @__PURE__ */ jsxs2("span", {
26098
+ /* @__PURE__ */ jsxs4("span", {
26062
26099
  fg: tokens.fg,
26063
26100
  children: [
26064
26101
  " ",
@@ -26067,13 +26104,13 @@ function DetailPane({ selectedRow }) {
26067
26104
  })
26068
26105
  ]
26069
26106
  }),
26070
- /* @__PURE__ */ jsxs2("text", {
26107
+ /* @__PURE__ */ jsxs4("text", {
26071
26108
  children: [
26072
- /* @__PURE__ */ jsx2("span", {
26109
+ /* @__PURE__ */ jsx4("span", {
26073
26110
  fg: tokens.dim,
26074
26111
  children: "Status:"
26075
26112
  }),
26076
- /* @__PURE__ */ jsxs2("span", {
26113
+ /* @__PURE__ */ jsxs4("span", {
26077
26114
  fg: tokens.fg,
26078
26115
  children: [
26079
26116
  " ",
@@ -26082,13 +26119,13 @@ function DetailPane({ selectedRow }) {
26082
26119
  })
26083
26120
  ]
26084
26121
  }),
26085
- base2 && /* @__PURE__ */ jsxs2("text", {
26122
+ base2 && /* @__PURE__ */ jsxs4("text", {
26086
26123
  children: [
26087
- /* @__PURE__ */ jsx2("span", {
26124
+ /* @__PURE__ */ jsx4("span", {
26088
26125
  fg: tokens.dim,
26089
26126
  children: "Base:"
26090
26127
  }),
26091
- /* @__PURE__ */ jsxs2("span", {
26128
+ /* @__PURE__ */ jsxs4("span", {
26092
26129
  fg: tokens.accent,
26093
26130
  children: [
26094
26131
  " ",
@@ -26097,26 +26134,26 @@ function DetailPane({ selectedRow }) {
26097
26134
  })
26098
26135
  ]
26099
26136
  }),
26100
- baseChanged && /* @__PURE__ */ jsxs2("text", {
26137
+ baseChanged && /* @__PURE__ */ jsxs4("text", {
26101
26138
  children: [
26102
- /* @__PURE__ */ jsx2("span", {
26139
+ /* @__PURE__ */ jsx4("span", {
26103
26140
  fg: tokens.dim,
26104
26141
  children: "Base state:"
26105
26142
  }),
26106
- /* @__PURE__ */ jsx2("span", {
26143
+ /* @__PURE__ */ jsx4("span", {
26107
26144
  fg: tokens.warning,
26108
26145
  children: " moved since recorded"
26109
26146
  })
26110
26147
  ]
26111
26148
  }),
26112
- /* @__PURE__ */ jsxs2("text", {
26149
+ /* @__PURE__ */ jsxs4("text", {
26113
26150
  style: { marginTop: 1 },
26114
26151
  children: [
26115
- /* @__PURE__ */ jsx2("span", {
26152
+ /* @__PURE__ */ jsx4("span", {
26116
26153
  fg: tokens.dim,
26117
26154
  children: base2 ? "vs base:" : "vs main:"
26118
26155
  }),
26119
- /* @__PURE__ */ jsxs2("span", {
26156
+ /* @__PURE__ */ jsxs4("span", {
26120
26157
  fg: tokens.fg,
26121
26158
  children: [
26122
26159
  " ",
@@ -26125,13 +26162,13 @@ function DetailPane({ selectedRow }) {
26125
26162
  })
26126
26163
  ]
26127
26164
  }),
26128
- /* @__PURE__ */ jsxs2("text", {
26165
+ /* @__PURE__ */ jsxs4("text", {
26129
26166
  children: [
26130
- /* @__PURE__ */ jsx2("span", {
26167
+ /* @__PURE__ */ jsx4("span", {
26131
26168
  fg: tokens.dim,
26132
26169
  children: "Deps:"
26133
26170
  }),
26134
- /* @__PURE__ */ jsxs2("span", {
26171
+ /* @__PURE__ */ jsxs4("span", {
26135
26172
  fg: tokens.fg,
26136
26173
  children: [
26137
26174
  " ",
@@ -26140,13 +26177,13 @@ function DetailPane({ selectedRow }) {
26140
26177
  })
26141
26178
  ]
26142
26179
  }),
26143
- rebaseStatus && /* @__PURE__ */ jsxs2("text", {
26180
+ rebaseStatus && /* @__PURE__ */ jsxs4("text", {
26144
26181
  children: [
26145
- /* @__PURE__ */ jsx2("span", {
26182
+ /* @__PURE__ */ jsx4("span", {
26146
26183
  fg: tokens.dim,
26147
26184
  children: "Rebase:"
26148
26185
  }),
26149
- /* @__PURE__ */ jsxs2("span", {
26186
+ /* @__PURE__ */ jsxs4("span", {
26150
26187
  fg: tokens.error,
26151
26188
  children: [
26152
26189
  " ",
@@ -26155,12 +26192,12 @@ function DetailPane({ selectedRow }) {
26155
26192
  })
26156
26193
  ]
26157
26194
  }),
26158
- prNumber !== null && /* @__PURE__ */ jsxs2(Fragment, {
26195
+ prNumber !== null && /* @__PURE__ */ jsxs4(Fragment, {
26159
26196
  children: [
26160
- /* @__PURE__ */ jsxs2("text", {
26197
+ /* @__PURE__ */ jsxs4("text", {
26161
26198
  style: { marginTop: 1 },
26162
26199
  children: [
26163
- /* @__PURE__ */ jsxs2("span", {
26200
+ /* @__PURE__ */ jsxs4("span", {
26164
26201
  fg: tokens.accent,
26165
26202
  children: [
26166
26203
  "PR #",
@@ -26168,7 +26205,7 @@ function DetailPane({ selectedRow }) {
26168
26205
  ":"
26169
26206
  ]
26170
26207
  }),
26171
- /* @__PURE__ */ jsxs2("span", {
26208
+ /* @__PURE__ */ jsxs4("span", {
26172
26209
  fg: tokens.dim,
26173
26210
  children: [
26174
26211
  " ",
@@ -26177,13 +26214,13 @@ function DetailPane({ selectedRow }) {
26177
26214
  })
26178
26215
  ]
26179
26216
  }),
26180
- prChecks && /* @__PURE__ */ jsxs2("text", {
26217
+ prChecks && /* @__PURE__ */ jsxs4("text", {
26181
26218
  children: [
26182
- /* @__PURE__ */ jsx2("span", {
26219
+ /* @__PURE__ */ jsx4("span", {
26183
26220
  fg: tokens.dim,
26184
26221
  children: "Checks:"
26185
26222
  }),
26186
- /* @__PURE__ */ jsxs2("span", {
26223
+ /* @__PURE__ */ jsxs4("span", {
26187
26224
  fg: tokens.fg,
26188
26225
  children: [
26189
26226
  " ",
@@ -26192,20 +26229,20 @@ function DetailPane({ selectedRow }) {
26192
26229
  })
26193
26230
  ]
26194
26231
  }),
26195
- prUrl && /* @__PURE__ */ jsx2("box", {
26232
+ prUrl && /* @__PURE__ */ jsx4("box", {
26196
26233
  ...prTap,
26197
- children: /* @__PURE__ */ jsxs2("text", {
26234
+ children: /* @__PURE__ */ jsxs4("text", {
26198
26235
  selectable: false,
26199
26236
  children: [
26200
- /* @__PURE__ */ jsx2("span", {
26237
+ /* @__PURE__ */ jsx4("span", {
26201
26238
  fg: tokens.dim,
26202
26239
  children: "URL:"
26203
26240
  }),
26204
- /* @__PURE__ */ jsx2("span", {
26241
+ /* @__PURE__ */ jsx4("span", {
26205
26242
  fg: tokens.accent,
26206
26243
  children: ` ${prUrl}`
26207
26244
  }),
26208
- /* @__PURE__ */ jsx2("span", {
26245
+ /* @__PURE__ */ jsx4("span", {
26209
26246
  fg: tokens.dim,
26210
26247
  children: " ↗ click"
26211
26248
  })
@@ -26214,11 +26251,11 @@ function DetailPane({ selectedRow }) {
26214
26251
  })
26215
26252
  ]
26216
26253
  }),
26217
- dirtyFiles.length > 0 && /* @__PURE__ */ jsxs2("box", {
26254
+ dirtyFiles.length > 0 && /* @__PURE__ */ jsxs4("box", {
26218
26255
  flexDirection: "column",
26219
26256
  style: { marginTop: 1 },
26220
26257
  children: [
26221
- /* @__PURE__ */ jsxs2("text", {
26258
+ /* @__PURE__ */ jsxs4("text", {
26222
26259
  fg: tokens.warning,
26223
26260
  children: [
26224
26261
  "Dirty Files (",
@@ -26226,14 +26263,14 @@ function DetailPane({ selectedRow }) {
26226
26263
  "):"
26227
26264
  ]
26228
26265
  }),
26229
- dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs2("text", {
26266
+ dirtyFiles.slice(0, 20).map((file2, idx) => /* @__PURE__ */ jsxs4("text", {
26230
26267
  fg: tokens.warning,
26231
26268
  children: [
26232
26269
  " ",
26233
26270
  file2
26234
26271
  ]
26235
26272
  }, idx)),
26236
- dirtyFiles.length > 20 && /* @__PURE__ */ jsxs2("text", {
26273
+ dirtyFiles.length > 20 && /* @__PURE__ */ jsxs4("text", {
26237
26274
  fg: tokens.warning,
26238
26275
  children: [
26239
26276
  " ...and ",
@@ -26243,17 +26280,17 @@ function DetailPane({ selectedRow }) {
26243
26280
  })
26244
26281
  ]
26245
26282
  }),
26246
- /* @__PURE__ */ jsxs2("box", {
26283
+ /* @__PURE__ */ jsxs4("box", {
26247
26284
  flexDirection: "column",
26248
26285
  style: { marginTop: 1 },
26249
26286
  children: [
26250
- /* @__PURE__ */ jsx2("text", {
26287
+ /* @__PURE__ */ jsx4("text", {
26251
26288
  fg: tokens.dim,
26252
26289
  children: "Actions:"
26253
26290
  }),
26254
- !isMainCheckout && /* @__PURE__ */ jsxs2(Fragment, {
26291
+ !isMainCheckout && /* @__PURE__ */ jsxs4(Fragment, {
26255
26292
  children: [
26256
- /* @__PURE__ */ jsxs2("text", {
26293
+ /* @__PURE__ */ jsxs4("text", {
26257
26294
  fg: tokens.dim,
26258
26295
  children: [
26259
26296
  " i install deps (",
@@ -26261,44 +26298,218 @@ function DetailPane({ selectedRow }) {
26261
26298
  ")"
26262
26299
  ]
26263
26300
  }),
26264
- /* @__PURE__ */ jsx2("text", {
26301
+ /* @__PURE__ */ jsx4("text", {
26265
26302
  fg: tokens.dim,
26266
26303
  children: " m rename worktree"
26267
26304
  })
26268
26305
  ]
26269
26306
  }),
26270
- /* @__PURE__ */ jsx2("text", {
26307
+ /* @__PURE__ */ jsx4("text", {
26271
26308
  fg: tokens.dim,
26272
26309
  children: " p pull branch"
26273
26310
  }),
26274
- /* @__PURE__ */ jsx2("text", {
26311
+ /* @__PURE__ */ jsx4("text", {
26275
26312
  fg: tokens.dim,
26276
26313
  children: " b rebase · s sync · o open in IDE · d remove"
26314
+ }),
26315
+ /* @__PURE__ */ jsx4("text", {
26316
+ fg: tokens.dim,
26317
+ children: " t new terminal session (max 5)"
26277
26318
  })
26278
26319
  ]
26279
26320
  })
26280
26321
  ]
26281
26322
  });
26282
26323
  }
26283
- var init_DetailPane = __esm(() => {
26324
+ var init_DetailsTab = __esm(() => {
26325
+ init_theme();
26326
+ init_use_tap();
26327
+ });
26328
+
26329
+ // src/tui/components/TerminalView.tsx
26330
+ import { useState as useState2, useEffect as useEffect3, useRef as useRef4, useCallback as useCallback3 } from "react";
26331
+ import { jsx as jsx5, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
26332
+ function TerminalView({ session, focused, onFocus, onSend, onResize, registerListener, unregisterListener }) {
26333
+ const [draft, setDraft] = useState2("");
26334
+ const focusTap = useTapHandler(() => onFocus());
26335
+ const termRef = useRef4(null);
26336
+ const handleData = useCallback3((data, source) => {
26337
+ if (source !== "input")
26338
+ return;
26339
+ if (session.usePty && focused)
26340
+ return;
26341
+ onSend(data);
26342
+ }, [onSend, focused, session.id, session.usePty]);
26343
+ const handleResize = useCallback3((cols, rows) => {
26344
+ onResize?.(cols, rows);
26345
+ }, [onResize]);
26346
+ useEffect3(() => {
26347
+ if (!session.usePty || !session.terminal)
26348
+ return;
26349
+ if (!termRef.current || !registerListener || !unregisterListener)
26350
+ return;
26351
+ try {
26352
+ termRef.current.write(new TextEncoder().encode("\x1B[2J\x1B[H"));
26353
+ } catch {}
26354
+ const write = (data) => {
26355
+ try {
26356
+ termRef.current?.write(data);
26357
+ } catch {}
26358
+ };
26359
+ registerListener(session.id, write);
26360
+ return () => unregisterListener(session.id);
26361
+ }, [session.id, session.usePty, session.terminal, registerListener, unregisterListener]);
26362
+ useEffect3(() => {
26363
+ if (!session.usePty)
26364
+ return;
26365
+ if (!termRef.current)
26366
+ return;
26367
+ try {
26368
+ if (focused)
26369
+ termRef.current.focus?.();
26370
+ else
26371
+ termRef.current.blur?.();
26372
+ } catch {}
26373
+ }, [focused, session.usePty]);
26374
+ if (session.usePty && session.terminal) {
26375
+ return /* @__PURE__ */ jsxs5("box", {
26376
+ flexDirection: "column",
26377
+ flexGrow: 1,
26378
+ width: "100%",
26379
+ height: "100%",
26380
+ ...focusTap,
26381
+ children: [
26382
+ /* @__PURE__ */ jsx5("box", {
26383
+ flexGrow: 1,
26384
+ width: "100%",
26385
+ height: "100%",
26386
+ flexDirection: "column",
26387
+ padding: 1,
26388
+ children: /* @__PURE__ */ jsx5("embedded-terminal", {
26389
+ ref: termRef,
26390
+ width: "100%",
26391
+ height: "100%",
26392
+ cols: session.cols,
26393
+ rows: session.rows,
26394
+ maxScrollback: 1000,
26395
+ onData: handleData,
26396
+ onTerminalResize: handleResize,
26397
+ selectable: true,
26398
+ focused
26399
+ })
26400
+ }),
26401
+ session.exited !== null && /* @__PURE__ */ jsxs5("text", {
26402
+ fg: tokens.warning,
26403
+ children: [
26404
+ "— exited with code ",
26405
+ session.exited,
26406
+ " — press t for new session"
26407
+ ]
26408
+ }),
26409
+ !focused && /* @__PURE__ */ jsxs5("text", {
26410
+ fg: tokens.dim,
26411
+ children: [
26412
+ "Click to focus · Ctrl+G or click table to unfocus · ",
26413
+ session.label,
26414
+ " PTY"
26415
+ ]
26416
+ }),
26417
+ focused && /* @__PURE__ */ jsx5("text", {
26418
+ fg: tokens.accent,
26419
+ children: "Focused PTY — all keys go to shell · Ctrl+G to unfocus"
26420
+ })
26421
+ ]
26422
+ });
26423
+ }
26424
+ const lines = session.lines;
26425
+ return /* @__PURE__ */ jsxs5("box", {
26426
+ flexDirection: "column",
26427
+ flexGrow: 1,
26428
+ width: "100%",
26429
+ height: "100%",
26430
+ ...focusTap,
26431
+ children: [
26432
+ /* @__PURE__ */ jsxs5("scrollbox", {
26433
+ flexGrow: 1,
26434
+ width: "100%",
26435
+ border: false,
26436
+ focused: false,
26437
+ style: { marginBottom: 1 },
26438
+ children: [
26439
+ lines.length === 0 ? /* @__PURE__ */ jsx5("text", {
26440
+ fg: tokens.dim,
26441
+ children: "No output yet — type a command below."
26442
+ }) : lines.slice(-200).map((line, idx) => /* @__PURE__ */ jsx5("text", {
26443
+ fg: session.exited !== null ? tokens.dim : tokens.fg,
26444
+ children: line || " "
26445
+ }, idx)),
26446
+ session.exited !== null && /* @__PURE__ */ jsxs5("text", {
26447
+ fg: tokens.warning,
26448
+ children: [
26449
+ "— exited with code ",
26450
+ session.exited,
26451
+ " —"
26452
+ ]
26453
+ })
26454
+ ]
26455
+ }),
26456
+ /* @__PURE__ */ jsxs5("box", {
26457
+ flexDirection: "row",
26458
+ gap: 1,
26459
+ width: "100%",
26460
+ children: [
26461
+ /* @__PURE__ */ jsx5("text", {
26462
+ fg: tokens.dim,
26463
+ children: "$"
26464
+ }),
26465
+ /* @__PURE__ */ jsx5("box", {
26466
+ flexGrow: 1,
26467
+ children: /* @__PURE__ */ jsx5("input", {
26468
+ focused,
26469
+ value: draft,
26470
+ placeholder: focused ? "Type command, Enter to send, Esc to unfocus" : "Click to focus terminal",
26471
+ onInput: (v) => setDraft(v),
26472
+ onSubmit: () => {
26473
+ if (!draft)
26474
+ return;
26475
+ onSend(draft + `
26476
+ `);
26477
+ setDraft("");
26478
+ }
26479
+ })
26480
+ })
26481
+ ]
26482
+ }),
26483
+ !focused && /* @__PURE__ */ jsx5("text", {
26484
+ fg: tokens.dim,
26485
+ children: "Click terminal or press 't' to focus · Ctrl+G or click table to unfocus"
26486
+ }),
26487
+ focused && /* @__PURE__ */ jsx5("text", {
26488
+ fg: tokens.accent,
26489
+ children: "Focused — typing goes to shell · Ctrl+G to unfocus · max 5 per worktree persist across switches"
26490
+ })
26491
+ ]
26492
+ });
26493
+ }
26494
+ var init_TerminalView = __esm(() => {
26284
26495
  init_theme();
26285
26496
  init_use_tap();
26286
26497
  });
26287
26498
 
26288
26499
  // src/tui/components/Footer.tsx
26289
- import { jsx as jsx3, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
26500
+ import { jsx as jsx6, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
26290
26501
  function HintItem({ hintKey, label, isFirst, onClick }) {
26291
26502
  const tap = useTapHandler(() => onClick?.(hintKey));
26292
- return /* @__PURE__ */ jsx3("box", {
26503
+ return /* @__PURE__ */ jsx6("box", {
26293
26504
  flexDirection: "row",
26294
26505
  ...onClick ? tap : {},
26295
- children: /* @__PURE__ */ jsxs3("text", {
26506
+ children: /* @__PURE__ */ jsxs6("text", {
26296
26507
  children: [
26297
- /* @__PURE__ */ jsx3("span", {
26508
+ /* @__PURE__ */ jsx6("span", {
26298
26509
  fg: tokens.dim,
26299
26510
  children: isFirst ? "" : " · "
26300
26511
  }),
26301
- /* @__PURE__ */ jsx3("span", {
26512
+ /* @__PURE__ */ jsx6("span", {
26302
26513
  fg: tokens.dim,
26303
26514
  children: `${hintKey} ${label}`
26304
26515
  })
@@ -26308,15 +26519,15 @@ function HintItem({ hintKey, label, isFirst, onClick }) {
26308
26519
  }
26309
26520
  function ErrorBadge({ count: count2, onClick }) {
26310
26521
  const tap = useTapHandler(() => onClick?.());
26311
- return /* @__PURE__ */ jsx3("box", {
26522
+ return /* @__PURE__ */ jsx6("box", {
26312
26523
  ...onClick ? tap : {},
26313
- children: /* @__PURE__ */ jsxs3("text", {
26524
+ children: /* @__PURE__ */ jsxs6("text", {
26314
26525
  children: [
26315
- /* @__PURE__ */ jsx3("span", {
26526
+ /* @__PURE__ */ jsx6("span", {
26316
26527
  fg: tokens.error,
26317
26528
  children: `${count2} error${count2 !== 1 ? "s" : ""}`
26318
26529
  }),
26319
- /* @__PURE__ */ jsx3("span", {
26530
+ /* @__PURE__ */ jsx6("span", {
26320
26531
  fg: tokens.dim,
26321
26532
  children: " · e view"
26322
26533
  })
@@ -26326,7 +26537,7 @@ function ErrorBadge({ count: count2, onClick }) {
26326
26537
  }
26327
26538
  function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinnerFrame, filter, onHintClick, onErrorClick }) {
26328
26539
  const frame = spinnerFrame ?? "◌";
26329
- return /* @__PURE__ */ jsxs3("box", {
26540
+ return /* @__PURE__ */ jsxs6("box", {
26330
26541
  id: "footer",
26331
26542
  width: "100%",
26332
26543
  height: 3,
@@ -26337,28 +26548,29 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26337
26548
  justifyContent: "space-between",
26338
26549
  alignItems: "center",
26339
26550
  children: [
26340
- busyText ? /* @__PURE__ */ jsx3("text", {
26551
+ busyText ? /* @__PURE__ */ jsx6("text", {
26341
26552
  fg: tokens.accent,
26342
26553
  children: `${frame} ${busyText}`
26343
- }) : /* @__PURE__ */ jsx3("box", {
26554
+ }) : /* @__PURE__ */ jsx6("box", {
26344
26555
  flexDirection: "row",
26345
26556
  gap: 1,
26346
- children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx3(HintItem, {
26557
+ children: HINTS.map(([key, label], idx) => /* @__PURE__ */ jsx6(HintItem, {
26347
26558
  hintKey: key,
26348
26559
  label,
26349
26560
  isFirst: idx === 0,
26350
26561
  onClick: onHintClick
26351
26562
  }, key))
26352
26563
  }),
26353
- /* @__PURE__ */ jsxs3("box", {
26564
+ /* @__PURE__ */ jsxs6("box", {
26354
26565
  flexDirection: "row",
26355
26566
  gap: 2,
26567
+ alignItems: "center",
26356
26568
  children: [
26357
- message ? /* @__PURE__ */ jsx3("text", {
26569
+ message ? /* @__PURE__ */ jsx6("text", {
26358
26570
  fg: tokens.warning,
26359
26571
  children: message
26360
26572
  }) : null,
26361
- filter ? /* @__PURE__ */ jsxs3("text", {
26573
+ filter ? /* @__PURE__ */ jsxs6("text", {
26362
26574
  fg: "magenta",
26363
26575
  children: [
26364
26576
  "filter: ",
@@ -26370,11 +26582,17 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
26370
26582
  ")"
26371
26583
  ]
26372
26584
  }) : null,
26373
- errorCount > 0 ? /* @__PURE__ */ jsx3(ErrorBadge, {
26585
+ errorCount > 0 ? /* @__PURE__ */ jsx6(ErrorBadge, {
26374
26586
  count: errorCount,
26375
26587
  onClick: onErrorClick
26376
26588
  }) : null,
26377
- /* @__PURE__ */ jsx3("text", {
26589
+ /* @__PURE__ */ jsx6(HintItem, {
26590
+ hintKey: "?",
26591
+ label: "help",
26592
+ isFirst: errorCount === 0,
26593
+ onClick: onHintClick
26594
+ }),
26595
+ /* @__PURE__ */ jsx6("text", {
26378
26596
  fg: tokens.dim,
26379
26597
  children: loading ? `${frame} Refreshing…` : `Updated ${lastRefreshed}`
26380
26598
  })
@@ -26388,33 +26606,22 @@ var init_Footer = __esm(() => {
26388
26606
  init_theme();
26389
26607
  init_use_tap();
26390
26608
  HINTS = [
26391
- ["c", "config"],
26392
26609
  ["n", "create"],
26393
- ["f", "fetch"],
26394
- ["?", "help"],
26395
- ["H", "history"],
26396
- ["o", "ide"],
26397
- ["i", "install"],
26398
- ["p", "pull"],
26399
- ["b", "rebase"],
26400
- ["r", "refresh"],
26401
- ["d", "remove"],
26402
- ["m", "rename"],
26403
- ["s", "sync"]
26610
+ ["d", "remove"]
26404
26611
  ];
26405
26612
  });
26406
26613
 
26407
26614
  // src/tui/components/Divider.tsx
26408
- import { useState as useState2, useRef as useRef4, useCallback as useCallback3 } from "react";
26615
+ import { useState as useState3, useRef as useRef5, useCallback as useCallback4 } from "react";
26409
26616
  import { useRenderer } from "@opentui/react";
26410
- import { jsx as jsx4 } from "@opentui/react/jsx-runtime";
26617
+ import { jsx as jsx7 } from "@opentui/react/jsx-runtime";
26411
26618
  function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26412
26619
  const renderer = useRenderer();
26413
- const [hovered, setHovered] = useState2(false);
26414
- const [dragging, setDragging] = useState2(false);
26415
- const startXRef = useRef4(null);
26416
- const startRatioRef = useRef4(splitRatio);
26417
- const handleMouseDown = useCallback3((e) => {
26620
+ const [hovered, setHovered] = useState3(false);
26621
+ const [dragging, setDragging] = useState3(false);
26622
+ const startXRef = useRef5(null);
26623
+ const startRatioRef = useRef5(splitRatio);
26624
+ const handleMouseDown = useCallback4((e) => {
26418
26625
  if (e.button !== 0)
26419
26626
  return;
26420
26627
  startXRef.current = e.x;
@@ -26424,7 +26631,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26424
26631
  renderer.clearSelection();
26425
26632
  e.stopPropagation();
26426
26633
  }, [splitRatio, onDraggingChange, renderer]);
26427
- const handleMouseUp = useCallback3((e) => {
26634
+ const handleMouseUp = useCallback4((e) => {
26428
26635
  if (e.button !== 0)
26429
26636
  return;
26430
26637
  startXRef.current = null;
@@ -26432,7 +26639,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26432
26639
  onDraggingChange?.(false);
26433
26640
  e.stopPropagation();
26434
26641
  }, [onDraggingChange]);
26435
- const handleMouseDrag = useCallback3((e) => {
26642
+ const handleMouseDrag = useCallback4((e) => {
26436
26643
  if (startXRef.current === null)
26437
26644
  return;
26438
26645
  renderer.clearSelection();
@@ -26447,7 +26654,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26447
26654
  onMouseDrag: handleMouseDrag,
26448
26655
  onMouseMove: handleMouseDrag
26449
26656
  };
26450
- return /* @__PURE__ */ jsx4("box", {
26657
+ return /* @__PURE__ */ jsx7("box", {
26451
26658
  width: 3,
26452
26659
  height: "100%",
26453
26660
  backgroundColor: dragging ? tokens.selectionBg : hovered ? tokens.panelBg : undefined,
@@ -26456,7 +26663,7 @@ function Divider({ splitRatio, totalWidth, onChange, onDraggingChange }) {
26456
26663
  onMouseOut: () => setHovered(false),
26457
26664
  alignItems: "center",
26458
26665
  justifyContent: "center",
26459
- children: /* @__PURE__ */ jsx4("text", {
26666
+ children: /* @__PURE__ */ jsx7("text", {
26460
26667
  fg: dragging ? tokens.accent : hovered ? tokens.borderActive : tokens.border,
26461
26668
  ...dragHandlers,
26462
26669
  onMouseOver: () => setHovered(true),
@@ -26471,9 +26678,9 @@ var init_Divider = __esm(() => {
26471
26678
  });
26472
26679
 
26473
26680
  // src/tui/components/Overlay.tsx
26474
- import { jsx as jsx5 } from "@opentui/react/jsx-runtime";
26681
+ import { jsx as jsx8 } from "@opentui/react/jsx-runtime";
26475
26682
  function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26476
- return /* @__PURE__ */ jsx5("box", {
26683
+ return /* @__PURE__ */ jsx8("box", {
26477
26684
  id: "overlay-scrim",
26478
26685
  position: "absolute",
26479
26686
  width: "100%",
@@ -26485,7 +26692,7 @@ function Overlay({ title, borderColor = tokens.border, width = 64, children }) {
26485
26692
  flexDirection: "row",
26486
26693
  justifyContent: "center",
26487
26694
  alignItems: "center",
26488
- children: /* @__PURE__ */ jsx5("box", {
26695
+ children: /* @__PURE__ */ jsx8("box", {
26489
26696
  id: "overlay-panel",
26490
26697
  width,
26491
26698
  border: true,
@@ -26505,34 +26712,47 @@ var init_Overlay = __esm(() => {
26505
26712
  });
26506
26713
 
26507
26714
  // src/tui/components/HelpOverlay.tsx
26508
- import { jsx as jsx6, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
26715
+ import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
26509
26716
  function HelpOverlay() {
26510
- return /* @__PURE__ */ jsxs4(Overlay, {
26717
+ return /* @__PURE__ */ jsxs7(Overlay, {
26511
26718
  title: "Help",
26512
26719
  borderColor: tokens.border,
26720
+ width: 84,
26513
26721
  children: [
26514
- /* @__PURE__ */ jsx6("box", {
26722
+ /* @__PURE__ */ jsx9("box", {
26515
26723
  flexDirection: "column",
26516
- style: { maxHeight: 40 },
26517
- children: /* @__PURE__ */ jsx6("scrollbox", {
26724
+ style: { maxHeight: 36 },
26725
+ children: /* @__PURE__ */ jsx9("scrollbox", {
26518
26726
  flexGrow: 1,
26519
- children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs4("text", {
26727
+ children: HELP_ENTRIES.map(([key, desc]) => /* @__PURE__ */ jsxs7("box", {
26728
+ flexDirection: "row",
26729
+ gap: 1,
26520
26730
  children: [
26521
- /* @__PURE__ */ jsx6("span", {
26522
- fg: tokens.accent,
26523
- children: key.padEnd(10)
26731
+ /* @__PURE__ */ jsx9("box", {
26732
+ width: 18,
26733
+ flexShrink: 0,
26734
+ justifyContent: "flex-end",
26735
+ children: /* @__PURE__ */ jsx9("text", {
26736
+ fg: tokens.accent,
26737
+ children: key
26738
+ })
26524
26739
  }),
26525
- /* @__PURE__ */ jsx6("span", {
26526
- children: " - "
26740
+ /* @__PURE__ */ jsx9("text", {
26741
+ fg: tokens.dim,
26742
+ children: "-"
26527
26743
  }),
26528
- /* @__PURE__ */ jsx6("span", {
26529
- children: desc
26744
+ /* @__PURE__ */ jsx9("box", {
26745
+ flexGrow: 1,
26746
+ children: /* @__PURE__ */ jsx9("text", {
26747
+ fg: tokens.fg,
26748
+ children: desc
26749
+ })
26530
26750
  })
26531
26751
  ]
26532
26752
  }, key))
26533
26753
  })
26534
26754
  }),
26535
- /* @__PURE__ */ jsx6("text", {
26755
+ /* @__PURE__ */ jsx9("text", {
26536
26756
  style: { marginTop: 1, fg: tokens.dim },
26537
26757
  children: "Press any key to close"
26538
26758
  })
@@ -26557,7 +26777,9 @@ var init_HelpOverlay = __esm(() => {
26557
26777
  ["i", "Install dependencies in selection (worktrees and main)"],
26558
26778
  ["m", "Rename selected worktree (branch + directory)"],
26559
26779
  ["o", "Open selected in IDE"],
26560
- ["a", "Spawn agent in selected"],
26780
+ ["t", "New terminal session (max 5 per worktree, Details ↔ Session tabs)"],
26781
+ ["click tab", "Switch Details ↔ Session (professional tabs with ▎ active)"],
26782
+ ["Ctrl+G / click table", "Leave terminal focus — focused terminal gets all keys"],
26561
26783
  ["d", "Remove selected worktree(s)"],
26562
26784
  ["e", "View data warnings (when count > 0)"],
26563
26785
  ["r", "Refresh data"],
@@ -26571,50 +26793,50 @@ var init_HelpOverlay = __esm(() => {
26571
26793
  });
26572
26794
 
26573
26795
  // src/tui/components/ConfirmModal.tsx
26574
- import { jsx as jsx7, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
26796
+ import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
26575
26797
  function ConfirmModal({ title, message, onConfirm, onCancel }) {
26576
26798
  const yesTap = useTapHandler(() => onConfirm?.());
26577
26799
  const noTap = useTapHandler(() => onCancel?.());
26578
- return /* @__PURE__ */ jsx7(Overlay, {
26800
+ return /* @__PURE__ */ jsx10(Overlay, {
26579
26801
  title,
26580
26802
  borderColor: tokens.warning,
26581
- children: /* @__PURE__ */ jsxs5("box", {
26803
+ children: /* @__PURE__ */ jsxs8("box", {
26582
26804
  flexDirection: "column",
26583
26805
  justifyContent: "center",
26584
26806
  alignItems: "center",
26585
26807
  children: [
26586
- /* @__PURE__ */ jsx7("text", {
26808
+ /* @__PURE__ */ jsx10("text", {
26587
26809
  children: message
26588
26810
  }),
26589
- /* @__PURE__ */ jsxs5("box", {
26811
+ /* @__PURE__ */ jsxs8("box", {
26590
26812
  flexDirection: "row",
26591
26813
  gap: 2,
26592
26814
  style: { marginTop: 2 },
26593
26815
  children: [
26594
- /* @__PURE__ */ jsx7("box", {
26816
+ /* @__PURE__ */ jsx10("box", {
26595
26817
  ...onConfirm ? yesTap : {},
26596
- children: /* @__PURE__ */ jsxs5("text", {
26818
+ children: /* @__PURE__ */ jsxs8("text", {
26597
26819
  children: [
26598
- /* @__PURE__ */ jsx7("span", {
26820
+ /* @__PURE__ */ jsx10("span", {
26599
26821
  fg: tokens.dim,
26600
26822
  children: "[y] "
26601
26823
  }),
26602
- /* @__PURE__ */ jsx7("span", {
26824
+ /* @__PURE__ */ jsx10("span", {
26603
26825
  fg: tokens.success,
26604
26826
  children: "Yes"
26605
26827
  })
26606
26828
  ]
26607
26829
  })
26608
26830
  }),
26609
- /* @__PURE__ */ jsx7("box", {
26831
+ /* @__PURE__ */ jsx10("box", {
26610
26832
  ...onCancel ? noTap : {},
26611
- children: /* @__PURE__ */ jsxs5("text", {
26833
+ children: /* @__PURE__ */ jsxs8("text", {
26612
26834
  children: [
26613
- /* @__PURE__ */ jsx7("span", {
26835
+ /* @__PURE__ */ jsx10("span", {
26614
26836
  fg: tokens.dim,
26615
26837
  children: "[n/esc] "
26616
26838
  }),
26617
- /* @__PURE__ */ jsx7("span", {
26839
+ /* @__PURE__ */ jsx10("span", {
26618
26840
  children: "Cancel"
26619
26841
  })
26620
26842
  ]
@@ -26690,52 +26912,52 @@ async function runWtxAction(args, onLine) {
26690
26912
  }
26691
26913
 
26692
26914
  // src/tui/components/ActionLogModal.tsx
26693
- import { jsx as jsx8, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
26915
+ import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
26694
26916
  function ActionLogModal({ title, lines, done, exitCode, remaining }) {
26695
- return /* @__PURE__ */ jsxs6(Overlay, {
26917
+ return /* @__PURE__ */ jsxs9(Overlay, {
26696
26918
  title,
26697
26919
  borderColor: tokens.border,
26698
26920
  children: [
26699
- /* @__PURE__ */ jsx8("box", {
26921
+ /* @__PURE__ */ jsx11("box", {
26700
26922
  flexGrow: 1,
26701
26923
  flexDirection: "column",
26702
26924
  style: { minHeight: 10, maxHeight: 20 },
26703
- children: /* @__PURE__ */ jsx8("scrollbox", {
26925
+ children: /* @__PURE__ */ jsx11("scrollbox", {
26704
26926
  flexGrow: 1,
26705
26927
  stickyScroll: true,
26706
- children: lines.map((line, i2) => /* @__PURE__ */ jsx8("text", {
26928
+ children: lines.map((line, i2) => /* @__PURE__ */ jsx11("text", {
26707
26929
  fg: line.type === "err" ? tokens.error : tokens.fg,
26708
26930
  children: line.text
26709
26931
  }, i2))
26710
26932
  })
26711
26933
  }),
26712
- /* @__PURE__ */ jsxs6("box", {
26934
+ /* @__PURE__ */ jsxs9("box", {
26713
26935
  marginTop: 1,
26714
26936
  flexDirection: "row",
26715
26937
  justifyContent: "center",
26716
26938
  children: [
26717
- !done && /* @__PURE__ */ jsx8("text", {
26939
+ !done && /* @__PURE__ */ jsx11("text", {
26718
26940
  fg: tokens.warning,
26719
26941
  children: "Running..."
26720
26942
  }),
26721
- done && exitCode === 0 && /* @__PURE__ */ jsx8("text", {
26943
+ done && exitCode === 0 && /* @__PURE__ */ jsx11("text", {
26722
26944
  fg: tokens.success,
26723
26945
  children: "✓ Done (exit 0)"
26724
26946
  }),
26725
- done && exitCode !== 0 && /* @__PURE__ */ jsxs6("text", {
26947
+ done && exitCode !== 0 && /* @__PURE__ */ jsxs9("text", {
26726
26948
  children: [
26727
- /* @__PURE__ */ jsxs6("span", {
26949
+ /* @__PURE__ */ jsxs9("span", {
26728
26950
  fg: tokens.error,
26729
26951
  children: [
26730
26952
  "✗ Exit ",
26731
26953
  exitCode
26732
26954
  ]
26733
26955
  }),
26734
- remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx8("span", {
26956
+ remaining !== undefined && remaining > 0 && /* @__PURE__ */ jsx11("span", {
26735
26957
  fg: tokens.warning,
26736
26958
  children: ` · ${remaining} more failure${remaining > 1 ? "s" : ""}`
26737
26959
  }),
26738
- /* @__PURE__ */ jsx8("span", {
26960
+ /* @__PURE__ */ jsx11("span", {
26739
26961
  fg: tokens.dim,
26740
26962
  children: " - press any key to close"
26741
26963
  })
@@ -26822,8 +27044,8 @@ var init_history = __esm(() => {
26822
27044
  });
26823
27045
 
26824
27046
  // src/tui/components/HistoryOverlay.tsx
26825
- import { useEffect as useEffect3, useState as useState3 } from "react";
26826
- import { jsx as jsx9, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
27047
+ import { useEffect as useEffect4, useState as useState4 } from "react";
27048
+ import { jsx as jsx12, jsxs as jsxs10 } from "@opentui/react/jsx-runtime";
26827
27049
  function formatShortTime(iso) {
26828
27050
  const d = new Date(iso);
26829
27051
  if (Number.isNaN(d.getTime()))
@@ -26836,16 +27058,16 @@ function HistoryEntryLine({ entry }) {
26836
27058
  const duration3 = entry.durationMs !== null && entry.durationMs !== undefined ? ` ${entry.durationMs}ms` : "";
26837
27059
  const prefix = `${formatShortTime(entry.ts)} ${entry.source.padEnd(8)} ${args}`;
26838
27060
  if (entry.exit === null || entry.exit === undefined) {
26839
- return /* @__PURE__ */ jsxs7("text", {
27061
+ return /* @__PURE__ */ jsxs10("text", {
26840
27062
  children: [
26841
- /* @__PURE__ */ jsxs7("span", {
27063
+ /* @__PURE__ */ jsxs10("span", {
26842
27064
  fg: tokens.dim,
26843
27065
  children: [
26844
27066
  "◌ ",
26845
27067
  prefix
26846
27068
  ]
26847
27069
  }),
26848
- duration3 && /* @__PURE__ */ jsx9("span", {
27070
+ duration3 && /* @__PURE__ */ jsx12("span", {
26849
27071
  fg: tokens.dim,
26850
27072
  children: duration3
26851
27073
  })
@@ -26853,32 +27075,32 @@ function HistoryEntryLine({ entry }) {
26853
27075
  });
26854
27076
  }
26855
27077
  if (entry.exit === 0) {
26856
- return /* @__PURE__ */ jsxs7("text", {
27078
+ return /* @__PURE__ */ jsxs10("text", {
26857
27079
  children: [
26858
- /* @__PURE__ */ jsxs7("span", {
27080
+ /* @__PURE__ */ jsxs10("span", {
26859
27081
  fg: tokens.success,
26860
27082
  children: [
26861
27083
  "✓ ",
26862
27084
  prefix
26863
27085
  ]
26864
27086
  }),
26865
- duration3 && /* @__PURE__ */ jsx9("span", {
27087
+ duration3 && /* @__PURE__ */ jsx12("span", {
26866
27088
  fg: tokens.dim,
26867
27089
  children: duration3
26868
27090
  })
26869
27091
  ]
26870
27092
  });
26871
27093
  }
26872
- return /* @__PURE__ */ jsxs7("text", {
27094
+ return /* @__PURE__ */ jsxs10("text", {
26873
27095
  children: [
26874
- /* @__PURE__ */ jsx9("span", {
27096
+ /* @__PURE__ */ jsx12("span", {
26875
27097
  fg: tokens.error,
26876
27098
  children: "✗ "
26877
27099
  }),
26878
- /* @__PURE__ */ jsx9("span", {
27100
+ /* @__PURE__ */ jsx12("span", {
26879
27101
  children: prefix
26880
27102
  }),
26881
- /* @__PURE__ */ jsxs7("span", {
27103
+ /* @__PURE__ */ jsxs10("span", {
26882
27104
  fg: tokens.dim,
26883
27105
  children: [
26884
27106
  " (exit ",
@@ -26891,32 +27113,32 @@ function HistoryEntryLine({ entry }) {
26891
27113
  });
26892
27114
  }
26893
27115
  function HistoryOverlay() {
26894
- const [entries, setEntries] = useState3([]);
26895
- useEffect3(() => {
27116
+ const [entries, setEntries] = useState4([]);
27117
+ useEffect4(() => {
26896
27118
  setEntries(readRecentHistory(500));
26897
27119
  }, []);
26898
- return /* @__PURE__ */ jsxs7(Overlay, {
27120
+ return /* @__PURE__ */ jsxs10(Overlay, {
26899
27121
  title: "Action History",
26900
27122
  borderColor: tokens.border,
26901
27123
  width: 110,
26902
27124
  children: [
26903
- /* @__PURE__ */ jsx9("box", {
27125
+ /* @__PURE__ */ jsx12("box", {
26904
27126
  flexGrow: 1,
26905
27127
  flexDirection: "column",
26906
27128
  style: { minHeight: 30, maxHeight: 46 },
26907
- children: entries.length === 0 ? /* @__PURE__ */ jsx9("text", {
27129
+ children: entries.length === 0 ? /* @__PURE__ */ jsx12("text", {
26908
27130
  fg: tokens.dim,
26909
27131
  children: "No actions recorded yet."
26910
- }) : /* @__PURE__ */ jsx9("scrollbox", {
27132
+ }) : /* @__PURE__ */ jsx12("scrollbox", {
26911
27133
  flexGrow: 1,
26912
- children: entries.map((entry, i2) => /* @__PURE__ */ jsx9(HistoryEntryLine, {
27134
+ children: entries.map((entry, i2) => /* @__PURE__ */ jsx12(HistoryEntryLine, {
26913
27135
  entry
26914
27136
  }, i2))
26915
27137
  })
26916
27138
  }),
26917
- /* @__PURE__ */ jsx9("box", {
27139
+ /* @__PURE__ */ jsx12("box", {
26918
27140
  marginTop: 1,
26919
- children: /* @__PURE__ */ jsx9("text", {
27141
+ children: /* @__PURE__ */ jsx12("text", {
26920
27142
  fg: tokens.dim,
26921
27143
  children: "Press any key to close · newest first"
26922
27144
  })
@@ -26931,28 +27153,28 @@ var init_HistoryOverlay = __esm(() => {
26931
27153
  });
26932
27154
 
26933
27155
  // src/tui/components/InputModal.tsx
26934
- import { useState as useState4 } from "react";
26935
- import { jsx as jsx10, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
27156
+ import { useState as useState5 } from "react";
27157
+ import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
26936
27158
  function InputModal({ title, placeholder, initialValue, errorMessage: errorMessage2, onSubmit }) {
26937
- const [value, setValue] = useState4(initialValue ?? "");
26938
- return /* @__PURE__ */ jsx10(Overlay, {
27159
+ const [value, setValue] = useState5(initialValue ?? "");
27160
+ return /* @__PURE__ */ jsx13(Overlay, {
26939
27161
  title,
26940
27162
  borderColor: tokens.accent,
26941
- children: /* @__PURE__ */ jsxs8("box", {
27163
+ children: /* @__PURE__ */ jsxs11("box", {
26942
27164
  flexDirection: "column",
26943
27165
  children: [
26944
- /* @__PURE__ */ jsx10("input", {
27166
+ /* @__PURE__ */ jsx13("input", {
26945
27167
  focused: true,
26946
27168
  value: initialValue ?? "",
26947
27169
  placeholder,
26948
27170
  onInput: (val) => setValue(val),
26949
27171
  onSubmit: () => onSubmit(value)
26950
27172
  }),
26951
- errorMessage2 ? /* @__PURE__ */ jsx10("text", {
27173
+ errorMessage2 ? /* @__PURE__ */ jsx13("text", {
26952
27174
  fg: tokens.error,
26953
27175
  style: { marginTop: 1 },
26954
27176
  children: errorMessage2
26955
- }) : /* @__PURE__ */ jsx10("text", {
27177
+ }) : /* @__PURE__ */ jsx13("text", {
26956
27178
  fg: tokens.dim,
26957
27179
  style: { marginTop: 1 },
26958
27180
  children: "Press Enter to submit, empty to cancel"
@@ -26967,23 +27189,23 @@ var init_InputModal = __esm(() => {
26967
27189
  });
26968
27190
 
26969
27191
  // src/tui/components/ChoiceModal.tsx
26970
- import { useState as useState5 } from "react";
27192
+ import { useState as useState6 } from "react";
26971
27193
  import { useKeyboard } from "@opentui/react";
26972
- import { jsx as jsx11, jsxs as jsxs9 } from "@opentui/react/jsx-runtime";
27194
+ import { jsx as jsx14, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
26973
27195
  function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
26974
27196
  const tap = useTapHandler(() => onSelect(option.value));
26975
- return /* @__PURE__ */ jsxs9("box", {
27197
+ return /* @__PURE__ */ jsxs12("box", {
26976
27198
  flexDirection: "column",
26977
27199
  backgroundColor: isSelected ? tokens.selectionBg : undefined,
26978
27200
  ...tap,
26979
27201
  children: [
26980
- /* @__PURE__ */ jsx11("text", {
26981
- children: /* @__PURE__ */ jsx11("span", {
27202
+ /* @__PURE__ */ jsx14("text", {
27203
+ children: /* @__PURE__ */ jsx14("span", {
26982
27204
  fg: isSelected ? tokens.bright : tokens.fg,
26983
27205
  children: `${index + 1}. ${option.label}`
26984
27206
  })
26985
27207
  }),
26986
- option.desc && /* @__PURE__ */ jsx11("text", {
27208
+ option.desc && /* @__PURE__ */ jsx14("text", {
26987
27209
  fg: tokens.dim,
26988
27210
  children: ` ${option.desc}`
26989
27211
  })
@@ -26991,7 +27213,7 @@ function ChoiceOptionRow({ option, index, isSelected, onSelect }) {
26991
27213
  });
26992
27214
  }
26993
27215
  function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
26994
- const [index, setIndex] = useState5(Math.min(Math.max(initialIndex, 0), options.length - 1));
27216
+ const [index, setIndex] = useState6(Math.min(Math.max(initialIndex, 0), options.length - 1));
26995
27217
  useKeyboard((key) => {
26996
27218
  if (key.name === "escape") {
26997
27219
  onCancel();
@@ -27018,19 +27240,19 @@ function ChoiceModal({ title, options, initialIndex = 0, onSubmit, onCancel }) {
27018
27240
  onSubmit(option.value);
27019
27241
  }
27020
27242
  });
27021
- return /* @__PURE__ */ jsx11(Overlay, {
27243
+ return /* @__PURE__ */ jsx14(Overlay, {
27022
27244
  title,
27023
27245
  borderColor: tokens.accent,
27024
- children: /* @__PURE__ */ jsxs9("box", {
27246
+ children: /* @__PURE__ */ jsxs12("box", {
27025
27247
  flexDirection: "column",
27026
27248
  children: [
27027
- options.map((option, i2) => /* @__PURE__ */ jsx11(ChoiceOptionRow, {
27249
+ options.map((option, i2) => /* @__PURE__ */ jsx14(ChoiceOptionRow, {
27028
27250
  option,
27029
27251
  index: i2,
27030
27252
  isSelected: i2 === index,
27031
27253
  onSelect: onSubmit
27032
27254
  }, option.value)),
27033
- /* @__PURE__ */ jsxs9("text", {
27255
+ /* @__PURE__ */ jsxs12("text", {
27034
27256
  fg: tokens.dim,
27035
27257
  style: { marginTop: 1 },
27036
27258
  children: [
@@ -27050,16 +27272,16 @@ var init_ChoiceModal = __esm(() => {
27050
27272
  });
27051
27273
 
27052
27274
  // src/tui/components/ConfigOverlay.tsx
27053
- import { useState as useState6, useEffect as useEffect4 } from "react";
27275
+ import { useState as useState7, useEffect as useEffect5 } from "react";
27054
27276
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
27055
- import { jsx as jsx12, jsxs as jsxs10, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27277
+ import { jsx as jsx15, jsxs as jsxs13, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
27056
27278
  function ConfigOverlay({ onClose, onSaved, onError }) {
27057
- const [config2, setConfig] = useState6(null);
27058
- const [viewState, setViewState] = useState6({ type: "main" });
27059
- const [selectedIndex, setSelectedIndex] = useState6(1);
27060
- const [windowStart, setWindowStart] = useState6(0);
27061
- const [editState, setEditState] = useState6({ type: "none" });
27062
- useEffect4(() => {
27279
+ const [config2, setConfig] = useState7(null);
27280
+ const [viewState, setViewState] = useState7({ type: "main" });
27281
+ const [selectedIndex, setSelectedIndex] = useState7(1);
27282
+ const [windowStart, setWindowStart] = useState7(0);
27283
+ const [editState, setEditState] = useState7({ type: "none" });
27284
+ useEffect5(() => {
27063
27285
  try {
27064
27286
  setConfig(loadConfig());
27065
27287
  } catch (err) {
@@ -27095,7 +27317,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27095
27317
  detailRows.push({ type: "remove_repo" });
27096
27318
  }
27097
27319
  const activeRows = viewState.type === "main" ? mainRows : detailRows;
27098
- useEffect4(() => {
27320
+ useEffect5(() => {
27099
27321
  if (!activeRows || selectedIndex >= activeRows.length) {
27100
27322
  let newIdx = activeRows.length - 1;
27101
27323
  while (newIdx > 0 && activeRows[newIdx]?.type === "header") {
@@ -27315,17 +27537,17 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27315
27537
  hint = "Remove this repository from config";
27316
27538
  }
27317
27539
  }
27318
- return /* @__PURE__ */ jsxs10(Fragment2, {
27540
+ return /* @__PURE__ */ jsxs13(Fragment2, {
27319
27541
  children: [
27320
- /* @__PURE__ */ jsx12(Overlay, {
27542
+ /* @__PURE__ */ jsx15(Overlay, {
27321
27543
  title: "Configuration",
27322
27544
  borderColor: tokens.border,
27323
- children: /* @__PURE__ */ jsxs10("box", {
27545
+ children: /* @__PURE__ */ jsxs13("box", {
27324
27546
  flexDirection: "column",
27325
27547
  width: "100%",
27326
27548
  height: 24,
27327
27549
  children: [
27328
- /* @__PURE__ */ jsx12("box", {
27550
+ /* @__PURE__ */ jsx15("box", {
27329
27551
  flexDirection: "column",
27330
27552
  flexGrow: 1,
27331
27553
  overflow: "hidden",
@@ -27333,7 +27555,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27333
27555
  const i2 = windowStart + idx;
27334
27556
  const isSelected = i2 === selectedIndex;
27335
27557
  if (row.type === "header") {
27336
- return /* @__PURE__ */ jsx12("text", {
27558
+ return /* @__PURE__ */ jsx15("text", {
27337
27559
  fg: tokens.dim,
27338
27560
  style: { marginTop: i2 === 0 ? 0 : 1 },
27339
27561
  children: row.label
@@ -27362,11 +27584,11 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27362
27584
  labelText = "Remove this repo";
27363
27585
  valText = "";
27364
27586
  }
27365
- return /* @__PURE__ */ jsxs10("box", {
27587
+ return /* @__PURE__ */ jsxs13("box", {
27366
27588
  flexDirection: "row",
27367
27589
  width: "100%",
27368
27590
  children: [
27369
- /* @__PURE__ */ jsxs10("text", {
27591
+ /* @__PURE__ */ jsxs13("text", {
27370
27592
  fg: isSelected ? tokens.bright : tokens.fg,
27371
27593
  bg: isSelected ? tokens.selectionBg : undefined,
27372
27594
  width: 24,
@@ -27375,7 +27597,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27375
27597
  labelText
27376
27598
  ]
27377
27599
  }),
27378
- valText && /* @__PURE__ */ jsx12("text", {
27600
+ valText && /* @__PURE__ */ jsx15("text", {
27379
27601
  fg: isSelected ? tokens.accent : tokens.dim,
27380
27602
  bg: isSelected ? tokens.selectionBg : undefined,
27381
27603
  flexShrink: 1,
@@ -27385,14 +27607,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27385
27607
  }, i2);
27386
27608
  })
27387
27609
  }),
27388
- /* @__PURE__ */ jsx12("box", {
27610
+ /* @__PURE__ */ jsx15("box", {
27389
27611
  flexDirection: "row",
27390
27612
  border: true,
27391
27613
  borderColor: tokens.border,
27392
27614
  paddingTop: 1,
27393
27615
  marginTop: 1,
27394
27616
  minHeight: 3,
27395
- children: /* @__PURE__ */ jsx12("text", {
27617
+ children: /* @__PURE__ */ jsx15("text", {
27396
27618
  fg: tokens.dim,
27397
27619
  children: hint
27398
27620
  })
@@ -27400,14 +27622,14 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
27400
27622
  ]
27401
27623
  })
27402
27624
  }),
27403
- editState.type === "input" && /* @__PURE__ */ jsx12(InputModal, {
27625
+ editState.type === "input" && /* @__PURE__ */ jsx15(InputModal, {
27404
27626
  title: editState.title,
27405
27627
  initialValue: editState.currentValue,
27406
27628
  placeholder: "Enter value...",
27407
27629
  errorMessage: editState.error,
27408
27630
  onSubmit: handleInputSubmit
27409
27631
  }),
27410
- editState.type === "confirm_remove" && /* @__PURE__ */ jsx12(ConfirmModal, {
27632
+ editState.type === "confirm_remove" && /* @__PURE__ */ jsx15(ConfirmModal, {
27411
27633
  title: "Remove Repo",
27412
27634
  message: `Remove repo ${editState.repo} from config?`
27413
27635
  })
@@ -27424,26 +27646,29 @@ var init_ConfigOverlay = __esm(() => {
27424
27646
  });
27425
27647
 
27426
27648
  // src/tui/components/WarningsOverlay.tsx
27427
- import { jsx as jsx13, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
27428
- function WarningsOverlay({ warnings }) {
27429
- return /* @__PURE__ */ jsxs11(Overlay, {
27649
+ import { jsx as jsx16, jsxs as jsxs14 } from "@opentui/react/jsx-runtime";
27650
+ function WarningsOverlay({ warnings, onAcknowledge, onClose }) {
27651
+ const ackTap = useTapHandler(() => onAcknowledge?.());
27652
+ const closeTap = useTapHandler(() => onClose?.());
27653
+ return /* @__PURE__ */ jsxs14(Overlay, {
27430
27654
  title: `Warnings (${warnings.length})`,
27431
27655
  borderColor: tokens.warning,
27656
+ width: 80,
27432
27657
  children: [
27433
- /* @__PURE__ */ jsx13("box", {
27658
+ /* @__PURE__ */ jsx16("box", {
27434
27659
  flexDirection: "column",
27435
- style: { maxHeight: 30 },
27436
- children: /* @__PURE__ */ jsx13("scrollbox", {
27660
+ style: { maxHeight: 28 },
27661
+ children: /* @__PURE__ */ jsx16("scrollbox", {
27437
27662
  flexGrow: 1,
27438
- children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs11("box", {
27663
+ children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs14("box", {
27439
27664
  flexDirection: "column",
27440
27665
  style: { marginBottom: 1 },
27441
27666
  children: [
27442
- /* @__PURE__ */ jsx13("text", {
27667
+ /* @__PURE__ */ jsx16("text", {
27443
27668
  fg: tokens.warning,
27444
27669
  children: `⚠ ${warning.repoName}`
27445
27670
  }),
27446
- wrapText(warning.message, 56).map((line, j) => /* @__PURE__ */ jsx13("text", {
27671
+ wrapText(warning.message, 72).map((line, j) => /* @__PURE__ */ jsx16("text", {
27447
27672
  fg: tokens.fg,
27448
27673
  children: ` ${line}`
27449
27674
  }, j))
@@ -27451,9 +27676,46 @@ function WarningsOverlay({ warnings }) {
27451
27676
  }, i2))
27452
27677
  })
27453
27678
  }),
27454
- /* @__PURE__ */ jsx13("text", {
27679
+ /* @__PURE__ */ jsxs14("box", {
27680
+ flexDirection: "row",
27681
+ gap: 3,
27682
+ style: { marginTop: 2 },
27683
+ justifyContent: "center",
27684
+ children: [
27685
+ /* @__PURE__ */ jsx16("box", {
27686
+ ...ackTap,
27687
+ children: /* @__PURE__ */ jsxs14("text", {
27688
+ children: [
27689
+ /* @__PURE__ */ jsx16("span", {
27690
+ fg: tokens.dim,
27691
+ children: "[a] "
27692
+ }),
27693
+ /* @__PURE__ */ jsx16("span", {
27694
+ fg: tokens.success,
27695
+ children: "Acknowledge"
27696
+ })
27697
+ ]
27698
+ })
27699
+ }),
27700
+ /* @__PURE__ */ jsx16("box", {
27701
+ ...closeTap,
27702
+ children: /* @__PURE__ */ jsxs14("text", {
27703
+ children: [
27704
+ /* @__PURE__ */ jsx16("span", {
27705
+ fg: tokens.dim,
27706
+ children: "[esc] "
27707
+ }),
27708
+ /* @__PURE__ */ jsx16("span", {
27709
+ children: "Close"
27710
+ })
27711
+ ]
27712
+ })
27713
+ })
27714
+ ]
27715
+ }),
27716
+ /* @__PURE__ */ jsx16("text", {
27455
27717
  style: { marginTop: 1, fg: tokens.dim },
27456
- children: "Press any key to close"
27718
+ children: "Press a to acknowledge and clear errors"
27457
27719
  })
27458
27720
  ]
27459
27721
  });
@@ -27462,13 +27724,14 @@ var init_WarningsOverlay = __esm(() => {
27462
27724
  init_Overlay();
27463
27725
  init_theme();
27464
27726
  init_utils();
27727
+ init_use_tap();
27465
27728
  });
27466
27729
 
27467
27730
  // src/tui/hooks/useSpinnerFrame.ts
27468
- import { useEffect as useEffect5, useState as useState7 } from "react";
27731
+ import { useEffect as useEffect6, useState as useState8 } from "react";
27469
27732
  function useSpinnerFrame(active) {
27470
- const [index, setIndex] = useState7(0);
27471
- useEffect5(() => {
27733
+ const [index, setIndex] = useState8(0);
27734
+ useEffect6(() => {
27472
27735
  if (!active)
27473
27736
  return;
27474
27737
  const id = setInterval(() => setIndex((i2) => (i2 + 1) % FRAMES.length), INTERVAL_MS);
@@ -27481,11 +27744,393 @@ var init_useSpinnerFrame = __esm(() => {
27481
27744
  FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
27482
27745
  });
27483
27746
 
27747
+ // src/tui/hooks/useTerminalSessions.ts
27748
+ import { useState as useState9, useCallback as useCallback5, useRef as useRef6, useEffect as useEffect7 } from "react";
27749
+ function worktreeKeyFor(repoName, branch, path37) {
27750
+ return path37 || `${repoName}:${branch}`;
27751
+ }
27752
+ function nextTerminalSessionLabel(existingCount) {
27753
+ return `Session ${existingCount + 1}`;
27754
+ }
27755
+ function isSameSize(session, cols, rows) {
27756
+ return session.cols === cols && session.rows === rows;
27757
+ }
27758
+ function updateSessionSize(session, cols, rows) {
27759
+ return isSameSize(session, cols, rows) ? session : { ...session, cols, rows };
27760
+ }
27761
+ function relabelTerminalSessions(sessions) {
27762
+ return sessions.map((session, index) => ({ ...session, label: nextTerminalSessionLabel(index) }));
27763
+ }
27764
+ function useTerminalSessions() {
27765
+ const [sessionsByKey, setSessionsByKey] = useState9(() => new Map);
27766
+ const procsRef = useRef6(new Map);
27767
+ const listenersRef = useRef6(new Map);
27768
+ const rawBuffersRef = useRef6(new Map);
27769
+ const getSessions = useCallback5((repoName, branch, path37) => {
27770
+ const key = worktreeKeyFor(repoName, branch, path37);
27771
+ return sessionsByKey.get(key) ?? [];
27772
+ }, [sessionsByKey]);
27773
+ const getKey = useCallback5((repoName, branch, path37) => worktreeKeyFor(repoName, branch, path37), []);
27774
+ const createSession = useCallback5((repoName, branch, path37, opts) => {
27775
+ const key = worktreeKeyFor(repoName, branch, path37);
27776
+ const existing = sessionsByKey.get(key) ?? [];
27777
+ if (existing.length >= MAX_TERMINAL_SESSIONS) {
27778
+ return { session: null, error: `Max ${MAX_TERMINAL_SESSIONS} sessions per worktree` };
27779
+ }
27780
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
27781
+ const label = nextTerminalSessionLabel(existing.length);
27782
+ const cols = opts?.cols ?? 80;
27783
+ const rows = opts?.rows ?? 24;
27784
+ const session = {
27785
+ id,
27786
+ label,
27787
+ worktreeKey: key,
27788
+ worktreePath: path37,
27789
+ repoName,
27790
+ branch,
27791
+ lines: [],
27792
+ inputBuffer: "",
27793
+ exited: null,
27794
+ proc: null,
27795
+ terminal: null,
27796
+ cols,
27797
+ rows,
27798
+ usePty: false
27799
+ };
27800
+ const shell = process.env.SHELL || "/bin/bash";
27801
+ const appendPipe = (data) => {
27802
+ const text = new TextDecoder().decode(data);
27803
+ const parts = text.split(`
27804
+ `);
27805
+ setSessionsByKey((prev) => {
27806
+ const next = new Map(prev);
27807
+ const list = next.get(key);
27808
+ if (!list)
27809
+ return prev;
27810
+ const idx = list.findIndex((s) => s.id === id);
27811
+ if (idx === -1)
27812
+ return prev;
27813
+ const updated = [...list];
27814
+ const sess = updated[idx];
27815
+ const newLines = [...sess.lines];
27816
+ for (let i2 = 0;i2 < parts.length; i2++) {
27817
+ const part = parts[i2];
27818
+ if (i2 === 0 && newLines.length > 0) {
27819
+ if (part)
27820
+ newLines[newLines.length - 1] = (newLines[newLines.length - 1] ?? "") + part;
27821
+ } else {
27822
+ if (part || i2 < parts.length - 1)
27823
+ newLines.push(part);
27824
+ }
27825
+ }
27826
+ if (newLines.length > 1000)
27827
+ newLines.splice(0, newLines.length - 1000);
27828
+ updated[idx] = { ...sess, lines: newLines };
27829
+ next.set(key, updated);
27830
+ return next;
27831
+ });
27832
+ };
27833
+ const onPtyData = (data) => {
27834
+ const buf = rawBuffersRef.current.get(id) ?? [];
27835
+ buf.push(data);
27836
+ if (buf.length > 500)
27837
+ buf.shift();
27838
+ rawBuffersRef.current.set(id, buf);
27839
+ const listener = listenersRef.current.get(id);
27840
+ if (listener) {
27841
+ listener(data);
27842
+ return;
27843
+ }
27844
+ };
27845
+ const onExit2 = (code) => {
27846
+ setSessionsByKey((prev) => {
27847
+ const next = new Map(prev);
27848
+ const list = next.get(key);
27849
+ if (!list)
27850
+ return prev;
27851
+ const idx = list.findIndex((s) => s.id === id);
27852
+ if (idx === -1)
27853
+ return prev;
27854
+ const updated = [...list];
27855
+ updated[idx] = { ...updated[idx], exited: code ?? 0, proc: null, terminal: null };
27856
+ next.set(key, updated);
27857
+ return next;
27858
+ });
27859
+ procsRef.current.delete(id);
27860
+ listenersRef.current.delete(id);
27861
+ };
27862
+ let usePty = false;
27863
+ try {
27864
+ if (typeof Bun !== "undefined" && typeof Bun.spawn === "function") {
27865
+ try {
27866
+ const proc = Bun.spawn([shell], {
27867
+ cwd: path37 || process.cwd(),
27868
+ env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor", WTX_SESSION: label },
27869
+ terminal: {
27870
+ cols,
27871
+ rows,
27872
+ name: "xterm-256color",
27873
+ data(_terminal, data) {
27874
+ onPtyData(data);
27875
+ },
27876
+ exit(_terminal, exitCode) {
27877
+ onExit2(exitCode);
27878
+ }
27879
+ }
27880
+ });
27881
+ if (proc && proc.terminal) {
27882
+ session.proc = proc;
27883
+ session.terminal = proc.terminal;
27884
+ session.usePty = true;
27885
+ usePty = true;
27886
+ procsRef.current.set(id, proc);
27887
+ (async () => {
27888
+ try {
27889
+ const code = await proc.exited;
27890
+ onExit2(code);
27891
+ } catch {}
27892
+ })();
27893
+ }
27894
+ } catch {}
27895
+ }
27896
+ if (!usePty)
27897
+ throw new Error("pty not available");
27898
+ } catch {
27899
+ try {
27900
+ const proc = Bun.spawn([shell], {
27901
+ cwd: path37 || process.cwd(),
27902
+ stdin: "pipe",
27903
+ stdout: "pipe",
27904
+ stderr: "pipe",
27905
+ env: { ...process.env, TERM: "xterm-256color", WTX_SESSION: label }
27906
+ });
27907
+ session.proc = proc;
27908
+ session.usePty = false;
27909
+ procsRef.current.set(id, proc);
27910
+ (async () => {
27911
+ const stdout = proc.stdout;
27912
+ if (stdout) {
27913
+ const reader = stdout.getReader();
27914
+ try {
27915
+ while (true) {
27916
+ const { value, done } = await reader.read();
27917
+ if (done)
27918
+ break;
27919
+ if (value)
27920
+ appendPipe(value);
27921
+ }
27922
+ } catch {}
27923
+ }
27924
+ })();
27925
+ (async () => {
27926
+ const stderr = proc.stderr;
27927
+ if (stderr) {
27928
+ const reader = stderr.getReader();
27929
+ try {
27930
+ while (true) {
27931
+ const { value, done } = await reader.read();
27932
+ if (done)
27933
+ break;
27934
+ if (value)
27935
+ appendPipe(value);
27936
+ }
27937
+ } catch {}
27938
+ }
27939
+ })();
27940
+ (async () => {
27941
+ try {
27942
+ const code = await proc.exited;
27943
+ onExit2(code);
27944
+ } catch {}
27945
+ })();
27946
+ } catch (e) {
27947
+ session.lines = [`Failed to spawn shell: ${e.message}`];
27948
+ }
27949
+ }
27950
+ if (session.usePty) {
27951
+ session.lines = [`${label} — ${path37} (${shell}) [pty]`, ""];
27952
+ } else if (session.lines.length === 0) {
27953
+ session.lines = [`${label} — ${path37} (${shell})`, ""];
27954
+ }
27955
+ setSessionsByKey((prev) => {
27956
+ const next = new Map(prev);
27957
+ const list = next.get(key) ?? [];
27958
+ next.set(key, [...list, session]);
27959
+ return next;
27960
+ });
27961
+ return { session };
27962
+ }, [sessionsByKey]);
27963
+ const removeSession = useCallback5((repoName, branch, path37, id) => {
27964
+ const key = worktreeKeyFor(repoName, branch, path37);
27965
+ setSessionsByKey((prev) => {
27966
+ const next = new Map(prev);
27967
+ const list = next.get(key);
27968
+ if (!list)
27969
+ return prev;
27970
+ const sess = list.find((s) => s.id === id);
27971
+ if (sess?.proc) {
27972
+ try {
27973
+ if (sess.terminal) {
27974
+ try {
27975
+ sess.terminal.close();
27976
+ } catch {}
27977
+ }
27978
+ sess.proc.kill();
27979
+ } catch {}
27980
+ procsRef.current.delete(id);
27981
+ listenersRef.current.delete(id);
27982
+ rawBuffersRef.current.delete(id);
27983
+ }
27984
+ const filtered = list.filter((s) => s.id !== id);
27985
+ const relabeled = relabelTerminalSessions(filtered);
27986
+ if (relabeled.length === 0) {
27987
+ next.delete(key);
27988
+ } else {
27989
+ next.set(key, relabeled);
27990
+ }
27991
+ return next;
27992
+ });
27993
+ }, []);
27994
+ const sendInput = useCallback5((repoName, branch, path37, id, data) => {
27995
+ const key = worktreeKeyFor(repoName, branch, path37);
27996
+ const list = sessionsByKey.get(key) ?? [];
27997
+ const sess = list.find((s) => s.id === id);
27998
+ if (!sess?.proc)
27999
+ return;
28000
+ try {
28001
+ if (sess.usePty && sess.terminal) {
28002
+ if (typeof data === "string") {
28003
+ sess.terminal.write(new TextEncoder().encode(data));
28004
+ } else {
28005
+ sess.terminal.write(data);
28006
+ }
28007
+ return;
28008
+ }
28009
+ const proc = sess.proc;
28010
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28011
+ if (proc.stdin && typeof proc.stdin.write === "function") {
28012
+ proc.stdin.write(str);
28013
+ } else if (proc.stdin instanceof WritableStream) {
28014
+ const writer = proc.stdin.getWriter();
28015
+ writer.write(new TextEncoder().encode(str));
28016
+ writer.releaseLock();
28017
+ } else {
28018
+ proc.stdin?.write?.(str);
28019
+ }
28020
+ } catch {}
28021
+ if (!sess.usePty) {
28022
+ const str = typeof data === "string" ? data : new TextDecoder().decode(data);
28023
+ if (str.endsWith(`
28024
+ `)) {
28025
+ const cmd = str.slice(0, -1);
28026
+ if (cmd) {
28027
+ setSessionsByKey((prev) => {
28028
+ const next = new Map(prev);
28029
+ const l = next.get(key);
28030
+ if (!l)
28031
+ return prev;
28032
+ const idx = l.findIndex((s2) => s2.id === id);
28033
+ if (idx === -1)
28034
+ return prev;
28035
+ const updated = [...l];
28036
+ const s = updated[idx];
28037
+ updated[idx] = { ...s, lines: [...s.lines, `$ ${cmd}`] };
28038
+ next.set(key, updated);
28039
+ return next;
28040
+ });
28041
+ }
28042
+ }
28043
+ }
28044
+ }, [sessionsByKey]);
28045
+ const resizeSession = useCallback5((repoName, branch, path37, id, cols, rows) => {
28046
+ const key = worktreeKeyFor(repoName, branch, path37);
28047
+ setSessionsByKey((prev) => {
28048
+ const list = prev.get(key);
28049
+ if (!list)
28050
+ return prev;
28051
+ const idx = list.findIndex((s) => s.id === id);
28052
+ if (idx === -1)
28053
+ return prev;
28054
+ const sess = list[idx];
28055
+ if (isSameSize(sess, cols, rows))
28056
+ return prev;
28057
+ try {
28058
+ sess.terminal?.resize?.(cols, rows);
28059
+ } catch {}
28060
+ const next = new Map(prev);
28061
+ const updated = [...list];
28062
+ updated[idx] = updateSessionSize(sess, cols, rows);
28063
+ next.set(key, updated);
28064
+ return next;
28065
+ });
28066
+ }, []);
28067
+ const registerListener = useCallback5((id, fn) => {
28068
+ listenersRef.current.set(id, fn);
28069
+ const buf = rawBuffersRef.current.get(id);
28070
+ if (buf) {
28071
+ for (const chunk of buf) {
28072
+ try {
28073
+ fn(chunk);
28074
+ } catch {}
28075
+ }
28076
+ }
28077
+ }, []);
28078
+ const unregisterListener = useCallback5((id) => {
28079
+ listenersRef.current.delete(id);
28080
+ }, []);
28081
+ const cleanupAll = useCallback5(() => {
28082
+ for (const proc of procsRef.current.values()) {
28083
+ try {
28084
+ if (proc.terminal) {
28085
+ try {
28086
+ proc.terminal.close();
28087
+ } catch {}
28088
+ }
28089
+ proc.kill();
28090
+ } catch {}
28091
+ }
28092
+ procsRef.current.clear();
28093
+ listenersRef.current.clear();
28094
+ rawBuffersRef.current.clear();
28095
+ }, []);
28096
+ useEffect7(() => {
28097
+ return () => cleanupAll();
28098
+ }, [cleanupAll]);
28099
+ const pruneKey = useCallback5((key) => {
28100
+ setSessionsByKey((prev) => {
28101
+ const next = new Map(prev);
28102
+ const list = next.get(key);
28103
+ if (!list)
28104
+ return prev;
28105
+ for (const s of list) {
28106
+ if (s.proc) {
28107
+ try {
28108
+ if (s.terminal) {
28109
+ try {
28110
+ s.terminal.close();
28111
+ } catch {}
28112
+ }
28113
+ s.proc.kill();
28114
+ } catch {}
28115
+ procsRef.current.delete(s.id);
28116
+ listenersRef.current.delete(s.id);
28117
+ rawBuffersRef.current.delete(s.id);
28118
+ }
28119
+ }
28120
+ next.delete(key);
28121
+ return next;
28122
+ });
28123
+ }, []);
28124
+ return { sessionsByKey, getSessions, getKey, createSession, removeSession, sendInput, resizeSession, registerListener, unregisterListener, pruneKey, cleanupAll };
28125
+ }
28126
+ var MAX_TERMINAL_SESSIONS = 5;
28127
+ var init_useTerminalSessions = () => {};
28128
+
27484
28129
  // src/tui/components/App.tsx
27485
- import { useState as useState8, useEffect as useEffect6, useMemo, useRef as useRef5, useCallback as useCallback4 } from "react";
28130
+ import { useState as useState10, useEffect as useEffect8, useMemo, useRef as useRef7, useCallback as useCallback6 } from "react";
27486
28131
  import { existsSync as existsSync4 } from "node:fs";
27487
28132
  import { useKeyboard as useKeyboard3, useRenderer as useRenderer2, useSelectionHandler, useTerminalDimensions } from "@opentui/react";
27488
- import { jsx as jsx14, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
28133
+ import { jsx as jsx17, jsxs as jsxs15 } from "@opentui/react/jsx-runtime";
27489
28134
  function getWorktreePathFor(repoName, branch) {
27490
28135
  try {
27491
28136
  const config2 = loadConfig();
@@ -27497,38 +28142,52 @@ function getWorktreePathFor(repoName, branch) {
27497
28142
  }
27498
28143
  function App({ opts }) {
27499
28144
  const renderer = useRenderer2();
27500
- const { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, refresh } = useWorktrees(opts);
27501
- const [selectedIndex, setSelectedIndex] = useState8(0);
27502
- const [modal, setModal] = useState8({ type: "none" });
27503
- const [actionMessage, setActionMessage] = useState8();
27504
- const messageTimer = useRef5(null);
27505
- const [ops, setOps] = useState8([]);
27506
- const [failedLogs, setFailedLogs] = useState8([]);
27507
- const nextOpId = useRef5(1);
27508
- const [createModal, setCreateModal] = useState8(false);
27509
- const [createError, setCreateError] = useState8();
27510
- const [createBaseModal, setCreateBaseModal] = useState8(null);
27511
- const [createBaseError, setCreateBaseError] = useState8();
27512
- const [createDepsChoice, setCreateDepsChoice] = useState8(null);
27513
- const [pullPrModal, setPullPrModal] = useState8(false);
27514
- const [pullPrError, setPullPrError] = useState8();
27515
- const [pullForceChoice, setPullForceChoice] = useState8(null);
27516
- const [renameModal, setRenameModal] = useState8(false);
27517
- const [renameError, setRenameError] = useState8();
27518
- const [configOpen, setConfigOpen] = useState8(false);
27519
- const [refreshScopes, setRefreshScopes] = useState8([]);
27520
- const [filterText, setFilterText] = useState8("");
27521
- const [isFiltering, setIsFiltering] = useState8(false);
27522
- const [selection, setSelection] = useState8(new Set);
27523
- const [splitRatio, setSplitRatio] = useState8(0.6);
27524
- const [isResizing, setIsResizing] = useState8(false);
27525
- const { width: termWidth } = useTerminalDimensions();
27526
- const totalWidth = termWidth || renderer.width || 80;
27527
- useEffect6(() => {
27528
- if (termWidth)
27529
- setSplitRatio((prev) => clampSplitRatio(termWidth, prev));
27530
- }, [termWidth]);
27531
- const doRefresh = useCallback4(async (scope) => {
28145
+ const { blocks, loading, refreshing, error: error52, warnings, lastRefreshed, pendingRepos, refresh, clearWarnings } = useWorktrees(opts);
28146
+ const [selectedIndex, setSelectedIndex] = useState10(0);
28147
+ const [modal, setModal] = useState10({ type: "none" });
28148
+ const [actionMessage, setActionMessage] = useState10();
28149
+ const messageTimer = useRef7(null);
28150
+ const [ops, setOps] = useState10([]);
28151
+ const [failedLogs, setFailedLogs] = useState10([]);
28152
+ const nextOpId = useRef7(1);
28153
+ const [createModal, setCreateModal] = useState10(false);
28154
+ const [createError, setCreateError] = useState10();
28155
+ const [createBaseModal, setCreateBaseModal] = useState10(null);
28156
+ const [createBaseError, setCreateBaseError] = useState10();
28157
+ const [createDepsChoice, setCreateDepsChoice] = useState10(null);
28158
+ const [pullPrModal, setPullPrModal] = useState10(false);
28159
+ const [pullPrError, setPullPrError] = useState10();
28160
+ const [pullForceChoice, setPullForceChoice] = useState10(null);
28161
+ const [renameModal, setRenameModal] = useState10(false);
28162
+ const [renameError, setRenameError] = useState10();
28163
+ const [configOpen, setConfigOpen] = useState10(false);
28164
+ const [refreshScopes, setRefreshScopes] = useState10([]);
28165
+ const [filterText, setFilterText] = useState10("");
28166
+ const [isFiltering, setIsFiltering] = useState10(false);
28167
+ const [selection, setSelection] = useState10(new Set);
28168
+ const [splitRatio, setSplitRatio] = useState10(() => {
28169
+ try {
28170
+ const cfg = loadConfig();
28171
+ const left = cfg.tui?.leftPaneWidthWeight ?? 3;
28172
+ const right = cfg.tui?.rightPaneWidthWeight ?? 7;
28173
+ const total = left + right;
28174
+ if (total > 0)
28175
+ return left / total;
28176
+ } catch {}
28177
+ return 3 / 10;
28178
+ });
28179
+ const [isResizing, setIsResizing] = useState10(false);
28180
+ const terminalSessions = useTerminalSessions();
28181
+ const [activeTabByWorktree, setActiveTabByWorktree] = useState10(() => new Map);
28182
+ const [terminalFocused, setTerminalFocused] = useState10(false);
28183
+ const { width: termW, height: termH } = useTerminalDimensions();
28184
+ const totalWidth = termW || renderer.width || 80;
28185
+ const totalHeight = termH || renderer.height || 24;
28186
+ useEffect8(() => {
28187
+ if (termW)
28188
+ setSplitRatio((prev) => clampSplitRatio(termW, prev));
28189
+ }, [termW]);
28190
+ const doRefresh = useCallback6(async (scope) => {
27532
28191
  const targets = scope ?? [
27533
28192
  ...new Set([...blocks.map((b) => b.repoName), ...pendingRepos])
27534
28193
  ];
@@ -27539,18 +28198,18 @@ function App({ opts }) {
27539
28198
  setRefreshScopes([]);
27540
28199
  }
27541
28200
  }, [blocks, pendingRepos, refresh]);
27542
- useEffect6(() => {
28201
+ useEffect8(() => {
27543
28202
  if (error52) {
27544
28203
  setModal({ type: "error", message: error52 });
27545
28204
  }
27546
28205
  }, [error52]);
27547
- const flash = useCallback4((message, ms = 3000) => {
28206
+ const flash = useCallback6((message, ms = 3000) => {
27548
28207
  if (messageTimer.current)
27549
28208
  clearTimeout(messageTimer.current);
27550
28209
  setActionMessage(message);
27551
28210
  messageTimer.current = setTimeout(() => setActionMessage(undefined), ms);
27552
28211
  }, []);
27553
- const copySelectedText = useCallback4((warnOnEmpty) => {
28212
+ const copySelectedText = useCallback6((warnOnEmpty) => {
27554
28213
  if (isResizing) {
27555
28214
  renderer.clearSelection();
27556
28215
  return;
@@ -27580,12 +28239,130 @@ function App({ opts }) {
27580
28239
  const flatRows = useMemo(() => displayBlocks.flatMap((b) => b.rows.filter((r) => !r.isPendingCreate)), [displayBlocks]);
27581
28240
  const totalRows = blocks.flatMap((b) => b.rows).length;
27582
28241
  const maxIndex = Math.max(0, flatRows.length - 1);
27583
- useEffect6(() => {
28242
+ useEffect8(() => {
27584
28243
  if (selectedIndex > maxIndex && maxIndex >= 0) {
27585
28244
  setSelectedIndex(maxIndex);
27586
28245
  }
27587
28246
  }, [maxIndex, selectedIndex]);
27588
28247
  const selectedRow = flatRows[selectedIndex] ?? null;
28248
+ const worktreeKey = selectedRow ? terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path) : "";
28249
+ const sessionsForSelected = selectedRow ? terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path) : [];
28250
+ const activeTabId = worktreeKey ? activeTabByWorktree.get(worktreeKey) ?? "details" : "details";
28251
+ useEffect8(() => {
28252
+ if (worktreeKey && !activeTabByWorktree.has(worktreeKey)) {
28253
+ setActiveTabByWorktree((prev) => {
28254
+ if (prev.has(worktreeKey))
28255
+ return prev;
28256
+ const next = new Map(prev);
28257
+ next.set(worktreeKey, "details");
28258
+ return next;
28259
+ });
28260
+ }
28261
+ }, [worktreeKey, activeTabByWorktree]);
28262
+ useEffect8(() => {
28263
+ if (sessionsForSelected.length === 0 && activeTabId !== "details") {
28264
+ setActiveTabByWorktree((prev) => {
28265
+ const next = new Map(prev);
28266
+ next.set(worktreeKey, "details");
28267
+ return next;
28268
+ });
28269
+ setTerminalFocused(false);
28270
+ } else if (sessionsForSelected.length > 0) {
28271
+ const exists = sessionsForSelected.some((s) => s.id === activeTabId) || activeTabId === "details";
28272
+ if (!exists) {
28273
+ setActiveTabByWorktree((prev) => {
28274
+ const next = new Map(prev);
28275
+ next.set(worktreeKey, sessionsForSelected[0].id);
28276
+ return next;
28277
+ });
28278
+ }
28279
+ }
28280
+ }, [sessionsForSelected, activeTabId, worktreeKey]);
28281
+ const handleSelectTab = useCallback6((id) => {
28282
+ if (!worktreeKey)
28283
+ return;
28284
+ setActiveTabByWorktree((prev) => {
28285
+ const next = new Map(prev);
28286
+ next.set(worktreeKey, id);
28287
+ return next;
28288
+ });
28289
+ const isSession = sessionsForSelected.some((s) => s.id === id);
28290
+ setTerminalFocused(isSession);
28291
+ }, [worktreeKey, sessionsForSelected]);
28292
+ const handleAddSession = useCallback6(() => {
28293
+ if (!selectedRow) {
28294
+ flash("No worktree selected");
28295
+ return;
28296
+ }
28297
+ const rawLeftTmp = Math.floor(totalWidth * splitRatio);
28298
+ const leftColsTmp = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeftTmp));
28299
+ const rightColsTmp = Math.max(20, totalWidth - leftColsTmp - DIVIDER_WIDTH);
28300
+ const cols = Math.max(20, rightColsTmp - 4);
28301
+ const rows = Math.max(10, totalHeight - 10);
28302
+ const { session, error: error53 } = terminalSessions.createSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, { cols, rows });
28303
+ if (error53 || !session) {
28304
+ flash(error53 ?? "Failed to create session");
28305
+ return;
28306
+ }
28307
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28308
+ setActiveTabByWorktree((prev) => {
28309
+ const next = new Map(prev);
28310
+ next.set(key, session.id);
28311
+ return next;
28312
+ });
28313
+ setTerminalFocused(true);
28314
+ flash(`Session ${session.label} created`, 2000);
28315
+ }, [selectedRow, terminalSessions, flash, totalWidth, splitRatio, totalHeight]);
28316
+ const handleCloseSession = useCallback6((id) => {
28317
+ if (!selectedRow)
28318
+ return;
28319
+ terminalSessions.removeSession(selectedRow.repoName, selectedRow.branch, selectedRow.path, id);
28320
+ const key = terminalSessions.getKey(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28321
+ setActiveTabByWorktree((prev) => {
28322
+ const next = new Map(prev);
28323
+ const current = next.get(key);
28324
+ if (current === id)
28325
+ next.set(key, "details");
28326
+ return next;
28327
+ });
28328
+ setTerminalFocused(false);
28329
+ flash("Session closed", 2000);
28330
+ }, [selectedRow, terminalSessions, flash]);
28331
+ useEffect8(() => {
28332
+ const existingKeys = new Set(blocks.flatMap((b) => b.rows.map((r) => terminalSessions.getKey(r.repoName, r.branch, r.path))));
28333
+ for (const key of terminalSessions.sessionsByKey.keys()) {
28334
+ if (!existingKeys.has(key))
28335
+ terminalSessions.pruneKey(key);
28336
+ }
28337
+ }, [blocks, terminalSessions]);
28338
+ const tabsForSelected = useMemo(() => {
28339
+ const base2 = [
28340
+ {
28341
+ id: "details",
28342
+ label: "Details",
28343
+ render: ({ worktree }) => /* @__PURE__ */ jsx17(DetailsContent, {
28344
+ selectedRow: worktree
28345
+ })
28346
+ }
28347
+ ];
28348
+ for (const s of sessionsForSelected) {
28349
+ base2.push({
28350
+ id: s.id,
28351
+ label: s.label,
28352
+ closable: true,
28353
+ render: () => /* @__PURE__ */ jsx17(TerminalView, {
28354
+ session: s,
28355
+ focused: terminalFocused && activeTabId === s.id,
28356
+ onFocus: () => setTerminalFocused(true),
28357
+ onSend: (data) => terminalSessions.sendInput(s.repoName, s.branch, s.worktreePath, s.id, data),
28358
+ onResize: (cols, rows) => terminalSessions.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, cols, rows),
28359
+ registerListener: terminalSessions.registerListener,
28360
+ unregisterListener: terminalSessions.unregisterListener
28361
+ })
28362
+ });
28363
+ }
28364
+ return base2;
28365
+ }, [sessionsForSelected, terminalFocused, activeTabId, terminalSessions]);
27589
28366
  const { repoVerbs, rowVerbs } = useMemo(() => {
27590
28367
  const rv = new Map;
27591
28368
  const nv = new Map;
@@ -27724,7 +28501,7 @@ function App({ opts }) {
27724
28501
  args.push("--force");
27725
28502
  executeOp(op, args, [repoName]);
27726
28503
  };
27727
- const handleHintClick = useCallback4((key) => {
28504
+ const handleHintClick = useCallback6((key) => {
27728
28505
  if (key === "c") {
27729
28506
  setConfigOpen(true);
27730
28507
  return;
@@ -27876,8 +28653,72 @@ function App({ opts }) {
27876
28653
  setModal({ type: "confirm_sync", rows: targets });
27877
28654
  return;
27878
28655
  }
27879
- }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks]);
28656
+ if (key === "t") {
28657
+ if (!selectedRow)
28658
+ return;
28659
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
28660
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
28661
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28662
+ return;
28663
+ }
28664
+ handleAddSession();
28665
+ return;
28666
+ }
28667
+ }, [selectedRow, selection, warnings, busyRepos, busyRowPaths, doRefresh, flash, blocks, terminalSessions]);
27880
28668
  useKeyboard3((key) => {
28669
+ if (terminalFocused) {
28670
+ if (key.ctrl && key.name === "g") {
28671
+ setTerminalFocused(false);
28672
+ return;
28673
+ }
28674
+ const activeSession = sessionsForSelected.find((s) => s.id === activeTabId);
28675
+ if (activeSession) {
28676
+ let data = null;
28677
+ const seq = key.sequence;
28678
+ const raw = key.raw;
28679
+ if (key.ctrl && key.name && key.name.length === 1) {
28680
+ const code = key.name.charCodeAt(0) - 96;
28681
+ if (code >= 1 && code <= 26)
28682
+ data = String.fromCharCode(code);
28683
+ else if (seq)
28684
+ data = seq;
28685
+ else if (raw)
28686
+ data = raw;
28687
+ } else if (seq) {
28688
+ data = seq;
28689
+ } else if (raw) {
28690
+ data = raw;
28691
+ } else if (key.name === "return" || key.name === "enter") {
28692
+ data = "\r";
28693
+ } else if (key.name === "backspace") {
28694
+ data = "";
28695
+ } else if (key.name === "tab") {
28696
+ data = "\t";
28697
+ } else if (key.name === "escape") {
28698
+ data = "\x1B";
28699
+ } else if (key.name && key.name.length === 1) {
28700
+ data = key.name;
28701
+ }
28702
+ if (data !== null) {
28703
+ terminalSessions.sendInput(activeSession.repoName, activeSession.branch, activeSession.worktreePath, activeSession.id, data);
28704
+ }
28705
+ }
28706
+ return;
28707
+ }
28708
+ if (key.ctrl && key.name === "g") {
28709
+ if (sessionsForSelected.length > 0) {
28710
+ const targetId = activeTabId !== "details" ? activeTabId : sessionsForSelected[0].id;
28711
+ if (worktreeKey) {
28712
+ setActiveTabByWorktree((prev) => {
28713
+ const next = new Map(prev);
28714
+ next.set(worktreeKey, targetId);
28715
+ return next;
28716
+ });
28717
+ }
28718
+ setTerminalFocused(true);
28719
+ return;
28720
+ }
28721
+ }
27881
28722
  if (isFiltering) {
27882
28723
  if (key.name === "escape") {
27883
28724
  setIsFiltering(false);
@@ -27927,7 +28768,20 @@ function App({ opts }) {
27927
28768
  return;
27928
28769
  }
27929
28770
  if (modal.type !== "none") {
27930
- if (modal.type === "error" || modal.type === "help" || modal.type === "history" || modal.type === "warnings") {
28771
+ if (modal.type === "warnings") {
28772
+ if (key.name === "a") {
28773
+ clearWarnings();
28774
+ setModal({ type: "none" });
28775
+ return;
28776
+ }
28777
+ if (key.name === "escape" || key.name === "q" || key.name === "enter" || key.name === "return") {
28778
+ setModal({ type: "none" });
28779
+ return;
28780
+ }
28781
+ setModal({ type: "none" });
28782
+ return;
28783
+ }
28784
+ if (modal.type === "error" || modal.type === "help" || modal.type === "history") {
27931
28785
  setModal({ type: "none" });
27932
28786
  if (modal.type === "error" && error52) {
27933
28787
  renderer.destroy();
@@ -28176,108 +29030,106 @@ function App({ opts }) {
28176
29030
  setModal({ type: "confirm_sync", rows: targets });
28177
29031
  return;
28178
29032
  }
28179
- if (key.name === "a") {
28180
- const targets = getSelectedRows();
28181
- if (targets.length === 0)
28182
- return;
28183
- const target = targets[0];
28184
- if (!target)
29033
+ if (key.name === "t") {
29034
+ if (!selectedRow) {
29035
+ flash("No worktree selected");
28185
29036
  return;
28186
- const conflict = findConflict([target]);
28187
- if (conflict) {
28188
- flash(conflict);
29037
+ }
29038
+ const sessions = terminalSessions.getSessions(selectedRow.repoName, selectedRow.branch, selectedRow.path);
29039
+ if (sessions.length >= MAX_TERMINAL_SESSIONS) {
29040
+ flash(`Max ${MAX_TERMINAL_SESSIONS} sessions per worktree`);
28189
29041
  return;
28190
29042
  }
28191
- (async () => {
28192
- const startedAt = Date.now();
28193
- try {
28194
- const config2 = await loadConfig();
28195
- const cmdTemplate = resolveAgentCommand("claude", config2.agents) ?? "claude";
28196
- const result = await spawnAgentInWorktree(cmdTemplate, target.path, { repoName: target.repoName, branch: target.branch });
28197
- appendHistory({
28198
- ts: new Date().toISOString(),
28199
- source: "terminal",
28200
- command: "agent",
28201
- args: ["agent", target.branch, "--repo", target.repoName],
28202
- durationMs: Date.now() - startedAt,
28203
- exit: 0
28204
- });
28205
- flash(`Agent spawned (${result.mode}${result.session ? ` session ${result.session}` : ""})`, 5000);
28206
- } catch (e) {
28207
- appendHistory({
28208
- ts: new Date().toISOString(),
28209
- source: "terminal",
28210
- command: "agent",
28211
- args: ["agent", target.branch, "--repo", target.repoName],
28212
- durationMs: Date.now() - startedAt,
28213
- exit: 1
28214
- });
28215
- flash(`Agent failed: ${e.message}`, 5000);
28216
- }
28217
- })();
29043
+ handleAddSession();
29044
+ return;
28218
29045
  }
28219
29046
  });
28220
29047
  const rawLeft = Math.floor(totalWidth * splitRatio);
28221
29048
  const leftCols = Math.max(20, Math.min(totalWidth - 20 - DIVIDER_WIDTH, rawLeft));
28222
29049
  const rightCols = Math.max(20, totalWidth - leftCols - DIVIDER_WIDTH);
28223
- return /* @__PURE__ */ jsxs12("box", {
29050
+ const paneCols = Math.max(20, rightCols - 4);
29051
+ const paneRows = Math.max(10, totalHeight - 10);
29052
+ useEffect8(() => {
29053
+ for (const sessions of terminalSessions.sessionsByKey.values()) {
29054
+ for (const s of sessions) {
29055
+ if (s.usePty && s.terminal && (s.cols !== paneCols || s.rows !== paneRows)) {
29056
+ terminalSessions.resizeSession(s.repoName, s.branch, s.worktreePath, s.id, paneCols, paneRows);
29057
+ }
29058
+ }
29059
+ }
29060
+ }, [paneCols, paneRows, terminalSessions]);
29061
+ useEffect8(() => {
29062
+ setTerminalFocused(false);
29063
+ }, [selectedIndex]);
29064
+ return /* @__PURE__ */ jsxs15("box", {
28224
29065
  flexDirection: "column",
28225
29066
  width: "100%",
28226
29067
  height: "100%",
28227
29068
  children: [
28228
- /* @__PURE__ */ jsxs12("box", {
29069
+ /* @__PURE__ */ jsxs15("box", {
28229
29070
  flexDirection: "row",
28230
29071
  width: "100%",
28231
29072
  flexGrow: 1,
28232
29073
  children: [
28233
- /* @__PURE__ */ jsx14("box", {
29074
+ /* @__PURE__ */ jsx17("box", {
28234
29075
  width: leftCols,
28235
29076
  height: "100%",
28236
29077
  flexDirection: "column",
28237
- children: /* @__PURE__ */ jsx14(WorktreeTable, {
29078
+ onMouseDown: () => setTerminalFocused(false),
29079
+ children: /* @__PURE__ */ jsx17(WorktreeTable, {
28238
29080
  blocks: displayBlocks,
28239
29081
  selectedIndex,
28240
29082
  selection,
28241
29083
  frame: spinnerFrame,
28242
29084
  repoVerbs,
28243
29085
  rowVerbs,
28244
- onRowClick: (idx) => setSelectedIndex(idx),
29086
+ onRowClick: (idx) => {
29087
+ setTerminalFocused(false);
29088
+ setSelectedIndex(idx);
29089
+ },
28245
29090
  onToggleSelect: (path37) => setSelection((prev) => toggleSelection(prev, path37))
28246
29091
  })
28247
29092
  }),
28248
- /* @__PURE__ */ jsx14(Divider, {
29093
+ /* @__PURE__ */ jsx17(Divider, {
28249
29094
  splitRatio,
28250
29095
  totalWidth,
28251
29096
  onChange: setSplitRatio,
28252
29097
  onDraggingChange: setIsResizing
28253
29098
  }),
28254
- /* @__PURE__ */ jsx14("box", {
29099
+ /* @__PURE__ */ jsx17("box", {
28255
29100
  width: rightCols,
28256
29101
  height: "100%",
28257
29102
  flexDirection: "column",
28258
- children: /* @__PURE__ */ jsx14(DetailPane, {
28259
- selectedRow
29103
+ children: /* @__PURE__ */ jsx17(TabPane, {
29104
+ selectedRow,
29105
+ tabs: tabsForSelected,
29106
+ activeId: activeTabId,
29107
+ focused: terminalFocused && tabsForSelected.some((t) => t.id === activeTabId && t.id !== "details"),
29108
+ canAdd: sessionsForSelected.length < MAX_TERMINAL_SESSIONS,
29109
+ onSelect: handleSelectTab,
29110
+ onAdd: handleAddSession,
29111
+ onClose: handleCloseSession
28260
29112
  })
28261
29113
  })
28262
29114
  ]
28263
29115
  }),
28264
- isFiltering && /* @__PURE__ */ jsxs12("box", {
29116
+ isFiltering && /* @__PURE__ */ jsxs15("box", {
28265
29117
  flexDirection: "row",
28266
29118
  paddingX: 1,
28267
29119
  border: true,
28268
29120
  borderColor: "magenta",
28269
29121
  children: [
28270
- /* @__PURE__ */ jsx14("text", {
29122
+ /* @__PURE__ */ jsx17("text", {
28271
29123
  children: "filter: "
28272
29124
  }),
28273
- /* @__PURE__ */ jsx14("input", {
29125
+ /* @__PURE__ */ jsx17("input", {
28274
29126
  focused: true,
28275
29127
  placeholder: "Type to filter...",
28276
29128
  onInput: (v) => setFilterText(v)
28277
29129
  })
28278
29130
  ]
28279
29131
  }),
28280
- /* @__PURE__ */ jsx14(Footer, {
29132
+ /* @__PURE__ */ jsx17(Footer, {
28281
29133
  loading: loading || refreshing,
28282
29134
  lastRefreshed,
28283
29135
  errorCount: warnings.length,
@@ -28288,12 +29140,17 @@ function App({ opts }) {
28288
29140
  onHintClick: handleHintClick,
28289
29141
  onErrorClick: () => setModal({ type: "warnings" })
28290
29142
  }),
28291
- modal.type === "help" && /* @__PURE__ */ jsx14(HelpOverlay, {}),
28292
- modal.type === "history" && /* @__PURE__ */ jsx14(HistoryOverlay, {}),
28293
- modal.type === "warnings" && /* @__PURE__ */ jsx14(WarningsOverlay, {
28294
- warnings
29143
+ modal.type === "help" && /* @__PURE__ */ jsx17(HelpOverlay, {}),
29144
+ modal.type === "history" && /* @__PURE__ */ jsx17(HistoryOverlay, {}),
29145
+ modal.type === "warnings" && /* @__PURE__ */ jsx17(WarningsOverlay, {
29146
+ warnings,
29147
+ onAcknowledge: () => {
29148
+ clearWarnings();
29149
+ setModal({ type: "none" });
29150
+ },
29151
+ onClose: () => setModal({ type: "none" })
28295
29152
  }),
28296
- modal.type === "error" && /* @__PURE__ */ jsx14(ConfirmModal, {
29153
+ modal.type === "error" && /* @__PURE__ */ jsx17(ConfirmModal, {
28297
29154
  title: "Error",
28298
29155
  message: modal.message,
28299
29156
  onConfirm: () => {
@@ -28311,7 +29168,7 @@ function App({ opts }) {
28311
29168
  }
28312
29169
  }
28313
29170
  }),
28314
- modal.type === "confirm_remove" && /* @__PURE__ */ jsx14(ConfirmModal, {
29171
+ modal.type === "confirm_remove" && /* @__PURE__ */ jsx17(ConfirmModal, {
28315
29172
  title: `Remove ${modal.rows.length} Worktree(s)`,
28316
29173
  message: (() => {
28317
29174
  const count2 = modal.rows.length;
@@ -28340,7 +29197,7 @@ function App({ opts }) {
28340
29197
  },
28341
29198
  onCancel: () => setModal({ type: "none" })
28342
29199
  }),
28343
- modal.type === "confirm_rebase" && /* @__PURE__ */ jsx14(ConfirmModal, {
29200
+ modal.type === "confirm_rebase" && /* @__PURE__ */ jsx17(ConfirmModal, {
28344
29201
  title: `Rebase ${modal.rows.length} Worktree(s)`,
28345
29202
  message: `Are you sure you want to fetch and rebase ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28346
29203
  onConfirm: () => {
@@ -28350,7 +29207,7 @@ function App({ opts }) {
28350
29207
  },
28351
29208
  onCancel: () => setModal({ type: "none" })
28352
29209
  }),
28353
- modal.type === "confirm_sync" && /* @__PURE__ */ jsx14(ConfirmModal, {
29210
+ modal.type === "confirm_sync" && /* @__PURE__ */ jsx17(ConfirmModal, {
28354
29211
  title: `Sync ${modal.rows.length} Worktree(s)`,
28355
29212
  message: `Are you sure you want to sync ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`,
28356
29213
  onConfirm: () => {
@@ -28360,7 +29217,7 @@ function App({ opts }) {
28360
29217
  },
28361
29218
  onCancel: () => setModal({ type: "none" })
28362
29219
  }),
28363
- createModal && /* @__PURE__ */ jsx14(InputModal, {
29220
+ createModal && /* @__PURE__ */ jsx17(InputModal, {
28364
29221
  title: `New worktree branch in ${selectedRow?.repoName ?? ""}`,
28365
29222
  placeholder: "Branch name (empty to cancel)",
28366
29223
  errorMessage: createError,
@@ -28387,7 +29244,7 @@ function App({ opts }) {
28387
29244
  setCreateBaseModal({ branch, repoName });
28388
29245
  }
28389
29246
  }),
28390
- createBaseModal && /* @__PURE__ */ jsx14(InputModal, {
29247
+ createBaseModal && /* @__PURE__ */ jsx17(InputModal, {
28391
29248
  title: `Base ref for ${createBaseModal.branch}`,
28392
29249
  placeholder: "origin/main (empty for default main)",
28393
29250
  errorMessage: createBaseError,
@@ -28403,7 +29260,7 @@ function App({ opts }) {
28403
29260
  setCreateDepsChoice({ branch, repoName, base: base2 || undefined });
28404
29261
  }
28405
29262
  }),
28406
- createDepsChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29263
+ createDepsChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28407
29264
  title: `Dependencies for ${createDepsChoice.branch}`,
28408
29265
  options: DEPS_CHOICES,
28409
29266
  onSubmit: (choice) => {
@@ -28413,7 +29270,7 @@ function App({ opts }) {
28413
29270
  },
28414
29271
  onCancel: () => setCreateDepsChoice(null)
28415
29272
  }),
28416
- pullPrModal && /* @__PURE__ */ jsx14(InputModal, {
29273
+ pullPrModal && /* @__PURE__ */ jsx17(InputModal, {
28417
29274
  title: `Pull PR into ${selectedRow?.repoName ?? ""}`,
28418
29275
  placeholder: "https://github.com/owner/repo/pull/123",
28419
29276
  errorMessage: pullPrError,
@@ -28441,7 +29298,7 @@ function App({ opts }) {
28441
29298
  setPullForceChoice({ link, repoName });
28442
29299
  }
28443
29300
  }),
28444
- pullForceChoice && /* @__PURE__ */ jsx14(ChoiceModal, {
29301
+ pullForceChoice && /* @__PURE__ */ jsx17(ChoiceModal, {
28445
29302
  title: `Pull ${pullForceChoice.link.split("/").pop()}?`,
28446
29303
  options: [
28447
29304
  { value: "normal", label: "Pull (skip if exists)", desc: "Fails if branch already exists" },
@@ -28454,7 +29311,7 @@ function App({ opts }) {
28454
29311
  },
28455
29312
  onCancel: () => setPullForceChoice(null)
28456
29313
  }),
28457
- renameModal && selectedRow && /* @__PURE__ */ jsx14(InputModal, {
29314
+ renameModal && selectedRow && /* @__PURE__ */ jsx17(InputModal, {
28458
29315
  title: `Rename branch ${selectedRow.branch}`,
28459
29316
  initialValue: selectedRow.branch,
28460
29317
  placeholder: "New branch name",
@@ -28479,7 +29336,7 @@ function App({ opts }) {
28479
29336
  setModal({ type: "confirm_rename", row: target, to });
28480
29337
  }
28481
29338
  }),
28482
- modal.type === "confirm_rename" && /* @__PURE__ */ jsx14(ConfirmModal, {
29339
+ modal.type === "confirm_rename" && /* @__PURE__ */ jsx17(ConfirmModal, {
28483
29340
  title: "Rename Worktree",
28484
29341
  message: [
28485
29342
  `${modal.row.branch} → ${modal.to}`,
@@ -28510,12 +29367,12 @@ function App({ opts }) {
28510
29367
  },
28511
29368
  onCancel: () => setModal({ type: "none" })
28512
29369
  }),
28513
- configOpen && /* @__PURE__ */ jsx14(ConfigOverlay, {
29370
+ configOpen && /* @__PURE__ */ jsx17(ConfigOverlay, {
28514
29371
  onClose: () => setConfigOpen(false),
28515
29372
  onSaved: () => void doRefresh(),
28516
29373
  onError: (msg) => setModal({ type: "error", message: msg })
28517
29374
  }),
28518
- failedLogs.length > 0 && /* @__PURE__ */ jsx14(ActionLogModal, {
29375
+ failedLogs.length > 0 && /* @__PURE__ */ jsx17(ActionLogModal, {
28519
29376
  title: failedLogs[0].title,
28520
29377
  lines: failedLogs[0].lines,
28521
29378
  done: true,
@@ -28529,7 +29386,9 @@ var VERBS, capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1), DEPS_CHOI
28529
29386
  var init_App = __esm(() => {
28530
29387
  init_useWorktrees();
28531
29388
  init_WorktreeTable();
28532
- init_DetailPane();
29389
+ init_TabPane();
29390
+ init_DetailsTab();
29391
+ init_TerminalView();
28533
29392
  init_Footer();
28534
29393
  init_Divider();
28535
29394
  init_HelpOverlay();
@@ -28542,10 +29401,9 @@ var init_App = __esm(() => {
28542
29401
  init_ConfigOverlay();
28543
29402
  init_WarningsOverlay();
28544
29403
  init_utils();
28545
- init_agents();
28546
29404
  init_config();
28547
- init_history();
28548
29405
  init_useSpinnerFrame();
29406
+ init_useTerminalSessions();
28549
29407
  VERBS = {
28550
29408
  create: "creating worktree",
28551
29409
  remove: "deleting",
@@ -28569,16 +29427,16 @@ var exports_tui = {};
28569
29427
  __export(exports_tui, {
28570
29428
  runTerminal: () => runTerminal
28571
29429
  });
28572
- import { createCliRenderer, TextTableRenderable } from "@opentui/core";
29430
+ import { createCliRenderer, TextTableRenderable, EmbeddedTerminalRenderable } from "@opentui/core";
28573
29431
  import { createRoot, extend as extend2 } from "@opentui/react";
28574
- import { jsx as jsx15 } from "@opentui/react/jsx-runtime";
29432
+ import { jsx as jsx18 } from "@opentui/react/jsx-runtime";
28575
29433
  async function runTerminal(opts) {
28576
29434
  const renderer = await createCliRenderer({
28577
29435
  exitOnCtrlC: false
28578
29436
  });
28579
29437
  const root = createRoot(renderer);
28580
29438
  try {
28581
- root.render(/* @__PURE__ */ jsx15(App, {
29439
+ root.render(/* @__PURE__ */ jsx18(App, {
28582
29440
  opts
28583
29441
  }));
28584
29442
  } catch (err) {
@@ -28589,7 +29447,7 @@ async function runTerminal(opts) {
28589
29447
  }
28590
29448
  var init_tui = __esm(() => {
28591
29449
  init_App();
28592
- extend2({ "text-table": TextTableRenderable });
29450
+ extend2({ "text-table": TextTableRenderable, "embedded-terminal": EmbeddedTerminalRenderable });
28593
29451
  });
28594
29452
 
28595
29453
  // node_modules/commander/lib/error.js
@@ -30928,7 +31786,80 @@ async function runPostCreateSetup(params) {
30928
31786
  init_deps();
30929
31787
  init_forge();
30930
31788
  init_owner();
30931
- init_agents();
31789
+
31790
+ // src/lib/agents.ts
31791
+ init_execa();
31792
+ var DEFAULT_AGENTS = {
31793
+ claude: "claude",
31794
+ codex: "codex",
31795
+ opencode: "opencode",
31796
+ cursor: "cursor"
31797
+ };
31798
+ function resolveAgentCommand(name, configured) {
31799
+ return configured?.[name]?.command ?? DEFAULT_AGENTS[name] ?? null;
31800
+ }
31801
+ function listAvailableAgents(configured) {
31802
+ return [...new Set([...Object.keys(DEFAULT_AGENTS), ...Object.keys(configured ?? {})])];
31803
+ }
31804
+ var tmuxAvailable = undefined;
31805
+ async function isTmuxAvailable() {
31806
+ if (tmuxAvailable !== undefined)
31807
+ return tmuxAvailable;
31808
+ try {
31809
+ await execa("tmux", ["-V"]);
31810
+ tmuxAvailable = true;
31811
+ } catch {
31812
+ tmuxAvailable = false;
31813
+ }
31814
+ return tmuxAvailable;
31815
+ }
31816
+ function tmuxSessionName(repoName, branch) {
31817
+ const sanitized = `${repoName}-${branch}`.replace(/[\/.:]/g, "-");
31818
+ return `wtx-${sanitized}`.substring(0, 60);
31819
+ }
31820
+ function buildTmuxArgs(session, wtPath, cmd) {
31821
+ return ["new-session", "-d", "-s", session, "-c", wtPath, cmd];
31822
+ }
31823
+ async function spawnAgentInWorktree(commandTemplate, wtPath, opts = {}) {
31824
+ const cmd = buildAgentCommand(commandTemplate, wtPath, opts.prompt, {
31825
+ branch: opts.branch,
31826
+ repo: opts.repoName
31827
+ });
31828
+ const session = opts.repoName && opts.branch ? tmuxSessionName(opts.repoName, opts.branch) : undefined;
31829
+ if (opts.dryRun) {
31830
+ if (session && await isTmuxAvailable()) {
31831
+ return { mode: "tmux", session };
31832
+ }
31833
+ return { mode: "direct" };
31834
+ }
31835
+ if (session && await isTmuxAvailable()) {
31836
+ try {
31837
+ const args = buildTmuxArgs(session, wtPath, cmd);
31838
+ await execa("tmux", args, { stdio: "inherit" });
31839
+ return { mode: "tmux", session };
31840
+ } catch {}
31841
+ }
31842
+ await execa(cmd, { shell: true, cwd: wtPath, stdio: "inherit" });
31843
+ return { mode: "direct" };
31844
+ }
31845
+ function shellQuote(value) {
31846
+ return `'${value.replaceAll("'", `'''`)}'`;
31847
+ }
31848
+ function buildAgentCommand(commandTemplate, wtPath, prompt, vars) {
31849
+ let cmd = commandTemplate.replaceAll("{wt}", shellQuote(wtPath));
31850
+ if (vars?.branch) {
31851
+ cmd = cmd.replaceAll("{branch}", shellQuote(vars.branch));
31852
+ }
31853
+ if (vars?.repo) {
31854
+ cmd = cmd.replaceAll("{repo}", shellQuote(vars.repo));
31855
+ }
31856
+ if (prompt) {
31857
+ cmd += ` ${shellQuote(prompt)}`;
31858
+ }
31859
+ return cmd;
31860
+ }
31861
+
31862
+ // src/commands/create.ts
30932
31863
  var DEPS_STRATEGIES = ["auto", "link", "symlink", "install", "off"];
30933
31864
  async function applyDepsStrategy(strategy, wtPath, mainPath, opts) {
30934
31865
  if (strategy === "install") {