agileflow 4.0.0-alpha.17 → 4.0.0-alpha.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "4.0.0-alpha.17",
3
+ "version": "4.0.0-alpha.19",
4
4
  "description": "AI-driven agile development toolkit for Claude Code — skills-first architecture with opt-in plugins (v4)",
5
5
  "keywords": [
6
6
  "agile",
@@ -1182,25 +1182,29 @@ async function maybeOfferAutoRestore(prefs) {
1182
1182
  if (ap !== bp) return bp - ap;
1183
1183
  return 0;
1184
1184
  });
1185
- // Synthetic toggles at the top of the picker for users with many
1186
- // saved sessions checking one of them rewrites the selection in
1187
- // bulk after submit (clack's multiselect doesn't natively support
1188
- // a "select all" shortcut, but synthetic entries are the lightest
1189
- // path and stay accessible via keyboard).
1190
- const SELECT_ALL = "__select_all__";
1191
- const DESELECT_ALL = "__deselect_all__";
1185
+ // Smart toggle at the top of the picker: one entry that flips the
1186
+ // current selection state. If everything's selected, checking it
1187
+ // deselects all; if anything's unselected, checking it selects all.
1188
+ // Visually separated from session entries by a dash divider line
1189
+ // so it doesn't blend in with real options.
1190
+ const TOGGLE_ALL = "__toggle_all__";
1191
+ const DIVIDER = "__divider__";
1192
1192
  const allNames = sorted.map((s) => s.name);
1193
1193
  const sessionOptions = sorted.map((s) => ({
1194
1194
  value: s.name,
1195
- label: `${s.pinned ? " " : " "}${s.name}`,
1195
+ label: `${s.pinned ? "* " : " "}${s.name}`,
1196
1196
  hint: `${s.cli} — ${s.cwd}${s.worktree && s.worktree.branch ? ` [wt ${s.worktree.branch}]` : ""}`,
1197
1197
  }));
1198
1198
  const options = [
1199
- { value: SELECT_ALL, label: "✅ Select all", hint: "check every session" },
1200
1199
  {
1201
- value: DESELECT_ALL,
1202
- label: " Deselect all",
1203
- hint: "uncheck every session",
1200
+ value: TOGGLE_ALL,
1201
+ label: "[ select all / deselect all ]",
1202
+ hint: "toggles every session below",
1203
+ },
1204
+ {
1205
+ value: DIVIDER,
1206
+ label: "─────────────────────────────",
1207
+ hint: "",
1204
1208
  },
1205
1209
  ...sessionOptions,
1206
1210
  ];
@@ -1228,14 +1232,18 @@ async function maybeOfferAutoRestore(prefs) {
1228
1232
  }
1229
1233
  /** @type {string[]} */
1230
1234
  let chosen = Array.isArray(selection) ? selection : [];
1231
- // Resolve the synthetic select-all / deselect-all toggles. If both
1232
- // were checked, select-all wins (the safer "do more" interpretation);
1233
- // either way, strip the synthetic entries from the final list.
1234
- const wantSelectAll = chosen.includes(SELECT_ALL);
1235
- const wantDeselectAll = chosen.includes(DESELECT_ALL);
1236
- chosen = chosen.filter((v) => v !== SELECT_ALL && v !== DESELECT_ALL);
1237
- if (wantSelectAll) chosen = [...allNames];
1238
- else if (wantDeselectAll) chosen = [];
1235
+ // Strip the synthetic divider always (it's never a real choice).
1236
+ // Resolve the toggle: if the user checked it, flip the current
1237
+ // selection state everything selected goes to nothing, anything
1238
+ // partial or empty goes to everything.
1239
+ const toggled = chosen.includes(TOGGLE_ALL);
1240
+ chosen = chosen.filter((v) => v !== TOGGLE_ALL && v !== DIVIDER);
1241
+ if (toggled) {
1242
+ const allSelected =
1243
+ chosen.length === allNames.length &&
1244
+ allNames.every((n) => chosen.includes(n));
1245
+ chosen = allSelected ? [] : [...allNames];
1246
+ }
1239
1247
  if (chosen.length === 0) {
1240
1248
  prompts.outro(
1241
1249
  "Skipped. Run `agileflow launch restore` later to bring them back.",
@@ -308,6 +308,14 @@ const TAB_KEYBINDS = [
308
308
  action: ["select-window", "-t", `:${n}`],
309
309
  hint: `Alt+${n} → switch to tab ${n}`,
310
310
  })),
