agileflow 4.0.0-alpha.16 → 4.0.0-alpha.18
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
|
@@ -1182,11 +1182,32 @@ async function maybeOfferAutoRestore(prefs) {
|
|
|
1182
1182
|
if (ap !== bp) return bp - ap;
|
|
1183
1183
|
return 0;
|
|
1184
1184
|
});
|
|
1185
|
-
|
|
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
|
+
const allNames = sorted.map((s) => s.name);
|
|
1193
|
+
const sessionOptions = sorted.map((s) => ({
|
|
1186
1194
|
value: s.name,
|
|
1187
|
-
label: `${s.pinned ? "
|
|
1195
|
+
label: `${s.pinned ? "* " : " "}${s.name}`,
|
|
1188
1196
|
hint: `${s.cli} — ${s.cwd}${s.worktree && s.worktree.branch ? ` [wt ${s.worktree.branch}]` : ""}`,
|
|
1189
1197
|
}));
|
|
1198
|
+
const options = [
|
|
1199
|
+
{
|
|
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: "",
|
|
1208
|
+
},
|
|
1209
|
+
...sessionOptions,
|
|
1210
|
+
];
|
|
1190
1211
|
const anyPinned = sorted.some((s) => s.pinned === true);
|
|
1191
1212
|
const initial = anyPinned
|
|
1192
1213
|
? sorted.filter((s) => s.pinned === true).map((s) => s.name)
|
|
@@ -1210,7 +1231,19 @@ async function maybeOfferAutoRestore(prefs) {
|
|
|
1210
1231
|
process.exit(0);
|
|
1211
1232
|
}
|
|
1212
1233
|
/** @type {string[]} */
|
|
1213
|
-
|
|
1234
|
+
let chosen = Array.isArray(selection) ? selection : [];
|
|
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
|
+
}
|
|
1214
1247
|
if (chosen.length === 0) {
|
|
1215
1248
|
prompts.outro(
|
|
1216
1249
|
"Skipped. Run `agileflow launch restore` later to bring them back.",
|
|
@@ -19,6 +19,8 @@ const {
|
|
|
19
19
|
sessionExists,
|
|
20
20
|
createSession,
|
|
21
21
|
applyKeybindPreset,
|
|
22
|
+
applyTabFormat,
|
|
23
|
+
detectTmuxVersion,
|
|
22
24
|
} = require("./tmux.js");
|
|
23
25
|
const { loadRegistry } = require("./session-registry.js");
|
|
24
26
|
const { resolveAgileflowBin } = require("./alias-installer.js");
|
|
@@ -117,6 +119,14 @@ function runRestore(opts) {
|
|
|
117
119
|
log(`agileflow launch: failed to restore ${entry.name} — ${stderr}`);
|
|
118
120
|
continue;
|
|
119
121
|
}
|
|
122
|
+
// Apply the same per-session styling launchInTmux does for fresh
|
|
123
|
+
// sessions so the tab strip looks consistent on restore. Without
|
|
124
|
+
// this, restored sessions show tmux's default green status bar
|
|
125
|
+
// instead of the AgileFlow dark strip.
|
|
126
|
+
runner.runSync(["set-option", "-t", entry.name, "status", "1"]);
|
|
127
|
+
applyTabFormat(entry.name, runner, {
|
|
128
|
+
tmuxVersion: detectTmuxVersion(runner),
|
|
129
|
+
});
|
|
120
130
|
result.restored++;
|
|
121
131
|
log(`agileflow launch: restored session ${entry.name} (${entry.cwd})`);
|
|
122
132
|
}
|
|
@@ -273,17 +273,15 @@ const TAB_KEYBINDS = [
|
|
|
273
273
|
hint: "Alt+, → rename current tab",
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
|
-
// Direct
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
276
|
+
// Direct kill-window — no CLI roundtrip. The previous
|
|
277
|
+
// run-shell-to-agileflow approach added Node-startup latency
|
|
278
|
+
// (150ms+) and could no-op silently if the binary path resolution
|
|
279
|
+
// returned stale state (e.g. after npx cache cleanup). Killing
|
|
280
|
+
// via tmux directly is instant and bulletproof. Undo is provided
|
|
281
|
+
// by Alt+Shift+T (which reads the closed-windows log populated by
|
|
282
|
+
// the window-unlinked hook installed in applyTabFormat).
|
|
282
283
|
key: "M-w",
|
|
283
|
-
action: [
|
|
284
|
-
"run-shell",
|
|
285
|
-
"%AGILEFLOW% launch __close-window #{session_name} #{window_index}",
|
|
286
|
-
],
|
|
284
|
+
action: ["kill-window"],
|
|
287
285
|
hint: "Alt+w → close current tab (Alt+Shift+T to undo)",
|
|
288
286
|
},
|
|
289
287
|
{
|
|
@@ -241,24 +241,53 @@ 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
|
|
245
|
-
//
|
|
246
|
-
//
|
|
244
|
+
// Apply each option with the correct tmux scope. Session-scope opts
|
|
245
|
+
// (status-style, status-left/right, status-justify) take
|
|
246
|
+
// `-t <session>`. Window-scope opts (window-status-format,
|
|
247
|
+
// window-status-current-format, window-status-separator) take
|
|
248
|
+
// `-wg` so every window in every session picks them up. Earlier
|
|
249
|
+
// code applied ALL options with `-t session`, which made tmux
|
|
250
|
+
// silently ignore the window-scope ones — the visible symptom
|
|
251
|
+
// was tmux's default green status bar surviving on every session.
|
|
247
252
|
const ops = [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
{
|
|
254
|
+
scope: "session",
|
|
255
|
+
option: "status-style",
|
|
256
|
+
value: `bg=${BG},fg=${theme.inactiveFg}`,
|
|
257
|
+
},
|
|
258
|
+
{ scope: "session", option: "status-justify", value: "centre" },
|
|
259
|
+
{ scope: "session", option: "status-left", value: statusLeft },
|
|
260
|
+
{ scope: "session", option: "status-left-length", value: "100" },
|
|
261
|
+
{ scope: "session", option: "status-right", value: statusRight },
|
|
262
|
+
{ scope: "session", option: "status-right-length", value: "100" },
|
|
263
|
+
// Number windows from 1 so Alt+1 maps to the first tab (matches
|
|
264
|
+
// Chrome's Ctrl+1 mental model). Tmux default is base-index 0,
|
|
265
|
+
// which means Alt+1 with no other tabs open does nothing.
|
|
266
|
+
{ scope: "session", option: "base-index", value: "1" },
|
|
267
|
+
// Keep tab indices contiguous after a close — without this,
|
|
268
|
+
// closing window 2 leaves indices 1, 3, 4 and Alt+2 becomes
|
|
269
|
+
// dead. tmux renumbers on close so Alt+1..N always works.
|
|
270
|
+
{ scope: "session", option: "renumber-windows", value: "on" },
|
|
271
|
+
{ scope: "window-global", option: "window-status-separator", value: "" },
|
|
272
|
+
{
|
|
273
|
+
scope: "window-global",
|
|
274
|
+
option: "window-status-format",
|
|
275
|
+
value: inactiveFormat,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
scope: "window-global",
|
|
279
|
+
option: "window-status-current-format",
|
|
280
|
+
value: activeFormat,
|
|
281
|
+
},
|
|
257
282
|
];
|
|
258
283
|
let lastResult = { status: 0, stderr: "" };
|
|
259
284
|
const failures = [];
|
|
260
|
-
for (const
|
|
261
|
-
const
|
|
285
|
+
for (const { scope, option, value } of ops) {
|
|
286
|
+
const args =
|
|
287
|
+
scope === "session"
|
|
288
|
+
? ["set-option", "-t", sessionName, option, value]
|
|
289
|
+
: ["set-option", "-wg", option, value];
|
|
290
|
+
const r = runner.runSync(args);
|
|
262
291
|
lastResult = r;
|
|
263
292
|
if (r.status !== 0) {
|
|
264
293
|
failures.push({ option, stderr: (r.stderr || "").trim() });
|