aoaoe 0.79.0 → 0.81.0
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/dist/index.js +17 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.js +35 -0
- package/dist/tui.d.ts +27 -5
- package/dist/tui.js +94 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -378,6 +378,23 @@ async function main() {
|
|
|
378
378
|
tui.setCompact(enabled);
|
|
379
379
|
tui.log("system", `compact mode: ${enabled ? "on" : "off"}`);
|
|
380
380
|
});
|
|
381
|
+
// wire /bell toggle
|
|
382
|
+
input.onBell(() => {
|
|
383
|
+
const enabled = !tui.isBellEnabled();
|
|
384
|
+
tui.setBell(enabled);
|
|
385
|
+
tui.log("system", `bell notifications: ${enabled ? "on" : "off"}`);
|
|
386
|
+
});
|
|
387
|
+
// wire /pin toggle
|
|
388
|
+
input.onPin((target) => {
|
|
389
|
+
const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
|
|
390
|
+
const ok = tui.togglePin(num ?? target);
|
|
391
|
+
if (ok) {
|
|
392
|
+
tui.log("system", `pin toggled: ${target}`);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
tui.log("system", `session not found: ${target}`);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
381
398
|
// wire mouse move to hover highlight on session cards (disabled in compact)
|
|
382
399
|
input.onMouseMove((row, _col) => {
|
|
383
400
|
if (tui.getViewMode() === "overview" && !tui.isCompact()) {
|
package/dist/input.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type SearchHandler = (pattern: string | null) => void;
|
|
|
5
5
|
export type QuickSwitchHandler = (sessionNum: number) => void;
|
|
6
6
|
export type SortHandler = (mode: string | null) => void;
|
|
7
7
|
export type CompactHandler = () => void;
|
|
8
|
+
export type PinHandler = (target: string) => void;
|
|
9
|
+
export type BellHandler = () => void;
|
|
8
10
|
export interface MouseEvent {
|
|
9
11
|
button: number;
|
|
10
12
|
col: number;
|
|
@@ -32,6 +34,8 @@ export declare class InputReader {
|
|
|
32
34
|
private quickSwitchHandler;
|
|
33
35
|
private sortHandler;
|
|
34
36
|
private compactHandler;
|
|
37
|
+
private pinHandler;
|
|
38
|
+
private bellHandler;
|
|
35
39
|
private mouseDataListener;
|
|
36
40
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
37
41
|
onQueueChange(handler: (count: number) => void): void;
|
|
@@ -43,6 +47,8 @@ export declare class InputReader {
|
|
|
43
47
|
onQuickSwitch(handler: QuickSwitchHandler): void;
|
|
44
48
|
onSort(handler: SortHandler): void;
|
|
45
49
|
onCompact(handler: CompactHandler): void;
|
|
50
|
+
onPin(handler: PinHandler): void;
|
|
51
|
+
onBell(handler: BellHandler): void;
|
|
46
52
|
private notifyQueueChange;
|
|
47
53
|
start(): void;
|
|
48
54
|
drain(): string[];
|
package/dist/input.js
CHANGED
|
@@ -37,6 +37,8 @@ export class InputReader {
|
|
|
37
37
|
quickSwitchHandler = null;
|
|
38
38
|
sortHandler = null;
|
|
39
39
|
compactHandler = null;
|
|
40
|
+
pinHandler = null;
|
|
41
|
+
bellHandler = null;
|
|
40
42
|
mouseDataListener = null;
|
|
41
43
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
42
44
|
onScroll(handler) {
|
|
@@ -78,6 +80,14 @@ export class InputReader {
|
|
|
78
80
|
onCompact(handler) {
|
|
79
81
|
this.compactHandler = handler;
|
|
80
82
|
}
|
|
83
|
+
// register a callback for pin/unpin commands (/pin <target>)
|
|
84
|
+
onPin(handler) {
|
|
85
|
+
this.pinHandler = handler;
|
|
86
|
+
}
|
|
87
|
+
// register a callback for bell toggle (/bell)
|
|
88
|
+
onBell(handler) {
|
|
89
|
+
this.bellHandler = handler;
|
|
90
|
+
}
|
|
81
91
|
notifyQueueChange() {
|
|
82
92
|
this.queueChangeHandler?.(this.queue.length);
|
|
83
93
|
}
|
|
@@ -257,6 +267,8 @@ ${BOLD}navigation:${RESET}
|
|
|
257
267
|
/back return to overview from drill-down
|
|
258
268
|
/sort [mode] sort sessions: status, name, activity, default (or cycle)
|
|
259
269
|
/compact toggle compact mode (dense session panel)
|
|
270
|
+
/pin [N|name] pin/unpin a session to the top (toggle)
|
|
271
|
+
/bell toggle terminal bell on errors/completions
|
|
260
272
|
/search <pattern> filter activity entries by substring (case-insensitive)
|
|
261
273
|
/search clear active search filter
|
|
262
274
|
click session click an agent card to drill down (click again to go back)
|
|
@@ -354,6 +366,29 @@ ${BOLD}other:${RESET}
|
|
|
354
366
|
console.error(`${DIM}compact mode not available (no TUI)${RESET}`);
|
|
355
367
|
}
|
|
356
368
|
break;
|
|
369
|
+
case "/pin": {
|
|
370
|
+
const pinArg = line.slice("/pin".length).trim();
|
|
371
|
+
if (this.pinHandler) {
|
|
372
|
+
if (pinArg) {
|
|
373
|
+
this.pinHandler(pinArg);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
console.error(`${DIM}usage: /pin <N|name> — toggle pin for a session${RESET}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
console.error(`${DIM}pin not available (no TUI)${RESET}`);
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "/bell":
|
|
385
|
+
if (this.bellHandler) {
|
|
386
|
+
this.bellHandler();
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
console.error(`${DIM}bell not available (no TUI)${RESET}`);
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
357
392
|
case "/search": {
|
|
358
393
|
const searchArg = line.slice("/search".length).trim();
|
|
359
394
|
if (this.searchHandler) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -2,18 +2,24 @@ import type { DaemonSessionState, DaemonPhase } from "./types.js";
|
|
|
2
2
|
import type { HistoryEntry } from "./tui-history.js";
|
|
3
3
|
export type SortMode = "default" | "status" | "name" | "activity";
|
|
4
4
|
declare const SORT_MODES: SortMode[];
|
|
5
|
-
/** Sort sessions by mode. Returns a new array (never mutates). */
|
|
6
|
-
declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>): DaemonSessionState[];
|
|
5
|
+
/** Sort sessions by mode. Pinned sessions always sort first (stable). Returns a new array (never mutates). */
|
|
6
|
+
declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>, pinnedIds?: Set<string>): DaemonSessionState[];
|
|
7
7
|
/** Cycle to the next sort mode. */
|
|
8
8
|
declare function nextSortMode(current: SortMode): SortMode;
|
|
9
|
+
/** Cooldown between terminal bells to avoid buzzing. */
|
|
10
|
+
export declare const BELL_COOLDOWN_MS = 5000;
|
|
11
|
+
/** Determine if an activity entry should trigger a terminal bell. High-signal events only. */
|
|
12
|
+
export declare function shouldBell(tag: string, text: string): boolean;
|
|
9
13
|
/** Max name length in compact token. */
|
|
10
14
|
declare const COMPACT_NAME_LEN = 10;
|
|
15
|
+
/** Pin indicator for pinned sessions. */
|
|
16
|
+
declare const PIN_ICON = "\u25B2";
|
|
11
17
|
/**
|
|
12
18
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
13
|
-
* Each token: "{idx}{dot}{name}" — e.g. "1
|
|
19
|
+
* Each token: "{idx}{pin?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2●Bravo" for unpinned.
|
|
14
20
|
* Returns array of formatted row strings (one per display row).
|
|
15
21
|
*/
|
|
16
|
-
declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number): string[];
|
|
22
|
+
declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number, pinnedIds?: Set<string>): string[];
|
|
17
23
|
/** Compute how many display rows compact mode needs (minimum 1). */
|
|
18
24
|
declare function computeCompactRowCount(sessions: DaemonSessionState[], maxWidth: number): number;
|
|
19
25
|
declare function phaseDisplay(phase: DaemonPhase, paused: boolean, spinnerFrame: number): string;
|
|
@@ -46,6 +52,9 @@ export declare class TUI {
|
|
|
46
52
|
private lastChangeAt;
|
|
47
53
|
private prevLastActivity;
|
|
48
54
|
private compactMode;
|
|
55
|
+
private pinnedIds;
|
|
56
|
+
private bellEnabled;
|
|
57
|
+
private lastBellAt;
|
|
49
58
|
private viewMode;
|
|
50
59
|
private drilldownSessionId;
|
|
51
60
|
private sessionOutputs;
|
|
@@ -71,6 +80,19 @@ export declare class TUI {
|
|
|
71
80
|
setCompact(enabled: boolean): void;
|
|
72
81
|
/** Return whether compact mode is enabled. */
|
|
73
82
|
isCompact(): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Toggle pin for a session (by 1-indexed number, ID, ID prefix, or title).
|
|
85
|
+
* Pinned sessions always sort to the top. Returns true if session found.
|
|
86
|
+
*/
|
|
87
|
+
togglePin(sessionIdOrIndex: string | number): boolean;
|
|
88
|
+
/** Check if a session ID is pinned. */
|
|
89
|
+
isPinned(id: string): boolean;
|
|
90
|
+
/** Return count of pinned sessions. */
|
|
91
|
+
getPinnedCount(): number;
|
|
92
|
+
/** Enable or disable terminal bell notifications. */
|
|
93
|
+
setBell(enabled: boolean): void;
|
|
94
|
+
/** Return whether terminal bell is enabled. */
|
|
95
|
+
isBellEnabled(): boolean;
|
|
74
96
|
updateState(opts: {
|
|
75
97
|
phase?: DaemonPhase;
|
|
76
98
|
pollCount?: number;
|
|
@@ -161,5 +183,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
|
|
|
161
183
|
* (row = headerHeight + 2 + i for 0-indexed session i)
|
|
162
184
|
*/
|
|
163
185
|
export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
|
|
164
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN };
|
|
186
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON };
|
|
165
187
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -26,8 +26,8 @@ const STATUS_PRIORITY = {
|
|
|
26
26
|
error: 0, waiting: 1, working: 2, running: 2,
|
|
27
27
|
idle: 3, done: 4, stopped: 5, unknown: 6,
|
|
28
28
|
};
|
|
29
|
-
/** Sort sessions by mode. Returns a new array (never mutates). */
|
|
30
|
-
function sortSessions(sessions, mode, lastChangeAt) {
|
|
29
|
+
/** Sort sessions by mode. Pinned sessions always sort first (stable). Returns a new array (never mutates). */
|
|
30
|
+
function sortSessions(sessions, mode, lastChangeAt, pinnedIds) {
|
|
31
31
|
const copy = sessions.slice();
|
|
32
32
|
switch (mode) {
|
|
33
33
|
case "status":
|
|
@@ -41,6 +41,10 @@ function sortSessions(sessions, mode, lastChangeAt) {
|
|
|
41
41
|
break;
|
|
42
42
|
// "default" — preserve original order
|
|
43
43
|
}
|
|
44
|
+
// stable-sort pinned to top (preserves mode order within each group)
|
|
45
|
+
if (pinnedIds && pinnedIds.size > 0) {
|
|
46
|
+
copy.sort((a, b) => (pinnedIds.has(a.id) ? 0 : 1) - (pinnedIds.has(b.id) ? 0 : 1));
|
|
47
|
+
}
|
|
44
48
|
return copy;
|
|
45
49
|
}
|
|
46
50
|
/** Cycle to the next sort mode. */
|
|
@@ -48,15 +52,28 @@ function nextSortMode(current) {
|
|
|
48
52
|
const idx = SORT_MODES.indexOf(current);
|
|
49
53
|
return SORT_MODES[(idx + 1) % SORT_MODES.length];
|
|
50
54
|
}
|
|
55
|
+
// ── Bell notifications ──────────────────────────────────────────────────────
|
|
56
|
+
/** Cooldown between terminal bells to avoid buzzing. */
|
|
57
|
+
export const BELL_COOLDOWN_MS = 5000;
|
|
58
|
+
/** Determine if an activity entry should trigger a terminal bell. High-signal events only. */
|
|
59
|
+
export function shouldBell(tag, text) {
|
|
60
|
+
if (tag === "! action" || tag === "error")
|
|
61
|
+
return true;
|
|
62
|
+
if (tag === "+ action" && text.toLowerCase().includes("complete"))
|
|
63
|
+
return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
51
66
|
// ── Compact mode ────────────────────────────────────────────────────────────
|
|
52
67
|
/** Max name length in compact token. */
|
|
53
68
|
const COMPACT_NAME_LEN = 10;
|
|
69
|
+
/** Pin indicator for pinned sessions. */
|
|
70
|
+
const PIN_ICON = "▲";
|
|
54
71
|
/**
|
|
55
72
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
56
|
-
* Each token: "{idx}{dot}{name}" — e.g. "1
|
|
73
|
+
* Each token: "{idx}{pin?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2●Bravo" for unpinned.
|
|
57
74
|
* Returns array of formatted row strings (one per display row).
|
|
58
75
|
*/
|
|
59
|
-
function formatCompactRows(sessions, maxWidth) {
|
|
76
|
+
function formatCompactRows(sessions, maxWidth, pinnedIds) {
|
|
60
77
|
if (sessions.length === 0)
|
|
61
78
|
return [`${DIM}no agents connected${RESET}`];
|
|
62
79
|
const tokens = [];
|
|
@@ -65,9 +82,11 @@ function formatCompactRows(sessions, maxWidth) {
|
|
|
65
82
|
const s = sessions[i];
|
|
66
83
|
const idx = String(i + 1);
|
|
67
84
|
const dot = STATUS_DOT[s.status] ?? `${AMBER}${DOT.filled}${RESET}`;
|
|
85
|
+
const pinned = pinnedIds?.has(s.id) ?? false;
|
|
86
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET}` : "";
|
|
68
87
|
const name = truncatePlain(s.title, COMPACT_NAME_LEN);
|
|
69
|
-
tokens.push(`${SLATE}${idx}${RESET}${dot}${BOLD}${name}${RESET}`);
|
|
70
|
-
widths.push(idx.length + 1
|
|
88
|
+
tokens.push(`${SLATE}${idx}${RESET}${pin}${dot}${BOLD}${name}${RESET}`);
|
|
89
|
+
widths.push(idx.length + (pinned ? 1 : 0) + 1 + name.length);
|
|
71
90
|
}
|
|
72
91
|
const rows = [];
|
|
73
92
|
let currentRow = "";
|
|
@@ -141,6 +160,9 @@ export class TUI {
|
|
|
141
160
|
lastChangeAt = new Map(); // session ID → epoch ms of last activity change
|
|
142
161
|
prevLastActivity = new Map(); // session ID → previous lastActivity string
|
|
143
162
|
compactMode = false;
|
|
163
|
+
pinnedIds = new Set(); // pinned session IDs (always sort to top)
|
|
164
|
+
bellEnabled = false;
|
|
165
|
+
lastBellAt = 0;
|
|
144
166
|
// drill-down mode: show a single session's full output
|
|
145
167
|
viewMode = "overview";
|
|
146
168
|
drilldownSessionId = null;
|
|
@@ -203,7 +225,7 @@ export class TUI {
|
|
|
203
225
|
return;
|
|
204
226
|
this.sortMode = mode;
|
|
205
227
|
// re-sort current sessions and repaint
|
|
206
|
-
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt);
|
|
228
|
+
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
207
229
|
if (this.active) {
|
|
208
230
|
this.paintSessions();
|
|
209
231
|
}
|
|
@@ -226,6 +248,51 @@ export class TUI {
|
|
|
226
248
|
isCompact() {
|
|
227
249
|
return this.compactMode;
|
|
228
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Toggle pin for a session (by 1-indexed number, ID, ID prefix, or title).
|
|
253
|
+
* Pinned sessions always sort to the top. Returns true if session found.
|
|
254
|
+
*/
|
|
255
|
+
togglePin(sessionIdOrIndex) {
|
|
256
|
+
let sessionId;
|
|
257
|
+
if (typeof sessionIdOrIndex === "number") {
|
|
258
|
+
sessionId = this.sessions[sessionIdOrIndex - 1]?.id;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const needle = sessionIdOrIndex.toLowerCase();
|
|
262
|
+
const match = this.sessions.find((s) => s.id === sessionIdOrIndex || s.id.startsWith(needle) || s.title.toLowerCase() === needle);
|
|
263
|
+
sessionId = match?.id;
|
|
264
|
+
}
|
|
265
|
+
if (!sessionId)
|
|
266
|
+
return false;
|
|
267
|
+
if (this.pinnedIds.has(sessionId)) {
|
|
268
|
+
this.pinnedIds.delete(sessionId);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.pinnedIds.add(sessionId);
|
|
272
|
+
}
|
|
273
|
+
// re-sort and repaint
|
|
274
|
+
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
275
|
+
if (this.active) {
|
|
276
|
+
this.paintSessions();
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
/** Check if a session ID is pinned. */
|
|
281
|
+
isPinned(id) {
|
|
282
|
+
return this.pinnedIds.has(id);
|
|
283
|
+
}
|
|
284
|
+
/** Return count of pinned sessions. */
|
|
285
|
+
getPinnedCount() {
|
|
286
|
+
return this.pinnedIds.size;
|
|
287
|
+
}
|
|
288
|
+
/** Enable or disable terminal bell notifications. */
|
|
289
|
+
setBell(enabled) {
|
|
290
|
+
this.bellEnabled = enabled;
|
|
291
|
+
}
|
|
292
|
+
/** Return whether terminal bell is enabled. */
|
|
293
|
+
isBellEnabled() {
|
|
294
|
+
return this.bellEnabled;
|
|
295
|
+
}
|
|
229
296
|
// ── State updates ───────────────────────────────────────────────────────
|
|
230
297
|
updateState(opts) {
|
|
231
298
|
if (opts.phase !== undefined)
|
|
@@ -251,7 +318,7 @@ export class TUI {
|
|
|
251
318
|
if (s.lastActivity !== undefined)
|
|
252
319
|
this.prevLastActivity.set(s.id, s.lastActivity);
|
|
253
320
|
}
|
|
254
|
-
const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt);
|
|
321
|
+
const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
255
322
|
const sessionCountChanged = sorted.length !== this.sessions.length;
|
|
256
323
|
this.sessions = sorted;
|
|
257
324
|
if (sessionCountChanged) {
|
|
@@ -279,6 +346,14 @@ export class TUI {
|
|
|
279
346
|
this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
|
|
280
347
|
this.activityTimestamps = this.activityTimestamps.slice(-this.maxActivity);
|
|
281
348
|
}
|
|
349
|
+
// terminal bell for high-signal events (with cooldown)
|
|
350
|
+
if (this.bellEnabled && shouldBell(tag, text)) {
|
|
351
|
+
const nowMs = now.getTime();
|
|
352
|
+
if (nowMs - this.lastBellAt >= BELL_COOLDOWN_MS) {
|
|
353
|
+
this.lastBellAt = nowMs;
|
|
354
|
+
process.stderr.write("\x07");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
282
357
|
if (this.active) {
|
|
283
358
|
if (this.searchPattern) {
|
|
284
359
|
// search active: only show new entry if it matches
|
|
@@ -596,8 +671,8 @@ export class TUI {
|
|
|
596
671
|
process.stderr.write(moveTo(startRow + 1, 1) + CLEAR_LINE + padded);
|
|
597
672
|
}
|
|
598
673
|
else if (this.compactMode) {
|
|
599
|
-
// compact: inline tokens, multiple per row
|
|
600
|
-
const compactRows = formatCompactRows(this.sessions, innerWidth - 1);
|
|
674
|
+
// compact: inline tokens, multiple per row (with pin indicators)
|
|
675
|
+
const compactRows = formatCompactRows(this.sessions, innerWidth - 1, this.pinnedIds);
|
|
601
676
|
for (let r = 0; r < compactRows.length; r++) {
|
|
602
677
|
const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
|
|
603
678
|
const padded = padBoxLine(line, this.cols);
|
|
@@ -609,7 +684,10 @@ export class TUI {
|
|
|
609
684
|
const s = this.sessions[i];
|
|
610
685
|
const isHovered = this.hoverSessionIdx === i + 1; // 1-indexed
|
|
611
686
|
const bg = isHovered ? BG_HOVER : "";
|
|
612
|
-
const
|
|
687
|
+
const pinned = this.pinnedIds.has(s.id);
|
|
688
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
689
|
+
const cardWidth = pinned ? innerWidth - 3 : innerWidth - 1; // pin takes 2 extra chars
|
|
690
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${formatSessionCard(s, cardWidth)}`;
|
|
613
691
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
614
692
|
process.stderr.write(moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded);
|
|
615
693
|
}
|
|
@@ -639,7 +717,10 @@ export class TUI {
|
|
|
639
717
|
const s = this.sessions[i];
|
|
640
718
|
const isHovered = this.hoverSessionIdx === idx;
|
|
641
719
|
const bg = isHovered ? BG_HOVER : "";
|
|
642
|
-
const
|
|
720
|
+
const pinned = this.pinnedIds.has(s.id);
|
|
721
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
722
|
+
const cardWidth = pinned ? innerWidth - 3 : innerWidth - 1;
|
|
723
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${formatSessionCard(s, cardWidth)}`;
|
|
643
724
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
644
725
|
process.stderr.write(SAVE_CURSOR + moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded + RESTORE_CURSOR);
|
|
645
726
|
}
|
|
@@ -1006,5 +1087,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
|
|
|
1006
1087
|
return row - firstSessionRow + 1; // 1-indexed
|
|
1007
1088
|
}
|
|
1008
1089
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
1009
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN };
|
|
1090
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON };
|
|
1010
1091
|
//# sourceMappingURL=tui.js.map
|