/** * Tab type definitions for the tabbed layout feature. * Based on specs/001-tabbed-layout-dashboard/contracts/tab-state.ts */ import type { Session } from './data'; import type { TriggerColor } from '@shared/constants/triggerColors'; // ============================================================================= // Navigation Request Types // ============================================================================= /** * Payload for error-based navigation (from notifications or trigger preview). */ export interface ErrorNavigationPayload { /** Error ID for tracking */ errorId: string; /** Error timestamp for finding the correct AI group */ errorTimestamp: number; /** Tool use ID for precise tool item highlighting */ toolUseId?: string; /** Subagent ID for subagent-aware group lookup */ subagentId?: string; /** Line number (fallback) */ lineNumber?: number; } /** * Payload for search-based navigation (from Command Palette). */ export interface SearchNavigationPayload { /** The search query */ query: string; /** Timestamp of the message containing the search match */ messageTimestamp: number; /** The matched text */ matchedText: string; /** Optional exact target group ID (e.g., "user-..." or "ai-...") */ targetGroupId?: string; /** Optional exact match index within the target group's searchable text */ targetMatchIndexInItem?: number; /** Optional character offset of the match in the searchable text */ targetMatchStartOffset?: number; /** Optional source message UUID for diagnostics/fallback mapping */ targetMessageUuid?: string; } /** * Unified tab navigation request. * Each click/action creates a new request with a unique nonce (id). * The nonce ensures repeated clicks produce new navigations. */ export interface TabNavigationRequest { /** Unique nonce per click/action (crypto.randomUUID) */ id: string; /** Kind of navigation */ kind: 'error' | 'search' | 'autoBottom'; /** Source of the navigation action */ source: 'notification' | 'triggerPreview' | 'commandPalette' | 'sessionOpen'; /** Highlight color to use */ highlight: TriggerColor | 'yellow' | 'none'; /** Navigation payload (depends on kind) */ payload: ErrorNavigationPayload | SearchNavigationPayload | Record; } // ============================================================================= // Core Types // ============================================================================= /** * Represents a single open tab in the main content area */ export interface Tab { /** Unique identifier (UUID v4) */ id: string; /** Type of content displayed in this tab */ type: 'session' | 'dashboard' | 'notifications' | 'settings'; /** Session ID (required when type === 'session') */ sessionId?: string; /** Project ID (required when type === 'session') */ projectId?: string; /** Display name for the tab (max 50 chars) */ label: string; /** Unix timestamp when tab was opened */ createdAt: number; /** Whether this tab was opened from CommandPalette search */ fromSearch?: boolean; /** Pending navigation request (replaces legacy deep-link fields) */ pendingNavigation?: TabNavigationRequest; /** ID of the last consumed navigation request (prevents re-processing) */ lastConsumedNavigationId?: string; /** Saved scroll position for restoring when tab becomes active again */ savedScrollTop?: number; /** Whether the Context panel is shown (per-tab UI state) */ showContextPanel?: boolean; } /** * Options for opening a tab */ export interface OpenTabOptions { /** Force open in new tab even if session already exists (e.g., for Ctrl+click) */ forceNewTab?: boolean; /** Replace the current active tab instead of creating a new one */ replaceActiveTab?: boolean; } /** * Input type for creating a new tab (id and createdAt are auto-generated) */ export type TabInput = Omit; /** * Categories for date-based session grouping */ export type DateCategory = 'Today' | 'Yesterday' | 'Previous 7 Days' | 'Older'; /** * Sessions grouped by relative date category */ export type DateGroupedSessions = Record; // ============================================================================= // Constants // ============================================================================= /** Maximum characters for tab label before truncation */ const TAB_LABEL_MAX_LENGTH = 50; /** Date category order for rendering */ export const DATE_CATEGORY_ORDER: DateCategory[] = [ 'Today', 'Yesterday', 'Previous 7 Days', 'Older', ]; // ============================================================================= // Validation Helpers // ============================================================================= /** * Find tab by session ID (for backwards compatibility) * NOTE: Prefer findTabBySessionAndProject when projectId is available */ export function findTabBySession(tabs: Tab[], sessionId: string): Tab | undefined { return tabs.find((t) => t.type === 'session' && t.sessionId === sessionId); } /** * Find tab by both session ID AND project ID * This prevents finding a tab with the same sessionId but different project * (e.g., same filename in different repositories) */ export function findTabBySessionAndProject( tabs: Tab[], sessionId: string, projectId: string ): Tab | undefined { return tabs.find( (t) => t.type === 'session' && t.sessionId === sessionId && t.projectId === projectId ); } /** * Truncate label to max length with ellipsis */ export function truncateLabel(label: string): string { if (label.length <= TAB_LABEL_MAX_LENGTH) return label; return label.slice(0, TAB_LABEL_MAX_LENGTH - 1) + '…'; } // ============================================================================= // Navigation Request Helpers // ============================================================================= /** * Create an error navigation request (from notification click or trigger preview). */ export function createErrorNavigationRequest( payload: ErrorNavigationPayload, source: 'notification' | 'triggerPreview' = 'notification', highlightColor?: TriggerColor ): TabNavigationRequest { return { id: crypto.randomUUID(), kind: 'error', source, highlight: highlightColor ?? 'red', payload, }; } /** * Create a search navigation request (from Command Palette). */ export function createSearchNavigationRequest( payload: SearchNavigationPayload ): TabNavigationRequest { return { id: crypto.randomUUID(), kind: 'search', source: 'commandPalette', highlight: 'yellow', payload, }; } /** * Type guard for error navigation payload. */ export function isErrorPayload( request: TabNavigationRequest ): request is TabNavigationRequest & { payload: ErrorNavigationPayload } { return request.kind === 'error'; } /** * Type guard for search navigation payload. */ export function isSearchPayload( request: TabNavigationRequest ): request is TabNavigationRequest & { payload: SearchNavigationPayload } { return request.kind === 'search'; }