* Add session activity indicator and store Introduce a SessionActivityStore (useSyncExternalStore) to track which tabs/workspaces have unread terminal activity. TerminalLayer now strips terminal control sequences, listens for session data, and marks tabs as active when not focused; it also clears activity on focus change and prunes stale IDs. TopTabs consumes the activity map to render a breathing activity dot on session/workspace tabs and adjusts the workspace tab layout to show the dot next to the pane count. Add CSS animation for the activity indicator. * fix: buffer incomplete escape sequences across data chunks Add ChunkedEscapeFilter to carry partial ANSI/OSC escape-sequence tails between successive data chunks, preventing false-positive activity badges from split control sequences on busy sessions. Also fix missing trailing newline in sessionActivity.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove 256-byte cap on pending escape sequence tails Long OSC sequences (e.g. clipboard/title payloads) can exceed 256 bytes. Removing the cap ensures they are fully buffered across chunks instead of being misclassified as printable output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: buffer OSC tails that end on bare ESC awaiting backslash OSC sequences terminated with ESC\ can split at the ESC boundary. Extend the incomplete tail regex to also match an in-progress OSC sequence ending with ESC (awaiting the closing backslash). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: bincxz <16399091+binaricat@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
47 lines
1.3 KiB
TypeScript
47 lines
1.3 KiB
TypeScript
import { TerminalSession } from '../../types';
|
|
|
|
type SessionActivityMap = Record<string, boolean>;
|
|
|
|
export const getValidSessionActivityIds = (sessions: TerminalSession[]): Set<string> => {
|
|
return new Set(sessions.map((session) => session.id));
|
|
};
|
|
|
|
export const shouldMarkSessionActivity = (
|
|
activeTabId: string | null,
|
|
session: Pick<TerminalSession, 'id' | 'workspaceId'>,
|
|
): boolean => {
|
|
return activeTabId !== session.id && activeTabId !== session.workspaceId;
|
|
};
|
|
|
|
export const getSessionActivityIdsToClear = (
|
|
activeTabId: string | null,
|
|
sessions: TerminalSession[],
|
|
): string[] => {
|
|
if (!activeTabId || activeTabId === 'vault' || activeTabId === 'sftp') {
|
|
return [];
|
|
}
|
|
|
|
const activeSession = sessions.find((session) => session.id === activeTabId);
|
|
if (activeSession) {
|
|
return [activeSession.id];
|
|
}
|
|
|
|
return sessions
|
|
.filter((session) => session.workspaceId === activeTabId)
|
|
.map((session) => session.id);
|
|
};
|
|
|
|
export const buildWorkspaceActivityMap = (
|
|
sessions: TerminalSession[],
|
|
sessionActivityMap: SessionActivityMap,
|
|
): Map<string, boolean> => {
|
|
const workspaceActivityMap = new Map<string, boolean>();
|
|
|
|
for (const session of sessions) {
|
|
if (!session.workspaceId || !sessionActivityMap[session.id]) continue;
|
|
workspaceActivityMap.set(session.workspaceId, true);
|
|
}
|
|
|
|
return workspaceActivityMap;
|
|
};
|