236 lines · 7.0 KB · TypeScript
RawDownload
/** * 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<string, never>;} // =============================================================================// 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<Tab, 'id' | 'createdAt'>; /** * 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<DateCategory, Session[]>; // =============================================================================// 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';}