aoaoe 0.85.0 → 0.87.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 +20 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.js +30 -0
- package/dist/tui.d.ts +16 -0
- package/dist/tui.js +88 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -444,6 +444,26 @@ async function main() {
|
|
|
444
444
|
tui.log("system", `session not found: ${target}`);
|
|
445
445
|
}
|
|
446
446
|
});
|
|
447
|
+
// wire /unmute-all
|
|
448
|
+
input.onUnmuteAll(() => {
|
|
449
|
+
const count = tui.unmuteAll();
|
|
450
|
+
if (count > 0) {
|
|
451
|
+
tui.log("system", `unmuted ${count} session${count === 1 ? "" : "s"}`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
tui.log("system", "no sessions are muted");
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
// wire /filter tag
|
|
458
|
+
input.onTagFilter((tag) => {
|
|
459
|
+
tui.setTagFilter(tag);
|
|
460
|
+
if (tag) {
|
|
461
|
+
tui.log("system", `filter: ${tag}`);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
tui.log("system", "filter cleared");
|
|
465
|
+
}
|
|
466
|
+
});
|
|
447
467
|
// wire /note set/clear
|
|
448
468
|
input.onNote((target, text) => {
|
|
449
469
|
const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
|
package/dist/input.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type MarkHandler = () => void;
|
|
|
12
12
|
export type JumpHandler = (num: number) => void;
|
|
13
13
|
export type MarksHandler = () => void;
|
|
14
14
|
export type MuteHandler = (target: string) => void;
|
|
15
|
+
export type UnmuteAllHandler = () => void;
|
|
16
|
+
export type TagFilterHandler = (tag: string | null) => void;
|
|
15
17
|
export type NoteHandler = (target: string, text: string) => void;
|
|
16
18
|
export type NotesHandler = () => void;
|
|
17
19
|
export interface MouseEvent {
|
|
@@ -48,6 +50,8 @@ export declare class InputReader {
|
|
|
48
50
|
private jumpHandler;
|
|
49
51
|
private marksHandler;
|
|
50
52
|
private muteHandler;
|
|
53
|
+
private unmuteAllHandler;
|
|
54
|
+
private tagFilterHandler;
|
|
51
55
|
private noteHandler;
|
|
52
56
|
private notesHandler;
|
|
53
57
|
private mouseDataListener;
|
|
@@ -68,6 +72,8 @@ export declare class InputReader {
|
|
|
68
72
|
onJump(handler: JumpHandler): void;
|
|
69
73
|
onMarks(handler: MarksHandler): void;
|
|
70
74
|
onMute(handler: MuteHandler): void;
|
|
75
|
+
onUnmuteAll(handler: UnmuteAllHandler): void;
|
|
76
|
+
onTagFilter(handler: TagFilterHandler): void;
|
|
71
77
|
onNote(handler: NoteHandler): void;
|
|
72
78
|
onNotes(handler: NotesHandler): void;
|
|
73
79
|
private notifyQueueChange;
|
package/dist/input.js
CHANGED
|
@@ -44,6 +44,8 @@ export class InputReader {
|
|
|
44
44
|
jumpHandler = null;
|
|
45
45
|
marksHandler = null;
|
|
46
46
|
muteHandler = null;
|
|
47
|
+
unmuteAllHandler = null;
|
|
48
|
+
tagFilterHandler = null;
|
|
47
49
|
noteHandler = null;
|
|
48
50
|
notesHandler = null;
|
|
49
51
|
mouseDataListener = null;
|
|
@@ -115,6 +117,14 @@ export class InputReader {
|
|
|
115
117
|
onMute(handler) {
|
|
116
118
|
this.muteHandler = handler;
|
|
117
119
|
}
|
|
120
|
+
// register a callback for unmuting all sessions (/unmute-all)
|
|
121
|
+
onUnmuteAll(handler) {
|
|
122
|
+
this.unmuteAllHandler = handler;
|
|
123
|
+
}
|
|
124
|
+
// register a callback for tag filter commands (/filter <tag>)
|
|
125
|
+
onTagFilter(handler) {
|
|
126
|
+
this.tagFilterHandler = handler;
|
|
127
|
+
}
|
|
118
128
|
// register a callback for note commands (/note <target> <text>)
|
|
119
129
|
onNote(handler) {
|
|
120
130
|
this.noteHandler = handler;
|
|
@@ -306,6 +316,8 @@ ${BOLD}navigation:${RESET}
|
|
|
306
316
|
/bell toggle terminal bell on errors/completions
|
|
307
317
|
/focus toggle focus mode (show only pinned sessions)
|
|
308
318
|
/mute [N|name] mute/unmute a session's activity entries (toggle)
|
|
319
|
+
/unmute-all unmute all sessions at once
|
|
320
|
+
/filter [tag] filter activity by tag (error, system, etc. — no arg = clear)
|
|
309
321
|
/note N|name text attach a note to a session (no text = clear)
|
|
310
322
|
/notes list all session notes
|
|
311
323
|
/mark bookmark current activity position
|
|
@@ -454,6 +466,24 @@ ${BOLD}other:${RESET}
|
|
|
454
466
|
}
|
|
455
467
|
break;
|
|
456
468
|
}
|
|
469
|
+
case "/unmute-all":
|
|
470
|
+
if (this.unmuteAllHandler) {
|
|
471
|
+
this.unmuteAllHandler();
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
console.error(`${DIM}unmute-all not available (no TUI)${RESET}`);
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
477
|
+
case "/filter": {
|
|
478
|
+
const filterArg = line.slice("/filter".length).trim();
|
|
479
|
+
if (this.tagFilterHandler) {
|
|
480
|
+
this.tagFilterHandler(filterArg || null);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
console.error(`${DIM}filter not available (no TUI)${RESET}`);
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
457
487
|
case "/note": {
|
|
458
488
|
const noteArg = line.slice("/note".length).trim();
|
|
459
489
|
if (this.noteHandler) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export declare const MAX_NOTE_LEN = 80;
|
|
|
51
51
|
export declare function truncateNote(text: string): string;
|
|
52
52
|
/** Determine if an activity entry should be hidden due to muting. */
|
|
53
53
|
export declare function shouldMuteEntry(entry: ActivityEntry, mutedIds: Set<string>): boolean;
|
|
54
|
+
/** Format a suppressed entry count badge for muted sessions. Returns empty string for 0. */
|
|
55
|
+
export declare function formatMuteBadge(count: number): string;
|
|
56
|
+
/** Check if an activity entry matches a tag filter (case-insensitive exact match on tag). */
|
|
57
|
+
export declare function matchesTagFilter(entry: ActivityEntry, tag: string): boolean;
|
|
58
|
+
/** Format the tag filter indicator text for the separator bar. */
|
|
59
|
+
export declare function formatTagFilterIndicator(tag: string, matchCount: number, totalCount: number): string;
|
|
54
60
|
export declare class TUI {
|
|
55
61
|
private active;
|
|
56
62
|
private countdownTimer;
|
|
@@ -69,6 +75,7 @@ export declare class TUI {
|
|
|
69
75
|
private newWhileScrolled;
|
|
70
76
|
private pendingCount;
|
|
71
77
|
private searchPattern;
|
|
78
|
+
private filterTag;
|
|
72
79
|
private hoverSessionIdx;
|
|
73
80
|
private activityTimestamps;
|
|
74
81
|
private sortMode;
|
|
@@ -81,6 +88,7 @@ export declare class TUI {
|
|
|
81
88
|
private bellEnabled;
|
|
82
89
|
private lastBellAt;
|
|
83
90
|
private mutedIds;
|
|
91
|
+
private mutedEntryCounts;
|
|
84
92
|
private sessionNotes;
|
|
85
93
|
private viewMode;
|
|
86
94
|
private drilldownSessionId;
|
|
@@ -132,10 +140,14 @@ export declare class TUI {
|
|
|
132
140
|
* Returns true if session found.
|
|
133
141
|
*/
|
|
134
142
|
toggleMute(sessionIdOrIndex: string | number): boolean;
|
|
143
|
+
/** Unmute all sessions at once. Returns count of sessions unmuted. */
|
|
144
|
+
unmuteAll(): number;
|
|
135
145
|
/** Check if a session ID is muted. */
|
|
136
146
|
isMuted(id: string): boolean;
|
|
137
147
|
/** Return count of muted sessions. */
|
|
138
148
|
getMutedCount(): number;
|
|
149
|
+
/** Return count of suppressed entries for a muted session (0 if not muted). */
|
|
150
|
+
getMutedEntryCount(id: string): number;
|
|
139
151
|
/**
|
|
140
152
|
* Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
|
|
141
153
|
* Returns true if session found. Pass empty text to clear.
|
|
@@ -197,6 +209,10 @@ export declare class TUI {
|
|
|
197
209
|
setSearch(pattern: string | null): void;
|
|
198
210
|
/** Get the current search pattern (or null if no active search). */
|
|
199
211
|
getSearchPattern(): string | null;
|
|
212
|
+
/** Set or clear the tag filter. Resets scroll and repaints. */
|
|
213
|
+
setTagFilter(tag: string | null): void;
|
|
214
|
+
/** Get the current tag filter (or null if none active). */
|
|
215
|
+
getTagFilter(): string | null;
|
|
200
216
|
/** Set the hovered session index (1-indexed) or null to clear. Only repaints the affected cards. */
|
|
201
217
|
setHoverSession(idx: number | null): void;
|
|
202
218
|
/** Get the current hovered session index (1-indexed, null if none). */
|
package/dist/tui.js
CHANGED
|
@@ -174,6 +174,23 @@ export function shouldMuteEntry(entry, mutedIds) {
|
|
|
174
174
|
return false;
|
|
175
175
|
return mutedIds.has(entry.sessionId);
|
|
176
176
|
}
|
|
177
|
+
/** Format a suppressed entry count badge for muted sessions. Returns empty string for 0. */
|
|
178
|
+
export function formatMuteBadge(count) {
|
|
179
|
+
if (count <= 0)
|
|
180
|
+
return "";
|
|
181
|
+
const label = count > 999 ? "999+" : String(count);
|
|
182
|
+
return `${DIM}(${label})${RESET}`;
|
|
183
|
+
}
|
|
184
|
+
/** Check if an activity entry matches a tag filter (case-insensitive exact match on tag). */
|
|
185
|
+
export function matchesTagFilter(entry, tag) {
|
|
186
|
+
if (!tag)
|
|
187
|
+
return true;
|
|
188
|
+
return entry.tag.toLowerCase() === tag.toLowerCase();
|
|
189
|
+
}
|
|
190
|
+
/** Format the tag filter indicator text for the separator bar. */
|
|
191
|
+
export function formatTagFilterIndicator(tag, matchCount, totalCount) {
|
|
192
|
+
return `${SLATE}filter:${RESET} ${AMBER}${tag}${RESET} ${DIM}(${matchCount}/${totalCount})${RESET}`;
|
|
193
|
+
}
|
|
177
194
|
// ── TUI class ───────────────────────────────────────────────────────────────
|
|
178
195
|
export class TUI {
|
|
179
196
|
active = false;
|
|
@@ -193,6 +210,7 @@ export class TUI {
|
|
|
193
210
|
newWhileScrolled = 0; // entries added while user is scrolled back
|
|
194
211
|
pendingCount = 0; // queued user messages awaiting next tick
|
|
195
212
|
searchPattern = null; // active search filter pattern
|
|
213
|
+
filterTag = null; // active tag filter (exact match on entry.tag)
|
|
196
214
|
hoverSessionIdx = null; // 1-indexed session under mouse cursor (null = none)
|
|
197
215
|
activityTimestamps = []; // epoch ms of each log() call for sparkline
|
|
198
216
|
sortMode = "default";
|
|
@@ -205,6 +223,7 @@ export class TUI {
|
|
|
205
223
|
bellEnabled = false;
|
|
206
224
|
lastBellAt = 0;
|
|
207
225
|
mutedIds = new Set(); // muted session IDs (activity entries hidden)
|
|
226
|
+
mutedEntryCounts = new Map(); // session ID → suppressed entry count since mute
|
|
208
227
|
sessionNotes = new Map(); // session ID → note text
|
|
209
228
|
// drill-down mode: show a single session's full output
|
|
210
229
|
viewMode = "overview";
|
|
@@ -380,9 +399,11 @@ export class TUI {
|
|
|
380
399
|
return false;
|
|
381
400
|
if (this.mutedIds.has(sessionId)) {
|
|
382
401
|
this.mutedIds.delete(sessionId);
|
|
402
|
+
this.mutedEntryCounts.delete(sessionId);
|
|
383
403
|
}
|
|
384
404
|
else {
|
|
385
405
|
this.mutedIds.add(sessionId);
|
|
406
|
+
this.mutedEntryCounts.set(sessionId, 0);
|
|
386
407
|
}
|
|
387
408
|
// repaint sessions (mute icon) and activity (filter changes)
|
|
388
409
|
if (this.active) {
|
|
@@ -391,6 +412,19 @@ export class TUI {
|
|
|
391
412
|
}
|
|
392
413
|
return true;
|
|
393
414
|
}
|
|
415
|
+
/** Unmute all sessions at once. Returns count of sessions unmuted. */
|
|
416
|
+
unmuteAll() {
|
|
417
|
+
const count = this.mutedIds.size;
|
|
418
|
+
if (count === 0)
|
|
419
|
+
return 0;
|
|
420
|
+
this.mutedIds.clear();
|
|
421
|
+
this.mutedEntryCounts.clear();
|
|
422
|
+
if (this.active) {
|
|
423
|
+
this.paintSessions();
|
|
424
|
+
this.repaintActivityRegion();
|
|
425
|
+
}
|
|
426
|
+
return count;
|
|
427
|
+
}
|
|
394
428
|
/** Check if a session ID is muted. */
|
|
395
429
|
isMuted(id) {
|
|
396
430
|
return this.mutedIds.has(id);
|
|
@@ -399,6 +433,10 @@ export class TUI {
|
|
|
399
433
|
getMutedCount() {
|
|
400
434
|
return this.mutedIds.size;
|
|
401
435
|
}
|
|
436
|
+
/** Return count of suppressed entries for a muted session (0 if not muted). */
|
|
437
|
+
getMutedEntryCount(id) {
|
|
438
|
+
return this.mutedEntryCounts.get(id) ?? 0;
|
|
439
|
+
}
|
|
402
440
|
/**
|
|
403
441
|
* Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
|
|
404
442
|
* Returns true if session found. Pass empty text to clear.
|
|
@@ -557,11 +595,18 @@ export class TUI {
|
|
|
557
595
|
process.stderr.write("\x07");
|
|
558
596
|
}
|
|
559
597
|
}
|
|
598
|
+
// track suppressed entry counts regardless of active state (for badge accuracy)
|
|
599
|
+
if (shouldMuteEntry(entry, this.mutedIds) && entry.sessionId) {
|
|
600
|
+
this.mutedEntryCounts.set(entry.sessionId, (this.mutedEntryCounts.get(entry.sessionId) ?? 0) + 1);
|
|
601
|
+
}
|
|
560
602
|
if (this.active) {
|
|
561
603
|
// muted entries are still buffered + persisted but hidden from display
|
|
562
604
|
if (shouldMuteEntry(entry, this.mutedIds)) {
|
|
563
605
|
// silently skip display — entry is in buffer for scroll-back if unmuted later
|
|
564
606
|
}
|
|
607
|
+
else if (this.filterTag && !matchesTagFilter(entry, this.filterTag)) {
|
|
608
|
+
// tag filter active: silently skip non-matching entries
|
|
609
|
+
}
|
|
565
610
|
else if (this.searchPattern) {
|
|
566
611
|
// search active: only show new entry if it matches
|
|
567
612
|
if (matchesSearch(entry, this.searchPattern)) {
|
|
@@ -607,6 +652,8 @@ export class TUI {
|
|
|
607
652
|
let filtered = this.mutedIds.size > 0
|
|
608
653
|
? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
|
|
609
654
|
: this.activityBuffer;
|
|
655
|
+
if (this.filterTag)
|
|
656
|
+
filtered = filtered.filter((e) => matchesTagFilter(e, this.filterTag));
|
|
610
657
|
const entryCount = this.searchPattern
|
|
611
658
|
? filtered.filter((e) => matchesSearch(e, this.searchPattern)).length
|
|
612
659
|
: filtered.length;
|
|
@@ -634,6 +681,8 @@ export class TUI {
|
|
|
634
681
|
let filtered = this.mutedIds.size > 0
|
|
635
682
|
? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
|
|
636
683
|
: this.activityBuffer;
|
|
684
|
+
if (this.filterTag)
|
|
685
|
+
filtered = filtered.filter((e) => matchesTagFilter(e, this.filterTag));
|
|
637
686
|
const entryCount = this.searchPattern
|
|
638
687
|
? filtered.filter((e) => matchesSearch(e, this.searchPattern)).length
|
|
639
688
|
: filtered.length;
|
|
@@ -771,6 +820,21 @@ export class TUI {
|
|
|
771
820
|
getSearchPattern() {
|
|
772
821
|
return this.searchPattern;
|
|
773
822
|
}
|
|
823
|
+
// ── Tag filter ─────────────────────────────────────────────────────────
|
|
824
|
+
/** Set or clear the tag filter. Resets scroll and repaints. */
|
|
825
|
+
setTagFilter(tag) {
|
|
826
|
+
this.filterTag = tag && tag.length > 0 ? tag : null;
|
|
827
|
+
this.scrollOffset = 0;
|
|
828
|
+
this.newWhileScrolled = 0;
|
|
829
|
+
if (this.active && this.viewMode === "overview") {
|
|
830
|
+
this.repaintActivityRegion();
|
|
831
|
+
this.paintSeparator();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/** Get the current tag filter (or null if none active). */
|
|
835
|
+
getTagFilter() {
|
|
836
|
+
return this.filterTag;
|
|
837
|
+
}
|
|
774
838
|
// ── Hover ───────────────────────────────────────────────────────────────
|
|
775
839
|
/** Set the hovered session index (1-indexed) or null to clear. Only repaints the affected cards. */
|
|
776
840
|
setHoverSession(idx) {
|
|
@@ -911,12 +975,16 @@ export class TUI {
|
|
|
911
975
|
const pinned = this.pinnedIds.has(s.id);
|
|
912
976
|
const muted = this.mutedIds.has(s.id);
|
|
913
977
|
const noted = this.sessionNotes.has(s.id);
|
|
978
|
+
const muteBadge = muted ? formatMuteBadge(this.mutedEntryCounts.get(s.id) ?? 0) : "";
|
|
979
|
+
const muteBadgeWidth = muted ? String(Math.min(this.mutedEntryCounts.get(s.id) ?? 0, 9999)).length + 2 : 0; // "(N)" visible chars, 0 when count is 0
|
|
980
|
+
const actualBadgeWidth = (this.mutedEntryCounts.get(s.id) ?? 0) > 0 ? muteBadgeWidth + 1 : 0; // +1 for trailing space
|
|
914
981
|
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
915
982
|
const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
|
|
916
983
|
const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
|
|
917
|
-
const
|
|
984
|
+
const badgeSuffix = muteBadge ? `${muteBadge} ` : "";
|
|
985
|
+
const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0) + actualBadgeWidth;
|
|
918
986
|
const cardWidth = innerWidth - 1 - iconsWidth;
|
|
919
|
-
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${note}${formatSessionCard(s, cardWidth)}`;
|
|
987
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${badgeSuffix}${note}${formatSessionCard(s, cardWidth)}`;
|
|
920
988
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
921
989
|
process.stderr.write(moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded);
|
|
922
990
|
}
|
|
@@ -949,19 +1017,31 @@ export class TUI {
|
|
|
949
1017
|
const pinned = this.pinnedIds.has(s.id);
|
|
950
1018
|
const muted = this.mutedIds.has(s.id);
|
|
951
1019
|
const noted = this.sessionNotes.has(s.id);
|
|
1020
|
+
const muteBadge = muted ? formatMuteBadge(this.mutedEntryCounts.get(s.id) ?? 0) : "";
|
|
1021
|
+
const actualBadgeWidth = (this.mutedEntryCounts.get(s.id) ?? 0) > 0
|
|
1022
|
+
? String(Math.min(this.mutedEntryCounts.get(s.id) ?? 0, 9999)).length + 3 : 0; // "(N) " visible chars
|
|
952
1023
|
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
953
1024
|
const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
|
|
954
1025
|
const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
|
|
955
|
-
const
|
|
1026
|
+
const badgeSuffix = muteBadge ? `${muteBadge} ` : "";
|
|
1027
|
+
const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0) + actualBadgeWidth;
|
|
956
1028
|
const cardWidth = innerWidth - 1 - iconsWidth;
|
|
957
|
-
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${note}${formatSessionCard(s, cardWidth)}`;
|
|
1029
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${badgeSuffix}${note}${formatSessionCard(s, cardWidth)}`;
|
|
958
1030
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
959
1031
|
process.stderr.write(SAVE_CURSOR + moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded + RESTORE_CURSOR);
|
|
960
1032
|
}
|
|
961
1033
|
paintSeparator() {
|
|
962
1034
|
const prefix = `${BOX.h}${BOX.h} activity `;
|
|
963
1035
|
let hints;
|
|
964
|
-
if (this.
|
|
1036
|
+
if (this.filterTag) {
|
|
1037
|
+
// tag filter takes precedence in the separator display
|
|
1038
|
+
let source = this.mutedIds.size > 0
|
|
1039
|
+
? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
|
|
1040
|
+
: this.activityBuffer;
|
|
1041
|
+
const matchCount = source.filter((e) => matchesTagFilter(e, this.filterTag)).length;
|
|
1042
|
+
hints = formatTagFilterIndicator(this.filterTag, matchCount, source.length);
|
|
1043
|
+
}
|
|
1044
|
+
else if (this.searchPattern) {
|
|
965
1045
|
const filtered = this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern));
|
|
966
1046
|
hints = formatSearchIndicator(this.searchPattern, filtered.length, this.activityBuffer.length);
|
|
967
1047
|
}
|
|
@@ -991,10 +1071,12 @@ export class TUI {
|
|
|
991
1071
|
}
|
|
992
1072
|
repaintActivityRegion() {
|
|
993
1073
|
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
994
|
-
// filter: muted
|
|
1074
|
+
// filter pipeline: muted → tag → search
|
|
995
1075
|
let source = this.mutedIds.size > 0
|
|
996
1076
|
? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
|
|
997
1077
|
: this.activityBuffer;
|
|
1078
|
+
if (this.filterTag)
|
|
1079
|
+
source = source.filter((e) => matchesTagFilter(e, this.filterTag));
|
|
998
1080
|
if (this.searchPattern) {
|
|
999
1081
|
source = source.filter((e) => matchesSearch(e, this.searchPattern));
|
|
1000
1082
|
}
|