311
+ // Alt+0 jumps to the LAST tab — matches Chrome's Ctrl+9 muscle
312
+ // memory (the 0 key sits naturally to the right of 9). tmux's
313
+ // `:$` is the alias for the last window in the session.
314
+ {
315
+ key: "M-0",
316
+ action: ["select-window", "-t", ":$"],
317
+ hint: "Alt+0 → switch to last tab",
318
+ },
311
319
  // Browser-style cycling. Bind M-S-Tab AND M-BTab — terminals split
312
320
  // on which escape sequence they emit for Shift+Tab, so binding both
313
321
  // covers either path. Note: some window managers (GNOME, KDE)
@@ -241,24 +241,60 @@ function applyTabFormat(sessionName, runner, opts = {}) {
241
241
  `#[bg=${PILL_BG},fg=${ACCENT}] #h ` +
242
242
  `#[fg=${PILL_BG},bg=${BG}]${HALF_ROUND_CLOSE}`;
243
243
 
244
- // Apply each option and collect failures. If ANYTHING fails we surface
245
- // it on stderr so users debugging "why doesn't my tab strip look right"
246
- // can see the tmux complaint instead of staring at default formatting.
244
+ // Reduce escape-time so pressing Esc inside command-prompt (Alt+n
245
+ // worktree-name prompt, etc.) cancels immediately. tmux's default
246
+ // is 500ms it waits for a follow-up key in case Esc is the start
247
+ // of an Alt+key sequence. 25ms is the common "snappy Esc" value used
248
+ // by tmux power-user configs (Oh My Tmux, gpakosz/.tmux, etc.).
249
+ runner.runSync(["set-option", "-sg", "escape-time", "25"]);
250
+
251
+ // Apply each option with the correct tmux scope. Session-scope opts
252
+ // (status-style, status-left/right, status-justify) take
253
+ // `-t <session>`. Window-scope opts (window-status-format,
254
+ // window-status-current-format, window-status-separator) take
255
+ // `-wg` so every window in every session picks them up. Earlier
256
+ // code applied ALL options with `-t session`, which made tmux
257
+ // silently ignore the window-scope ones — the visible symptom
258
+ // was tmux's default green status bar surviving on every session.
247
259
  const ops = [
248
- ["status-style", `bg=${BG},fg=${theme.inactiveFg}`],
249
- ["status-justify", "centre"],
250
- ["status-left", statusLeft],
251
- ["status-left-length", "100"],
252
- ["status-right", statusRight],
253
- ["status-right-length", "100"],
254
- ["window-status-separator", ""],
255
- ["window-status-format", inactiveFormat],
256
- ["window-status-current-format", activeFormat],
260
+ {
261
+ scope: "session",
262
+ option: "status-style",
263
+ value: `bg=${BG},fg=${theme.inactiveFg}`,
264
+ },
265
+ { scope: "session", option: "status-justify", value: "centre" },
266
+ { scope: "session", option: "status-left", value: statusLeft },
267
+ { scope: "session", option: "status-left-length", value: "100" },
268
+ { scope: "session", option: "status-right", value: statusRight },
269
+ { scope: "session", option: "status-right-length", value: "100" },
270
+ // Number windows from 1 so Alt+1 maps to the first tab (matches
271
+ // Chrome's Ctrl+1 mental model). Tmux default is base-index 0,
272
+ // which means Alt+1 with no other tabs open does nothing.
273
+ { scope: "session", option: "base-index", value: "1" },
274
+ // Keep tab indices contiguous after a close — without this,
275
+ // closing window 2 leaves indices 1, 3, 4 and Alt+2 becomes
276
+ // dead. tmux renumbers on close so Alt+1..N always works.
277
+ { scope: "session", option: "renumber-windows", value: "on" },
278
+ { scope: "window-global", option: "window-status-separator", value: "" },
279
+ {
280
+ scope: "window-global",
281
+ option: "window-status-format",
282
+ value: inactiveFormat,
283
+ },
284
+ {
285
+ scope: "window-global",
286
+ option: "window-status-current-format",
287
+ value: activeFormat,
288
+ },
257
289
  ];
258
290
  let lastResult = { status: 0, stderr: "" };
259
291
  const failures = [];
260
- for (const [option, value] of ops) {
261
- const r = runner.runSync(["set-option", "-t", sessionName, option, value]);
292
+ for (const { scope, option, value } of ops) {
293
+ const args =
294
+ scope === "session"
295
+ ? ["set-option", "-t", sessionName, option, value]
296
+ : ["set-option", "-wg", option, value];
297
+ const r = runner.runSync(args);
262
298
  lastResult = r;
263
299
  if (r.status !== 0) {
264
300
  failures.push({ option, stderr: (r.stderr || "").trim() });