opencode-worktree 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ui.ts CHANGED
@@ -10,12 +10,15 @@ import {
10
10
  type KeyEvent,
11
11
  type SelectOption,
12
12
  } from "@opentui/core";
13
- import { checkForUpdate } from "./update-check.js";
13
+ import { checkForUpdatesOnLaunch, getCachedUpdateNotice } from "./update-check.js";
14
14
  import { basename } from "node:path";
15
15
  import {
16
+ checkoutBranch,
17
+ createBranchFromCommit,
16
18
  createWorktree,
17
19
  deleteWorktree,
18
20
  getDefaultWorktreesDir,
21
+ getHeadCommit,
19
22
  hasUncommittedChanges,
20
23
  isMainWorktree,
21
24
  listWorktrees,
@@ -24,7 +27,7 @@ import {
24
27
  } from "./git.js";
25
28
  import { isCommandAvailable, launchCommand, openInFileManager } from "./opencode.js";
26
29
  import { WorktreeInfo } from "./types.js";
27
- import { loadRepoConfig, saveRepoConfig, configExists, type Config } from "./config.js";
30
+ import { loadRepoConfig, saveRepoConfig, type Config } from "./config.js";
28
31
  import { runPostCreateHook, type HookResult } from "./hooks.js";
29
32
 
30
33
  type StatusLevel = "info" | "warning" | "error" | "success";
@@ -106,7 +109,16 @@ class WorktreeSelector {
106
109
  private configOpenInput: InputRenderable | null = null;
107
110
  private configLaunchInput: InputRenderable | null = null;
108
111
  private configActiveField: "hook" | "open" | "launch" = "hook";
109
- private isFirstTimeSetup = false;
112
+ private repoKey: string | null = null; // Normalized git remote URL for config lookup
113
+
114
+ // Branch creation state
115
+ private isCreatingBranch = false;
116
+ private branchCreateContainer: BoxRenderable | null = null;
117
+ private branchNameInput: InputRenderable | null = null;
118
+ private sourceWorktree: WorktreeInfo | null = null;
119
+ private pendingBranchName: string | null = null;
120
+ private isAskingCheckout = false;
121
+ private checkoutSelect: SelectRenderable | null = null;
110
122
 
111
123
  constructor(
112
124
  private renderer: CliRenderer,
@@ -115,7 +127,11 @@ class WorktreeSelector {
115
127
  ) {
116
128
  // Load worktrees first to get initial options
117
129
  this.repoRoot = resolveRepoRoot(this.targetPath);
118
- this.repoConfig = this.repoRoot ? loadRepoConfig(this.repoRoot) : {};
130
+ if (this.repoRoot) {
131
+ const { config, repoKey } = loadRepoConfig(this.repoRoot);
132
+ this.repoConfig = config;
133
+ this.repoKey = repoKey;
134
+ }
119
135
  this.opencodeAvailable = isCommandAvailable(this.repoConfig.launchCommand || "opencode");
120
136
  this.worktreeOptions = this.buildInitialOptions();
121
137
 
@@ -129,21 +145,17 @@ class WorktreeSelector {
129
145
  });
130
146
  this.renderer.root.add(this.title);
131
147
 
132
- // Display version or update notification in title line
148
+ // Display version/update notice from previous launch, then refresh cache now.
133
149
  if (this.pkg) {
134
- const updateInfo = checkForUpdate(this.pkg);
150
+ const updateInfo = getCachedUpdateNotice(this.pkg);
151
+ checkForUpdatesOnLaunch(this.pkg);
135
152
 
136
- let noticeContent: string;
137
- let noticeColor: string;
153
+ let noticeContent = `v${this.pkg.version}`;
154
+ let noticeColor = "#64748B";
138
155
 
139
156
  if (updateInfo?.hasUpdate) {
140
- // Update available
141
- noticeContent = `Update: ${updateInfo.current} → ${updateInfo.latest} (npm i -g)`;
142
- noticeColor = "#F59E0B"; // Amber
143
- } else {
144
- // On latest version (or no cache yet)
145
- noticeContent = `v${this.pkg.version}`;
146
- noticeColor = "#64748B"; // Subtle gray
157
+ noticeContent = `Update: ${updateInfo.current} -> ${updateInfo.latest} (npm i -g)`;
158
+ noticeColor = "#F59E0B";
147
159
  }
148
160
 
149
161
  this.versionNotice = new TextRenderable(renderer, {
@@ -195,7 +207,7 @@ class WorktreeSelector {
195
207
  left: 2,
196
208
  top: 20,
197
209
  content:
198
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit",
210
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit",
199
211
  fg: "#64748B",
200
212
  });
201
213
  this.renderer.root.add(this.instructions);
@@ -216,11 +228,6 @@ class WorktreeSelector {
216
228
  });
217
229
 
218
230
  this.selectElement.focus();
219
-
220
- // Check for first-time setup
221
- if (this.repoRoot && !configExists(this.repoRoot)) {
222
- this.showFirstTimeSetup();
223
- }
224
231
  }
225
232
 
226
233
  private getInitialStatusMessage(): string {
@@ -265,7 +272,7 @@ class WorktreeSelector {
265
272
  this.selectElement.visible = true;
266
273
  this.selectElement.focus();
267
274
  this.instructions.content =
268
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
275
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
269
276
  return;
270
277
  }
271
278
  this.cleanup(true);
@@ -353,6 +360,24 @@ class WorktreeSelector {
353
360
  return;
354
361
  }
355
362
 
363
+ // Handle branch creation mode
364
+ if (this.isCreatingBranch) {
365
+ if (key.name === "escape") {
366
+ this.hideBranchCreateInput();
367
+ return;
368
+ }
369
+ return;
370
+ }
371
+
372
+ // Handle checkout confirmation mode
373
+ if (this.isAskingCheckout) {
374
+ if (key.name === "escape") {
375
+ this.hideCheckoutConfirm();
376
+ return;
377
+ }
378
+ return;
379
+ }
380
+
356
381
  if (key.name === "q" || key.name === "escape") {
357
382
  this.cleanup(true);
358
383
  return;
@@ -386,6 +411,12 @@ class WorktreeSelector {
386
411
  this.showConfigEditor();
387
412
  return;
388
413
  }
414
+
415
+ // 'b' for creating a new branch from selected worktree
416
+ if (key.name === "b") {
417
+ this.showBranchCreateInput();
418
+ return;
419
+ }
389
420
  }
390
421
 
391
422
  private handleSelection(value: SelectionValue): void {
@@ -412,9 +443,8 @@ class WorktreeSelector {
412
443
  return;
413
444
  }
414
445
 
415
- // Load config to check for custom open command
416
- const config = this.repoRoot ? loadRepoConfig(this.repoRoot) : {};
417
- const customCommand = config.openCommand;
446
+ // Use the already-loaded config's openCommand
447
+ const customCommand = this.repoConfig.openCommand;
418
448
 
419
449
  const success = openInFileManager(worktree.path, customCommand);
420
450
  if (success) {
@@ -497,7 +527,7 @@ class WorktreeSelector {
497
527
 
498
528
  this.selectElement.visible = true;
499
529
  this.instructions.content =
500
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
530
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
501
531
  this.selectElement.focus();
502
532
  this.loadWorktrees(selectWorktreePath);
503
533
  }
@@ -523,11 +553,10 @@ class WorktreeSelector {
523
553
  if (result.success) {
524
554
  this.setStatus(`Worktree created at ${result.path}`, "success");
525
555
 
526
- // Check for post-create hook
527
- const config = loadRepoConfig(this.repoRoot);
528
- if (config.postCreateHook) {
556
+ // Check for post-create hook (use already-loaded config)
557
+ if (this.repoConfig.postCreateHook) {
529
558
  this.pendingWorktreePath = result.path;
530
- this.runHook(result.path, config.postCreateHook);
559
+ this.runHook(result.path, this.repoConfig.postCreateHook);
531
560
  } else {
532
561
  // No hook, launch command directly
533
562
  this.hideCreateWorktreeInput();
@@ -692,7 +721,7 @@ class WorktreeSelector {
692
721
  this.selectElement.visible = true;
693
722
  this.selectElement.focus();
694
723
  this.instructions.content =
695
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
724
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
696
725
  }
697
726
  }
698
727
 
@@ -716,11 +745,6 @@ class WorktreeSelector {
716
745
 
717
746
  // ========== Config Editor Methods ==========
718
747
 
719
- private showFirstTimeSetup(): void {
720
- this.isFirstTimeSetup = true;
721
- this.showConfigEditor();
722
- }
723
-
724
748
  private showConfigEditor(): void {
725
749
  if (!this.repoRoot) {
726
750
  this.setStatus("No git repository found.", "error");
@@ -732,12 +756,18 @@ class WorktreeSelector {
732
756
  this.selectElement.visible = false;
733
757
  this.selectElement.blur();
734
758
 
735
- // Load existing config to pre-fill
736
- const existingConfig = loadRepoConfig(this.repoRoot);
737
-
738
- const title = this.isFirstTimeSetup
739
- ? "First-time Setup: Project Configuration"
740
- : "Edit Project Configuration";
759
+ // Build title showing repo key
760
+ let title: string;
761
+ if (this.repoKey) {
762
+ // Truncate if too long for the box
763
+ const maxKeyLen = 50;
764
+ const displayKey = this.repoKey.length > maxKeyLen
765
+ ? "..." + this.repoKey.slice(-maxKeyLen + 3)
766
+ : this.repoKey;
767
+ title = `Config: ${displayKey}`;
768
+ } else {
769
+ title = "Config: [no remote]";
770
+ }
741
771
 
742
772
  this.configContainer = new BoxRenderable(this.renderer, {
743
773
  id: "config-container",
@@ -773,7 +803,7 @@ class WorktreeSelector {
773
803
  top: 2,
774
804
  width: 72,
775
805
  placeholder: "npm install",
776
- value: existingConfig.postCreateHook || "",
806
+ value: this.repoConfig.postCreateHook || "",
777
807
  focusedBackgroundColor: "#1E293B",
778
808
  backgroundColor: "#1E293B",
779
809
  });
@@ -797,7 +827,7 @@ class WorktreeSelector {
797
827
  top: 5,
798
828
  width: 72,
799
829
  placeholder: "open (default)",
800
- value: existingConfig.openCommand || "",
830
+ value: this.repoConfig.openCommand || "",
801
831
  focusedBackgroundColor: "#1E293B",
802
832
  backgroundColor: "#1E293B",
803
833
  });
@@ -821,30 +851,38 @@ class WorktreeSelector {
821
851
  top: 8,
822
852
  width: 72,
823
853
  placeholder: "opencode (default)",
824
- value: existingConfig.launchCommand || "",
854
+ value: this.repoConfig.launchCommand || "",
825
855
  focusedBackgroundColor: "#1E293B",
826
856
  backgroundColor: "#1E293B",
827
857
  });
828
858
  this.configContainer.add(this.configLaunchInput);
829
859
 
830
- // Help text
860
+ // Help text - show warning if no remote
861
+ let helpContent: string;
862
+ if (this.repoKey) {
863
+ helpContent = "Tab to switch fields • Leave empty to use defaults";
864
+ } else {
865
+ helpContent = "No remote - config won't be saved for this repo";
866
+ }
867
+
831
868
  const helpText = new TextRenderable(this.renderer, {
832
869
  id: "config-help",
833
870
  position: "absolute",
834
871
  left: 1,
835
872
  top: 10,
836
- content: "Tab to switch fields • Leave empty to use defaults",
837
- fg: "#64748B",
873
+ content: helpContent,
874
+ fg: this.repoKey ? "#64748B" : "#F59E0B",
838
875
  });
839
876
  this.configContainer.add(helpText);
840
877
 
841
878
  this.instructions.content = "Tab switch • Enter save • Esc cancel";
842
- this.setStatus(
843
- this.isFirstTimeSetup
844
- ? "Welcome! Configure your project settings."
845
- : "Edit project configuration.",
846
- "info"
847
- );
879
+
880
+ // Show warning if no remote
881
+ if (this.repoKey) {
882
+ this.setStatus("Edit project configuration.", "info");
883
+ } else {
884
+ this.setStatus("Warning: No git remote. Config changes won't be saved.", "warning");
885
+ }
848
886
 
849
887
  // Delay focus to prevent the triggering keypress from being captured
850
888
  setTimeout(() => {
@@ -855,7 +893,6 @@ class WorktreeSelector {
855
893
 
856
894
  private hideConfigEditor(): void {
857
895
  this.isEditingConfig = false;
858
- this.isFirstTimeSetup = false;
859
896
 
860
897
  if (this.configHookInput) {
861
898
  this.configHookInput.blur();
@@ -877,7 +914,7 @@ class WorktreeSelector {
877
914
 
878
915
  this.selectElement.visible = true;
879
916
  this.instructions.content =
880
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
917
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
881
918
 
882
919
  // Delay focus to prevent the Enter keypress from triggering a selection
883
920
  setTimeout(() => {
@@ -935,6 +972,279 @@ class WorktreeSelector {
935
972
  this.hideConfigEditor();
936
973
  }
937
974
 
975
+ // ========== Branch Creation Methods ==========
976
+
977
+ private showBranchCreateInput(): void {
978
+ const worktree = this.getSelectedWorktree();
979
+ if (!worktree) {
980
+ this.setStatus("Select a worktree to create a branch from.", "warning");
981
+ return;
982
+ }
983
+
984
+ if (!this.repoRoot) {
985
+ this.setStatus("No git repository found.", "error");
986
+ return;
987
+ }
988
+
989
+ this.isCreatingBranch = true;
990
+ this.sourceWorktree = worktree;
991
+ this.selectElement.visible = false;
992
+ this.selectElement.blur();
993
+
994
+ const sourceName = worktree.branch || basename(worktree.path);
995
+
996
+ this.branchCreateContainer = new BoxRenderable(this.renderer, {
997
+ id: "branch-create-container",
998
+ position: "absolute",
999
+ left: 2,
1000
+ top: 3,
1001
+ width: 76,
1002
+ height: 7,
1003
+ borderStyle: "single",
1004
+ borderColor: "#38BDF8",
1005
+ title: `New Branch from: ${sourceName}`,
1006
+ titleAlignment: "center",
1007
+ backgroundColor: "#0F172A",
1008
+ border: true,
1009
+ });
1010
+ this.renderer.root.add(this.branchCreateContainer);
1011
+
1012
+ const inputLabel = new TextRenderable(this.renderer, {
1013
+ id: "branch-name-label",
1014
+ position: "absolute",
1015
+ left: 1,
1016
+ top: 1,
1017
+ content: "Branch name:",
1018
+ fg: "#E2E8F0",
1019
+ });
1020
+ this.branchCreateContainer.add(inputLabel);
1021
+
1022
+ this.branchNameInput = new InputRenderable(this.renderer, {
1023
+ id: "branch-name-input",
1024
+ position: "absolute",
1025
+ left: 14,
1026
+ top: 1,
1027
+ width: 58,
1028
+ placeholder: "feature/new-branch",
1029
+ focusedBackgroundColor: "#1E293B",
1030
+ backgroundColor: "#1E293B",
1031
+ });
1032
+ this.branchCreateContainer.add(this.branchNameInput);
1033
+
1034
+ const helpText = new TextRenderable(this.renderer, {
1035
+ id: "branch-create-help",
1036
+ position: "absolute",
1037
+ left: 1,
1038
+ top: 3,
1039
+ content: `Branch will start from commit: ${worktree.head.slice(0, 8)}`,
1040
+ fg: "#64748B",
1041
+ });
1042
+ this.branchCreateContainer.add(helpText);
1043
+
1044
+ this.branchNameInput.on(InputRenderableEvents.CHANGE, (value: string) => {
1045
+ this.handleBranchCreate(value);
1046
+ });
1047
+
1048
+ this.instructions.content = "Enter to create • Esc to cancel";
1049
+ this.setStatus("Enter a name for the new branch.", "info");
1050
+
1051
+ this.branchNameInput.focus();
1052
+ this.renderer.requestRender();
1053
+ }
1054
+
1055
+ private hideBranchCreateInput(): void {
1056
+ this.isCreatingBranch = false;
1057
+ this.sourceWorktree = null;
1058
+
1059
+ if (this.branchNameInput) {
1060
+ this.branchNameInput.blur();
1061
+ }
1062
+
1063
+ if (this.branchCreateContainer) {
1064
+ this.renderer.root.remove(this.branchCreateContainer.id);
1065
+ this.branchCreateContainer = null;
1066
+ this.branchNameInput = null;
1067
+ }
1068
+
1069
+ this.selectElement.visible = true;
1070
+ this.instructions.content =
1071
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
1072
+ this.selectElement.focus();
1073
+ this.renderer.requestRender();
1074
+ }
1075
+
1076
+ private handleBranchCreate(branchName: string): void {
1077
+ const trimmed = branchName.trim();
1078
+ if (!trimmed) {
1079
+ this.setStatus("Branch name cannot be empty.", "error");
1080
+ return;
1081
+ }
1082
+
1083
+ if (!this.repoRoot || !this.sourceWorktree) {
1084
+ this.setStatus("No source worktree selected.", "error");
1085
+ return;
1086
+ }
1087
+
1088
+ const commitHash = this.sourceWorktree.head;
1089
+ if (!commitHash) {
1090
+ this.setStatus("Could not determine source commit.", "error");
1091
+ return;
1092
+ }
1093
+
1094
+ this.setStatus(`Creating branch '${trimmed}'...`, "info");
1095
+ this.renderer.requestRender();
1096
+
1097
+ const result = createBranchFromCommit(this.repoRoot, trimmed, commitHash);
1098
+
1099
+ if (result.success) {
1100
+ this.pendingBranchName = trimmed;
1101
+ // Hide the input and show checkout confirmation
1102
+ if (this.branchNameInput) {
1103
+ this.branchNameInput.blur();
1104
+ }
1105
+ if (this.branchCreateContainer) {
1106
+ this.renderer.root.remove(this.branchCreateContainer.id);
1107
+ this.branchCreateContainer = null;
1108
+ this.branchNameInput = null;
1109
+ }
1110
+ this.isCreatingBranch = false;
1111
+
1112
+ this.showCheckoutConfirm(trimmed);
1113
+ } else {
1114
+ this.setStatus(`Failed to create branch: ${result.error}`, "error");
1115
+ }
1116
+ }
1117
+
1118
+ private showCheckoutConfirm(branchName: string): void {
1119
+ if (!this.sourceWorktree) {
1120
+ this.setStatus("No source worktree.", "error");
1121
+ this.hideBranchCreateInput();
1122
+ return;
1123
+ }
1124
+
1125
+ this.isAskingCheckout = true;
1126
+
1127
+ const sourceName = this.sourceWorktree.branch || basename(this.sourceWorktree.path);
1128
+
1129
+ this.branchCreateContainer = new BoxRenderable(this.renderer, {
1130
+ id: "checkout-confirm-container",
1131
+ position: "absolute",
1132
+ left: 2,
1133
+ top: 3,
1134
+ width: 76,
1135
+ height: 8,
1136
+ borderStyle: "single",
1137
+ borderColor: "#10B981",
1138
+ title: `Branch '${branchName}' Created`,
1139
+ titleAlignment: "center",
1140
+ backgroundColor: "#0F172A",
1141
+ border: true,
1142
+ });
1143
+ this.renderer.root.add(this.branchCreateContainer);
1144
+
1145
+ const infoText = new TextRenderable(this.renderer, {
1146
+ id: "checkout-info",
1147
+ position: "absolute",
1148
+ left: 1,
1149
+ top: 1,
1150
+ content: `Checkout '${branchName}' in worktree '${sourceName}'?`,
1151
+ fg: "#E2E8F0",
1152
+ });
1153
+ this.branchCreateContainer.add(infoText);
1154
+
1155
+ this.checkoutSelect = new SelectRenderable(this.renderer, {
1156
+ id: "checkout-select",
1157
+ position: "absolute",
1158
+ left: 1,
1159
+ top: 3,
1160
+ width: 72,
1161
+ height: 3,
1162
+ options: [
1163
+ {
1164
+ name: "Yes, checkout the new branch",
1165
+ description: "Switch this worktree to the new branch",
1166
+ value: "checkout",
1167
+ },
1168
+ {
1169
+ name: "No, keep current branch",
1170
+ description: "Branch created but worktree stays on current branch",
1171
+ value: "keep",
1172
+ },
1173
+ ],
1174
+ backgroundColor: "#0F172A",
1175
+ focusedBackgroundColor: "#1E293B",
1176
+ selectedBackgroundColor: "#1E3A5F",
1177
+ textColor: "#E2E8F0",
1178
+ selectedTextColor: "#38BDF8",
1179
+ descriptionColor: "#94A3B8",
1180
+ selectedDescriptionColor: "#E2E8F0",
1181
+ showDescription: true,
1182
+ wrapSelection: true,
1183
+ });
1184
+ this.branchCreateContainer.add(this.checkoutSelect);
1185
+
1186
+ this.checkoutSelect.on(
1187
+ SelectRenderableEvents.ITEM_SELECTED,
1188
+ (_index: number, option: SelectOption) => {
1189
+ this.handleCheckoutChoice(option.value as string);
1190
+ }
1191
+ );
1192
+
1193
+ this.instructions.content = "↑/↓ select • Enter confirm • Esc cancel";
1194
+ this.setStatus(`Branch '${branchName}' created successfully!`, "success");
1195
+
1196
+ this.checkoutSelect.focus();
1197
+ this.renderer.requestRender();
1198
+ }
1199
+
1200
+ private hideCheckoutConfirm(): void {
1201
+ this.isAskingCheckout = false;
1202
+ this.pendingBranchName = null;
1203
+ this.sourceWorktree = null;
1204
+
1205
+ if (this.checkoutSelect) {
1206
+ this.checkoutSelect.blur();
1207
+ this.checkoutSelect = null;
1208
+ }
1209
+
1210
+ if (this.branchCreateContainer) {
1211
+ this.renderer.root.remove(this.branchCreateContainer.id);
1212
+ this.branchCreateContainer = null;
1213
+ }
1214
+
1215
+ this.selectElement.visible = true;
1216
+ this.instructions.content =
1217
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
1218
+ this.selectElement.focus();
1219
+ this.loadWorktrees();
1220
+ this.renderer.requestRender();
1221
+ }
1222
+
1223
+ private handleCheckoutChoice(choice: string): void {
1224
+ if (choice === "checkout" && this.pendingBranchName && this.sourceWorktree) {
1225
+ this.setStatus(`Checking out '${this.pendingBranchName}'...`, "info");
1226
+ this.renderer.requestRender();
1227
+
1228
+ const result = checkoutBranch(this.sourceWorktree.path, this.pendingBranchName);
1229
+
1230
+ if (result.success) {
1231
+ this.setStatus(
1232
+ `Switched to branch '${this.pendingBranchName}'.`,
1233
+ "success"
1234
+ );
1235
+ } else {
1236
+ this.setStatus(`Failed to checkout: ${result.error}`, "error");
1237
+ }
1238
+ } else {
1239
+ this.setStatus(
1240
+ `Branch '${this.pendingBranchName}' created (not checked out).`,
1241
+ "success"
1242
+ );
1243
+ }
1244
+
1245
+ this.hideCheckoutConfirm();
1246
+ }
1247
+
938
1248
  private loadWorktrees(selectWorktreePath?: string): void {
939
1249
  this.repoRoot = resolveRepoRoot(this.targetPath);
940
1250
  if (!this.repoRoot) {
@@ -1242,7 +1552,7 @@ class WorktreeSelector {
1242
1552
 
1243
1553
  this.selectElement.visible = true;
1244
1554
  this.instructions.content =
1245
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
1555
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
1246
1556
  this.selectElement.focus();
1247
1557
  this.loadWorktrees();
1248
1558
  }
@@ -1371,7 +1681,7 @@ class WorktreeSelector {
1371
1681
 
1372
1682
  this.loadWorktrees();
1373
1683
  this.instructions.content =
1374
- "↑/↓ navigate • Enter open • o folder • d delete • n new • c config • q quit";
1684
+ "↑/↓ navigate • Enter open • o folder • d delete • n new • b branch • c config • q quit";
1375
1685
  this.renderer.requestRender();
1376
1686
  }
1377
1687