mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
refactor(store): Extract types from app-store.ts into modular type files
- Create store/types/ directory with 8 modular type files: - usage-types.ts: ClaudeUsage, CodexUsage, isClaudeUsageAtLimit - ui-types.ts: ViewMode, ThemeMode, KeyboardShortcuts, etc. - settings-types.ts: ApiKeys - chat-types.ts: ChatMessage, ChatSession, FeatureImage - terminal-types.ts: TerminalState, TerminalTab, etc. - project-types.ts: Feature, FileTreeNode, ProjectAnalysis - state-types.ts: AppState, AppActions interfaces - index.ts: Re-exports all types - Update electron.ts to import from store/types/usage-types (breaks circular dependency between electron.ts and app-store.ts) - Update app-store.ts to import and re-export types for backward compatibility - existing imports from @/store/app-store continue to work This is PR 1 of the app-store refactoring plan. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
// Type definitions for Electron IPC API
|
// Type definitions for Electron IPC API
|
||||||
import type { SessionListItem, Message } from '@/types/electron';
|
import type { SessionListItem, Message } from '@/types/electron';
|
||||||
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/types/usage-types';
|
||||||
import type {
|
import type {
|
||||||
IssueValidationVerdict,
|
IssueValidationVerdict,
|
||||||
IssueValidationConfidence,
|
IssueValidationConfidence,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
43
apps/ui/src/store/types/chat-types.ts
Normal file
43
apps/ui/src/store/types/chat-types.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export interface ImageAttachment {
|
||||||
|
id?: string; // Optional - may not be present in messages loaded from server
|
||||||
|
data: string; // base64 encoded image data
|
||||||
|
mimeType: string; // e.g., "image/png", "image/jpeg"
|
||||||
|
filename: string;
|
||||||
|
size?: number; // file size in bytes - optional for messages from server
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextFileAttachment {
|
||||||
|
id: string;
|
||||||
|
content: string; // text content of the file
|
||||||
|
mimeType: string; // e.g., "text/plain", "text/markdown"
|
||||||
|
filename: string;
|
||||||
|
size: number; // file size in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
role: 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
timestamp: Date;
|
||||||
|
images?: ImageAttachment[];
|
||||||
|
textFiles?: TextFileAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatSession {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
projectId: string;
|
||||||
|
messages: ChatMessage[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
archived: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI-specific: base64-encoded images (not in shared types)
|
||||||
|
export interface FeatureImage {
|
||||||
|
id: string;
|
||||||
|
data: string; // base64 encoded
|
||||||
|
mimeType: string;
|
||||||
|
filename: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
7
apps/ui/src/store/types/index.ts
Normal file
7
apps/ui/src/store/types/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from './usage-types';
|
||||||
|
export * from './ui-types';
|
||||||
|
export * from './settings-types';
|
||||||
|
export * from './chat-types';
|
||||||
|
export * from './terminal-types';
|
||||||
|
export * from './project-types';
|
||||||
|
export * from './state-types';
|
||||||
66
apps/ui/src/store/types/project-types.ts
Normal file
66
apps/ui/src/store/types/project-types.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import type {
|
||||||
|
Feature as BaseFeature,
|
||||||
|
FeatureImagePath,
|
||||||
|
FeatureTextFilePath,
|
||||||
|
ThinkingLevel,
|
||||||
|
ReasoningEffort,
|
||||||
|
FeatureStatusWithPipeline,
|
||||||
|
PlanSpec,
|
||||||
|
} from '@automaker/types';
|
||||||
|
import type { FeatureImage } from './chat-types';
|
||||||
|
|
||||||
|
// Available models for feature execution
|
||||||
|
export type ClaudeModel = 'opus' | 'sonnet' | 'haiku';
|
||||||
|
|
||||||
|
export interface Feature extends Omit<
|
||||||
|
BaseFeature,
|
||||||
|
| 'steps'
|
||||||
|
| 'imagePaths'
|
||||||
|
| 'textFilePaths'
|
||||||
|
| 'status'
|
||||||
|
| 'planSpec'
|
||||||
|
| 'dependencies'
|
||||||
|
| 'model'
|
||||||
|
| 'branchName'
|
||||||
|
| 'thinkingLevel'
|
||||||
|
| 'reasoningEffort'
|
||||||
|
| 'summary'
|
||||||
|
> {
|
||||||
|
id: string;
|
||||||
|
title?: string;
|
||||||
|
titleGenerating?: boolean;
|
||||||
|
category: string;
|
||||||
|
description: string;
|
||||||
|
steps: string[]; // Required in UI (not optional)
|
||||||
|
status: FeatureStatusWithPipeline;
|
||||||
|
images?: FeatureImage[]; // UI-specific base64 images
|
||||||
|
imagePaths?: FeatureImagePath[]; // Stricter type than base (no string | union)
|
||||||
|
textFilePaths?: FeatureTextFilePath[]; // Text file attachments for context
|
||||||
|
justFinishedAt?: string; // UI-specific: ISO timestamp when agent just finished
|
||||||
|
prUrl?: string; // UI-specific: Pull request URL
|
||||||
|
planSpec?: PlanSpec; // Explicit planSpec type to override BaseFeature's index signature
|
||||||
|
dependencies?: string[]; // Explicit type to override BaseFeature's index signature
|
||||||
|
model?: string; // Explicit type to override BaseFeature's index signature
|
||||||
|
branchName?: string; // Explicit type to override BaseFeature's index signature
|
||||||
|
thinkingLevel?: ThinkingLevel; // Explicit type to override BaseFeature's index signature
|
||||||
|
reasoningEffort?: ReasoningEffort; // Explicit type to override BaseFeature's index signature
|
||||||
|
summary?: string; // Explicit type to override BaseFeature's index signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// File tree node for project analysis
|
||||||
|
export interface FileTreeNode {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
isDirectory: boolean;
|
||||||
|
extension?: string;
|
||||||
|
children?: FileTreeNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project analysis result
|
||||||
|
export interface ProjectAnalysis {
|
||||||
|
fileTree: FileTreeNode[];
|
||||||
|
totalFiles: number;
|
||||||
|
totalDirectories: number;
|
||||||
|
filesByExtension: Record<string, number>;
|
||||||
|
analyzedAt: string;
|
||||||
|
}
|
||||||
5
apps/ui/src/store/types/settings-types.ts
Normal file
5
apps/ui/src/store/types/settings-types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ApiKeys {
|
||||||
|
anthropic: string;
|
||||||
|
google: string;
|
||||||
|
openai: string;
|
||||||
|
}
|
||||||
806
apps/ui/src/store/types/state-types.ts
Normal file
806
apps/ui/src/store/types/state-types.ts
Normal file
@@ -0,0 +1,806 @@
|
|||||||
|
import type { Project, TrashedProject } from '@/lib/electron';
|
||||||
|
import type {
|
||||||
|
ModelAlias,
|
||||||
|
PlanningMode,
|
||||||
|
ThinkingLevel,
|
||||||
|
ReasoningEffort,
|
||||||
|
ModelProvider,
|
||||||
|
CursorModelId,
|
||||||
|
CodexModelId,
|
||||||
|
OpencodeModelId,
|
||||||
|
GeminiModelId,
|
||||||
|
CopilotModelId,
|
||||||
|
PhaseModelConfig,
|
||||||
|
PhaseModelKey,
|
||||||
|
PhaseModelEntry,
|
||||||
|
MCPServerConfig,
|
||||||
|
PipelineConfig,
|
||||||
|
PipelineStep,
|
||||||
|
PromptCustomization,
|
||||||
|
ModelDefinition,
|
||||||
|
ServerLogLevel,
|
||||||
|
EventHook,
|
||||||
|
ClaudeApiProfile,
|
||||||
|
ClaudeCompatibleProvider,
|
||||||
|
SidebarStyle,
|
||||||
|
} from '@automaker/types';
|
||||||
|
|
||||||
|
import type { ViewMode, ThemeMode, BoardViewMode, KeyboardShortcuts } from './ui-types';
|
||||||
|
import type { ApiKeys } from './settings-types';
|
||||||
|
import type { ChatMessage, ChatSession, FeatureImage } from './chat-types';
|
||||||
|
import type { TerminalState, TerminalPanelContent, PersistedTerminalState } from './terminal-types';
|
||||||
|
import type { Feature, ProjectAnalysis } from './project-types';
|
||||||
|
import type { ClaudeUsage, CodexUsage } from './usage-types';
|
||||||
|
|
||||||
|
/** State for worktree init script execution */
|
||||||
|
export interface InitScriptState {
|
||||||
|
status: 'idle' | 'running' | 'success' | 'failed';
|
||||||
|
branch: string;
|
||||||
|
output: string[];
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoModeActivity {
|
||||||
|
id: string;
|
||||||
|
featureId: string;
|
||||||
|
timestamp: Date;
|
||||||
|
type:
|
||||||
|
| 'start'
|
||||||
|
| 'progress'
|
||||||
|
| 'tool'
|
||||||
|
| 'complete'
|
||||||
|
| 'error'
|
||||||
|
| 'planning'
|
||||||
|
| 'action'
|
||||||
|
| 'verification';
|
||||||
|
message: string;
|
||||||
|
tool?: string;
|
||||||
|
passes?: boolean;
|
||||||
|
phase?: 'planning' | 'action' | 'verification';
|
||||||
|
errorType?: 'authentication' | 'execution';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppState {
|
||||||
|
// Project state
|
||||||
|
projects: Project[];
|
||||||
|
currentProject: Project | null;
|
||||||
|
trashedProjects: TrashedProject[];
|
||||||
|
projectHistory: string[]; // Array of project IDs in MRU order (most recent first)
|
||||||
|
projectHistoryIndex: number; // Current position in project history for cycling
|
||||||
|
|
||||||
|
// View state
|
||||||
|
currentView: ViewMode;
|
||||||
|
sidebarOpen: boolean;
|
||||||
|
sidebarStyle: SidebarStyle; // 'unified' (modern) or 'discord' (classic two-sidebar layout)
|
||||||
|
collapsedNavSections: Record<string, boolean>; // Collapsed state of nav sections (key: section label)
|
||||||
|
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
|
||||||
|
|
||||||
|
// Agent Session state (per-project, keyed by project path)
|
||||||
|
lastSelectedSessionByProject: Record<string, string>; // projectPath -> sessionId
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
theme: ThemeMode;
|
||||||
|
|
||||||
|
// Fonts (global defaults)
|
||||||
|
fontFamilySans: string | null; // null = use default Geist Sans
|
||||||
|
fontFamilyMono: string | null; // null = use default Geist Mono
|
||||||
|
|
||||||
|
// Features/Kanban
|
||||||
|
features: Feature[];
|
||||||
|
|
||||||
|
// App spec
|
||||||
|
appSpec: string;
|
||||||
|
|
||||||
|
// IPC status
|
||||||
|
ipcConnected: boolean;
|
||||||
|
|
||||||
|
// API Keys
|
||||||
|
apiKeys: ApiKeys;
|
||||||
|
|
||||||
|
// Chat Sessions
|
||||||
|
chatSessions: ChatSession[];
|
||||||
|
currentChatSession: ChatSession | null;
|
||||||
|
chatHistoryOpen: boolean;
|
||||||
|
|
||||||
|
// Auto Mode (per-worktree state, keyed by "${projectId}::${branchName ?? '__main__'}")
|
||||||
|
autoModeByWorktree: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
isRunning: boolean;
|
||||||
|
runningTasks: string[]; // Feature IDs being worked on
|
||||||
|
branchName: string | null; // null = main worktree
|
||||||
|
maxConcurrency?: number; // Maximum concurrent features for this worktree (defaults to 3)
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
autoModeActivityLog: AutoModeActivity[];
|
||||||
|
maxConcurrency: number; // Legacy: Maximum number of concurrent agent tasks (deprecated, use per-worktree maxConcurrency)
|
||||||
|
|
||||||
|
// Kanban Card Display Settings
|
||||||
|
boardViewMode: BoardViewMode; // Whether to show kanban or dependency graph view
|
||||||
|
|
||||||
|
// Feature Default Settings
|
||||||
|
defaultSkipTests: boolean; // Default value for skip tests when creating new features
|
||||||
|
enableDependencyBlocking: boolean; // When true, show blocked badges and warnings for features with incomplete dependencies (default: true)
|
||||||
|
skipVerificationInAutoMode: boolean; // When true, auto-mode grabs features even if dependencies are not verified (only checks they're not running)
|
||||||
|
enableAiCommitMessages: boolean; // When true, auto-generate commit messages using AI when opening commit dialog
|
||||||
|
planUseSelectedWorktreeBranch: boolean; // When true, Plan dialog creates features on the currently selected worktree branch
|
||||||
|
addFeatureUseSelectedWorktreeBranch: boolean; // When true, Add Feature dialog defaults to custom mode with selected worktree branch
|
||||||
|
|
||||||
|
// Worktree Settings
|
||||||
|
useWorktrees: boolean; // Whether to use git worktree isolation for features (default: true)
|
||||||
|
|
||||||
|
// User-managed Worktrees (per-project)
|
||||||
|
// projectPath -> { path: worktreePath or null for main, branch: branch name }
|
||||||
|
currentWorktreeByProject: Record<string, { path: string | null; branch: string }>;
|
||||||
|
worktreesByProject: Record<
|
||||||
|
string,
|
||||||
|
Array<{
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
changedFilesCount?: number;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Keyboard Shortcuts
|
||||||
|
keyboardShortcuts: KeyboardShortcuts; // User-defined keyboard shortcuts
|
||||||
|
|
||||||
|
// Audio Settings
|
||||||
|
muteDoneSound: boolean; // When true, mute the notification sound when agents complete (default: false)
|
||||||
|
|
||||||
|
// Splash Screen Settings
|
||||||
|
disableSplashScreen: boolean; // When true, skip showing the splash screen overlay on startup
|
||||||
|
|
||||||
|
// Server Log Level Settings
|
||||||
|
serverLogLevel: ServerLogLevel; // Log level for the API server (error, warn, info, debug)
|
||||||
|
enableRequestLogging: boolean; // Enable HTTP request logging (Morgan)
|
||||||
|
|
||||||
|
// Developer Tools Settings
|
||||||
|
showQueryDevtools: boolean; // Show React Query DevTools panel (only in development mode)
|
||||||
|
|
||||||
|
// Enhancement Model Settings
|
||||||
|
enhancementModel: ModelAlias; // Model used for feature enhancement (default: sonnet)
|
||||||
|
|
||||||
|
// Validation Model Settings
|
||||||
|
validationModel: ModelAlias; // Model used for GitHub issue validation (default: opus)
|
||||||
|
|
||||||
|
// Phase Model Settings - per-phase AI model configuration
|
||||||
|
phaseModels: PhaseModelConfig;
|
||||||
|
favoriteModels: string[];
|
||||||
|
|
||||||
|
// Cursor CLI Settings (global)
|
||||||
|
enabledCursorModels: CursorModelId[]; // Which Cursor models are available in feature modal
|
||||||
|
cursorDefaultModel: CursorModelId; // Default Cursor model selection
|
||||||
|
|
||||||
|
// Codex CLI Settings (global)
|
||||||
|
enabledCodexModels: CodexModelId[]; // Which Codex models are available in feature modal
|
||||||
|
codexDefaultModel: CodexModelId; // Default Codex model selection
|
||||||
|
codexAutoLoadAgents: boolean; // Auto-load .codex/AGENTS.md files
|
||||||
|
codexSandboxMode: 'read-only' | 'workspace-write' | 'danger-full-access'; // Sandbox policy
|
||||||
|
codexApprovalPolicy: 'untrusted' | 'on-failure' | 'on-request' | 'never'; // Approval policy
|
||||||
|
codexEnableWebSearch: boolean; // Enable web search capability
|
||||||
|
codexEnableImages: boolean; // Enable image processing
|
||||||
|
|
||||||
|
// OpenCode CLI Settings (global)
|
||||||
|
// Static OpenCode settings are persisted via SETTINGS_FIELDS_TO_SYNC
|
||||||
|
enabledOpencodeModels: OpencodeModelId[]; // Which static OpenCode models are available
|
||||||
|
opencodeDefaultModel: OpencodeModelId; // Default OpenCode model selection
|
||||||
|
// Dynamic models are session-only (not persisted) because they're discovered at runtime
|
||||||
|
// from `opencode models` CLI and depend on current provider authentication state
|
||||||
|
dynamicOpencodeModels: ModelDefinition[]; // Dynamically discovered models from OpenCode CLI
|
||||||
|
enabledDynamicModelIds: string[]; // Which dynamic models are enabled
|
||||||
|
cachedOpencodeProviders: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
authenticated: boolean;
|
||||||
|
authMethod?: string;
|
||||||
|
}>; // Cached providers
|
||||||
|
opencodeModelsLoading: boolean; // Whether OpenCode models are being fetched
|
||||||
|
opencodeModelsError: string | null; // Error message if fetch failed
|
||||||
|
opencodeModelsLastFetched: number | null; // Timestamp of last successful fetch
|
||||||
|
opencodeModelsLastFailedAt: number | null; // Timestamp of last failed fetch
|
||||||
|
|
||||||
|
// Gemini CLI Settings (global)
|
||||||
|
enabledGeminiModels: GeminiModelId[]; // Which Gemini models are available in feature modal
|
||||||
|
geminiDefaultModel: GeminiModelId; // Default Gemini model selection
|
||||||
|
|
||||||
|
// Copilot SDK Settings (global)
|
||||||
|
enabledCopilotModels: CopilotModelId[]; // Which Copilot models are available in feature modal
|
||||||
|
copilotDefaultModel: CopilotModelId; // Default Copilot model selection
|
||||||
|
|
||||||
|
// Provider Visibility Settings
|
||||||
|
disabledProviders: ModelProvider[]; // Providers that are disabled and hidden from dropdowns
|
||||||
|
|
||||||
|
// Claude Agent SDK Settings
|
||||||
|
autoLoadClaudeMd: boolean; // Auto-load CLAUDE.md files using SDK's settingSources option
|
||||||
|
skipSandboxWarning: boolean; // Skip the sandbox environment warning dialog on startup
|
||||||
|
|
||||||
|
// MCP Servers
|
||||||
|
mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use
|
||||||
|
|
||||||
|
// Editor Configuration
|
||||||
|
defaultEditorCommand: string | null; // Default editor for "Open In" action
|
||||||
|
|
||||||
|
// Terminal Configuration
|
||||||
|
defaultTerminalId: string | null; // Default external terminal for "Open In Terminal" action (null = integrated)
|
||||||
|
|
||||||
|
// Skills Configuration
|
||||||
|
enableSkills: boolean; // Enable Skills functionality (loads from .claude/skills/ directories)
|
||||||
|
skillsSources: Array<'user' | 'project'>; // Which directories to load Skills from
|
||||||
|
|
||||||
|
// Subagents Configuration
|
||||||
|
enableSubagents: boolean; // Enable Custom Subagents functionality (loads from .claude/agents/ directories)
|
||||||
|
subagentsSources: Array<'user' | 'project'>; // Which directories to load Subagents from
|
||||||
|
|
||||||
|
// Prompt Customization
|
||||||
|
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
|
||||||
|
|
||||||
|
// Event Hooks
|
||||||
|
eventHooks: EventHook[]; // Event hooks for custom commands or webhooks
|
||||||
|
|
||||||
|
// Claude-Compatible Providers (new system)
|
||||||
|
claudeCompatibleProviders: ClaudeCompatibleProvider[]; // Providers that expose models to dropdowns
|
||||||
|
|
||||||
|
// Claude API Profiles (deprecated - kept for backward compatibility)
|
||||||
|
claudeApiProfiles: ClaudeApiProfile[]; // Claude-compatible API endpoint profiles
|
||||||
|
activeClaudeApiProfileId: string | null; // Active profile ID (null = use direct Anthropic API)
|
||||||
|
|
||||||
|
// Project Analysis
|
||||||
|
projectAnalysis: ProjectAnalysis | null;
|
||||||
|
isAnalyzing: boolean;
|
||||||
|
|
||||||
|
// Board Background Settings (per-project, keyed by project path)
|
||||||
|
boardBackgroundByProject: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
imagePath: string | null; // Path to background image in .automaker directory
|
||||||
|
imageVersion?: number; // Timestamp to bust browser cache when image is updated
|
||||||
|
cardOpacity: number; // Opacity of cards (0-100)
|
||||||
|
columnOpacity: number; // Opacity of columns (0-100)
|
||||||
|
columnBorderEnabled: boolean; // Whether to show column borders
|
||||||
|
cardGlassmorphism: boolean; // Whether to use glassmorphism (backdrop-blur) on cards
|
||||||
|
cardBorderEnabled: boolean; // Whether to show card borders
|
||||||
|
cardBorderOpacity: number; // Opacity of card borders (0-100)
|
||||||
|
hideScrollbar: boolean; // Whether to hide the board scrollbar
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Theme Preview (for hover preview in theme selectors)
|
||||||
|
previewTheme: ThemeMode | null;
|
||||||
|
|
||||||
|
// Terminal state
|
||||||
|
terminalState: TerminalState;
|
||||||
|
|
||||||
|
// Terminal layout persistence (per-project, keyed by project path)
|
||||||
|
// Stores the tab/split structure so it can be restored when switching projects
|
||||||
|
terminalLayoutByProject: Record<string, PersistedTerminalState>;
|
||||||
|
|
||||||
|
// Spec Creation State (per-project, keyed by project path)
|
||||||
|
// Tracks which project is currently having its spec generated
|
||||||
|
specCreatingForProject: string | null;
|
||||||
|
|
||||||
|
defaultPlanningMode: PlanningMode;
|
||||||
|
defaultRequirePlanApproval: boolean;
|
||||||
|
defaultFeatureModel: PhaseModelEntry;
|
||||||
|
|
||||||
|
// Plan Approval State
|
||||||
|
// When a plan requires user approval, this holds the pending approval details
|
||||||
|
pendingPlanApproval: {
|
||||||
|
featureId: string;
|
||||||
|
projectPath: string;
|
||||||
|
planContent: string;
|
||||||
|
planningMode: 'lite' | 'spec' | 'full';
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
// Claude Usage Tracking
|
||||||
|
claudeRefreshInterval: number; // Refresh interval in seconds (default: 60)
|
||||||
|
claudeUsage: ClaudeUsage | null;
|
||||||
|
claudeUsageLastUpdated: number | null;
|
||||||
|
|
||||||
|
// Codex Usage Tracking
|
||||||
|
codexUsage: CodexUsage | null;
|
||||||
|
codexUsageLastUpdated: number | null;
|
||||||
|
|
||||||
|
// Codex Models (dynamically fetched)
|
||||||
|
codexModels: Array<{
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
hasThinking: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: 'premium' | 'standard' | 'basic';
|
||||||
|
isDefault: boolean;
|
||||||
|
}>;
|
||||||
|
codexModelsLoading: boolean;
|
||||||
|
codexModelsError: string | null;
|
||||||
|
codexModelsLastFetched: number | null;
|
||||||
|
codexModelsLastFailedAt: number | null;
|
||||||
|
|
||||||
|
// Pipeline Configuration (per-project, keyed by project path)
|
||||||
|
pipelineConfigByProject: Record<string, PipelineConfig>;
|
||||||
|
|
||||||
|
// Worktree Panel Visibility (per-project, keyed by project path)
|
||||||
|
// Whether the worktree panel row is visible (default: true)
|
||||||
|
worktreePanelVisibleByProject: Record<string, boolean>;
|
||||||
|
|
||||||
|
// Init Script Indicator Visibility (per-project, keyed by project path)
|
||||||
|
// Whether to show the floating init script indicator panel (default: true)
|
||||||
|
showInitScriptIndicatorByProject: Record<string, boolean>;
|
||||||
|
|
||||||
|
// Default Delete Branch With Worktree (per-project, keyed by project path)
|
||||||
|
// Whether to default the "delete branch" checkbox when deleting a worktree (default: false)
|
||||||
|
defaultDeleteBranchByProject: Record<string, boolean>;
|
||||||
|
|
||||||
|
// Auto-dismiss Init Script Indicator (per-project, keyed by project path)
|
||||||
|
// Whether to auto-dismiss the indicator after completion (default: true)
|
||||||
|
autoDismissInitScriptIndicatorByProject: Record<string, boolean>;
|
||||||
|
|
||||||
|
// Use Worktrees Override (per-project, keyed by project path)
|
||||||
|
// undefined = use global setting, true/false = project-specific override
|
||||||
|
useWorktreesByProject: Record<string, boolean | undefined>;
|
||||||
|
|
||||||
|
// UI State (previously in localStorage, now synced via API)
|
||||||
|
/** Whether worktree panel is collapsed in board view */
|
||||||
|
worktreePanelCollapsed: boolean;
|
||||||
|
/** Last directory opened in file picker */
|
||||||
|
lastProjectDir: string;
|
||||||
|
/** Recently accessed folders for quick access */
|
||||||
|
recentFolders: string[];
|
||||||
|
|
||||||
|
// Init Script State (keyed by "projectPath::branch" to support concurrent scripts)
|
||||||
|
initScriptState: Record<string, InitScriptState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppActions {
|
||||||
|
// Project actions
|
||||||
|
setProjects: (projects: Project[]) => void;
|
||||||
|
addProject: (project: Project) => void;
|
||||||
|
removeProject: (projectId: string) => void;
|
||||||
|
moveProjectToTrash: (projectId: string) => void;
|
||||||
|
restoreTrashedProject: (projectId: string) => void;
|
||||||
|
deleteTrashedProject: (projectId: string) => void;
|
||||||
|
emptyTrash: () => void;
|
||||||
|
setCurrentProject: (project: Project | null) => void;
|
||||||
|
upsertAndSetCurrentProject: (path: string, name: string, theme?: ThemeMode) => Project; // Upsert project by path and set as current
|
||||||
|
reorderProjects: (oldIndex: number, newIndex: number) => void;
|
||||||
|
cyclePrevProject: () => void; // Cycle back through project history (Q)
|
||||||
|
cycleNextProject: () => void; // Cycle forward through project history (E)
|
||||||
|
clearProjectHistory: () => void; // Clear history, keeping only current project
|
||||||
|
toggleProjectFavorite: (projectId: string) => void; // Toggle project favorite status
|
||||||
|
setProjectIcon: (projectId: string, icon: string | null) => void; // Set project icon (null to clear)
|
||||||
|
setProjectCustomIcon: (projectId: string, customIconPath: string | null) => void; // Set custom project icon image path (null to clear)
|
||||||
|
setProjectName: (projectId: string, name: string) => void; // Update project name
|
||||||
|
|
||||||
|
// View actions
|
||||||
|
setCurrentView: (view: ViewMode) => void;
|
||||||
|
toggleSidebar: () => void;
|
||||||
|
setSidebarOpen: (open: boolean) => void;
|
||||||
|
setSidebarStyle: (style: SidebarStyle) => void;
|
||||||
|
setCollapsedNavSections: (sections: Record<string, boolean>) => void;
|
||||||
|
toggleNavSection: (sectionLabel: string) => void;
|
||||||
|
toggleMobileSidebarHidden: () => void;
|
||||||
|
setMobileSidebarHidden: (hidden: boolean) => void;
|
||||||
|
|
||||||
|
// Theme actions
|
||||||
|
setTheme: (theme: ThemeMode) => void;
|
||||||
|
setProjectTheme: (projectId: string, theme: ThemeMode | null) => void; // Set per-project theme (null to clear)
|
||||||
|
getEffectiveTheme: () => ThemeMode; // Get the effective theme (project, global, or preview if set)
|
||||||
|
setPreviewTheme: (theme: ThemeMode | null) => void; // Set preview theme for hover preview (null to clear)
|
||||||
|
|
||||||
|
// Font actions (global + per-project override)
|
||||||
|
setFontSans: (fontFamily: string | null) => void; // Set global UI/sans font (null to clear)
|
||||||
|
setFontMono: (fontFamily: string | null) => void; // Set global code/mono font (null to clear)
|
||||||
|
setProjectFontSans: (projectId: string, fontFamily: string | null) => void; // Set per-project UI/sans font override (null = use global)
|
||||||
|
setProjectFontMono: (projectId: string, fontFamily: string | null) => void; // Set per-project code/mono font override (null = use global)
|
||||||
|
getEffectiveFontSans: () => string | null; // Get effective UI font (project override -> global -> null for default)
|
||||||
|
getEffectiveFontMono: () => string | null; // Get effective code font (project override -> global -> null for default)
|
||||||
|
|
||||||
|
// Claude API Profile actions (per-project override)
|
||||||
|
/** @deprecated Use setProjectPhaseModelOverride instead */
|
||||||
|
setProjectClaudeApiProfile: (projectId: string, profileId: string | null | undefined) => void; // Set per-project Claude API profile (undefined = use global, null = direct API, string = specific profile)
|
||||||
|
|
||||||
|
// Project Phase Model Overrides
|
||||||
|
setProjectPhaseModelOverride: (
|
||||||
|
projectId: string,
|
||||||
|
phase: PhaseModelKey,
|
||||||
|
entry: PhaseModelEntry | null // null = use global
|
||||||
|
) => void;
|
||||||
|
clearAllProjectPhaseModelOverrides: (projectId: string) => void;
|
||||||
|
|
||||||
|
// Project Default Feature Model Override
|
||||||
|
setProjectDefaultFeatureModel: (
|
||||||
|
projectId: string,
|
||||||
|
entry: PhaseModelEntry | null // null = use global
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// Feature actions
|
||||||
|
setFeatures: (features: Feature[]) => void;
|
||||||
|
updateFeature: (id: string, updates: Partial<Feature>) => void;
|
||||||
|
addFeature: (feature: Omit<Feature, 'id'> & Partial<Pick<Feature, 'id'>>) => Feature;
|
||||||
|
removeFeature: (id: string) => void;
|
||||||
|
moveFeature: (id: string, newStatus: Feature['status']) => void;
|
||||||
|
|
||||||
|
// App spec actions
|
||||||
|
setAppSpec: (spec: string) => void;
|
||||||
|
|
||||||
|
// IPC actions
|
||||||
|
setIpcConnected: (connected: boolean) => void;
|
||||||
|
|
||||||
|
// API Keys actions
|
||||||
|
setApiKeys: (keys: Partial<ApiKeys>) => void;
|
||||||
|
|
||||||
|
// Chat Session actions
|
||||||
|
createChatSession: (title?: string) => ChatSession;
|
||||||
|
updateChatSession: (sessionId: string, updates: Partial<ChatSession>) => void;
|
||||||
|
addMessageToSession: (sessionId: string, message: ChatMessage) => void;
|
||||||
|
setCurrentChatSession: (session: ChatSession | null) => void;
|
||||||
|
archiveChatSession: (sessionId: string) => void;
|
||||||
|
unarchiveChatSession: (sessionId: string) => void;
|
||||||
|
deleteChatSession: (sessionId: string) => void;
|
||||||
|
setChatHistoryOpen: (open: boolean) => void;
|
||||||
|
toggleChatHistory: () => void;
|
||||||
|
|
||||||
|
// Auto Mode actions (per-worktree)
|
||||||
|
setAutoModeRunning: (
|
||||||
|
projectId: string,
|
||||||
|
branchName: string | null,
|
||||||
|
running: boolean,
|
||||||
|
maxConcurrency?: number,
|
||||||
|
runningTasks?: string[]
|
||||||
|
) => void;
|
||||||
|
addRunningTask: (projectId: string, branchName: string | null, taskId: string) => void;
|
||||||
|
removeRunningTask: (projectId: string, branchName: string | null, taskId: string) => void;
|
||||||
|
clearRunningTasks: (projectId: string, branchName: string | null) => void;
|
||||||
|
getAutoModeState: (
|
||||||
|
projectId: string,
|
||||||
|
branchName: string | null
|
||||||
|
) => {
|
||||||
|
isRunning: boolean;
|
||||||
|
runningTasks: string[];
|
||||||
|
branchName: string | null;
|
||||||
|
maxConcurrency?: number;
|
||||||
|
};
|
||||||
|
/** Helper to generate worktree key from projectId and branchName */
|
||||||
|
getWorktreeKey: (projectId: string, branchName: string | null) => string;
|
||||||
|
addAutoModeActivity: (activity: Omit<AutoModeActivity, 'id' | 'timestamp'>) => void;
|
||||||
|
clearAutoModeActivity: () => void;
|
||||||
|
setMaxConcurrency: (max: number) => void; // Legacy: kept for backward compatibility
|
||||||
|
getMaxConcurrencyForWorktree: (projectId: string, branchName: string | null) => number;
|
||||||
|
setMaxConcurrencyForWorktree: (
|
||||||
|
projectId: string,
|
||||||
|
branchName: string | null,
|
||||||
|
maxConcurrency: number
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// Kanban Card Settings actions
|
||||||
|
setBoardViewMode: (mode: BoardViewMode) => void;
|
||||||
|
|
||||||
|
// Feature Default Settings actions
|
||||||
|
setDefaultSkipTests: (skip: boolean) => void;
|
||||||
|
setEnableDependencyBlocking: (enabled: boolean) => void;
|
||||||
|
setSkipVerificationInAutoMode: (enabled: boolean) => Promise<void>;
|
||||||
|
setEnableAiCommitMessages: (enabled: boolean) => Promise<void>;
|
||||||
|
setPlanUseSelectedWorktreeBranch: (enabled: boolean) => Promise<void>;
|
||||||
|
setAddFeatureUseSelectedWorktreeBranch: (enabled: boolean) => Promise<void>;
|
||||||
|
|
||||||
|
// Worktree Settings actions
|
||||||
|
setUseWorktrees: (enabled: boolean) => void;
|
||||||
|
setCurrentWorktree: (projectPath: string, worktreePath: string | null, branch: string) => void;
|
||||||
|
setWorktrees: (
|
||||||
|
projectPath: string,
|
||||||
|
worktrees: Array<{
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
changedFilesCount?: number;
|
||||||
|
}>
|
||||||
|
) => void;
|
||||||
|
getCurrentWorktree: (projectPath: string) => { path: string | null; branch: string } | null;
|
||||||
|
getWorktrees: (projectPath: string) => Array<{
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
isMain: boolean;
|
||||||
|
isCurrent: boolean;
|
||||||
|
hasWorktree: boolean;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
changedFilesCount?: number;
|
||||||
|
}>;
|
||||||
|
isPrimaryWorktreeBranch: (projectPath: string, branchName: string) => boolean;
|
||||||
|
getPrimaryWorktreeBranch: (projectPath: string) => string | null;
|
||||||
|
|
||||||
|
// Keyboard Shortcuts actions
|
||||||
|
setKeyboardShortcut: (key: keyof KeyboardShortcuts, value: string) => void;
|
||||||
|
setKeyboardShortcuts: (shortcuts: Partial<KeyboardShortcuts>) => void;
|
||||||
|
resetKeyboardShortcuts: () => void;
|
||||||
|
|
||||||
|
// Audio Settings actions
|
||||||
|
setMuteDoneSound: (muted: boolean) => void;
|
||||||
|
|
||||||
|
// Splash Screen actions
|
||||||
|
setDisableSplashScreen: (disabled: boolean) => void;
|
||||||
|
|
||||||
|
// Server Log Level actions
|
||||||
|
setServerLogLevel: (level: ServerLogLevel) => void;
|
||||||
|
setEnableRequestLogging: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
// Developer Tools actions
|
||||||
|
setShowQueryDevtools: (show: boolean) => void;
|
||||||
|
|
||||||
|
// Enhancement Model actions
|
||||||
|
setEnhancementModel: (model: ModelAlias) => void;
|
||||||
|
|
||||||
|
// Validation Model actions
|
||||||
|
setValidationModel: (model: ModelAlias) => void;
|
||||||
|
|
||||||
|
// Phase Model actions
|
||||||
|
setPhaseModel: (phase: PhaseModelKey, entry: PhaseModelEntry) => Promise<void>;
|
||||||
|
setPhaseModels: (models: Partial<PhaseModelConfig>) => Promise<void>;
|
||||||
|
resetPhaseModels: () => Promise<void>;
|
||||||
|
toggleFavoriteModel: (modelId: string) => void;
|
||||||
|
|
||||||
|
// Cursor CLI Settings actions
|
||||||
|
setEnabledCursorModels: (models: CursorModelId[]) => void;
|
||||||
|
setCursorDefaultModel: (model: CursorModelId) => void;
|
||||||
|
toggleCursorModel: (model: CursorModelId, enabled: boolean) => void;
|
||||||
|
|
||||||
|
// Codex CLI Settings actions
|
||||||
|
setEnabledCodexModels: (models: CodexModelId[]) => void;
|
||||||
|
setCodexDefaultModel: (model: CodexModelId) => void;
|
||||||
|
toggleCodexModel: (model: CodexModelId, enabled: boolean) => void;
|
||||||
|
setCodexAutoLoadAgents: (enabled: boolean) => Promise<void>;
|
||||||
|
setCodexSandboxMode: (
|
||||||
|
mode: 'read-only' | 'workspace-write' | 'danger-full-access'
|
||||||
|
) => Promise<void>;
|
||||||
|
setCodexApprovalPolicy: (
|
||||||
|
policy: 'untrusted' | 'on-failure' | 'on-request' | 'never'
|
||||||
|
) => Promise<void>;
|
||||||
|
setCodexEnableWebSearch: (enabled: boolean) => Promise<void>;
|
||||||
|
setCodexEnableImages: (enabled: boolean) => Promise<void>;
|
||||||
|
|
||||||
|
// OpenCode CLI Settings actions
|
||||||
|
setEnabledOpencodeModels: (models: OpencodeModelId[]) => void;
|
||||||
|
setOpencodeDefaultModel: (model: OpencodeModelId) => void;
|
||||||
|
toggleOpencodeModel: (model: OpencodeModelId, enabled: boolean) => void;
|
||||||
|
setDynamicOpencodeModels: (models: ModelDefinition[]) => void;
|
||||||
|
setEnabledDynamicModelIds: (ids: string[]) => void;
|
||||||
|
toggleDynamicModel: (modelId: string, enabled: boolean) => void;
|
||||||
|
setCachedOpencodeProviders: (
|
||||||
|
providers: Array<{ id: string; name: string; authenticated: boolean; authMethod?: string }>
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// Gemini CLI Settings actions
|
||||||
|
setEnabledGeminiModels: (models: GeminiModelId[]) => void;
|
||||||
|
setGeminiDefaultModel: (model: GeminiModelId) => void;
|
||||||
|
toggleGeminiModel: (model: GeminiModelId, enabled: boolean) => void;
|
||||||
|
|
||||||
|
// Copilot SDK Settings actions
|
||||||
|
setEnabledCopilotModels: (models: CopilotModelId[]) => void;
|
||||||
|
setCopilotDefaultModel: (model: CopilotModelId) => void;
|
||||||
|
toggleCopilotModel: (model: CopilotModelId, enabled: boolean) => void;
|
||||||
|
|
||||||
|
// Provider Visibility Settings actions
|
||||||
|
setDisabledProviders: (providers: ModelProvider[]) => void;
|
||||||
|
toggleProviderDisabled: (provider: ModelProvider, disabled: boolean) => void;
|
||||||
|
isProviderDisabled: (provider: ModelProvider) => boolean;
|
||||||
|
|
||||||
|
// Claude Agent SDK Settings actions
|
||||||
|
setAutoLoadClaudeMd: (enabled: boolean) => Promise<void>;
|
||||||
|
setSkipSandboxWarning: (skip: boolean) => Promise<void>;
|
||||||
|
|
||||||
|
// Editor Configuration actions
|
||||||
|
setDefaultEditorCommand: (command: string | null) => void;
|
||||||
|
|
||||||
|
// Terminal Configuration actions
|
||||||
|
setDefaultTerminalId: (terminalId: string | null) => void;
|
||||||
|
|
||||||
|
// Prompt Customization actions
|
||||||
|
setPromptCustomization: (customization: PromptCustomization) => Promise<void>;
|
||||||
|
|
||||||
|
// Event Hook actions
|
||||||
|
setEventHooks: (hooks: EventHook[]) => void;
|
||||||
|
|
||||||
|
// Claude-Compatible Provider actions (new system)
|
||||||
|
addClaudeCompatibleProvider: (provider: ClaudeCompatibleProvider) => Promise<void>;
|
||||||
|
updateClaudeCompatibleProvider: (
|
||||||
|
id: string,
|
||||||
|
updates: Partial<ClaudeCompatibleProvider>
|
||||||
|
) => Promise<void>;
|
||||||
|
deleteClaudeCompatibleProvider: (id: string) => Promise<void>;
|
||||||
|
setClaudeCompatibleProviders: (providers: ClaudeCompatibleProvider[]) => Promise<void>;
|
||||||
|
toggleClaudeCompatibleProviderEnabled: (id: string) => Promise<void>;
|
||||||
|
|
||||||
|
// Claude API Profile actions (deprecated - kept for backward compatibility)
|
||||||
|
addClaudeApiProfile: (profile: ClaudeApiProfile) => Promise<void>;
|
||||||
|
updateClaudeApiProfile: (id: string, updates: Partial<ClaudeApiProfile>) => Promise<void>;
|
||||||
|
deleteClaudeApiProfile: (id: string) => Promise<void>;
|
||||||
|
setActiveClaudeApiProfile: (id: string | null) => Promise<void>;
|
||||||
|
setClaudeApiProfiles: (profiles: ClaudeApiProfile[]) => Promise<void>;
|
||||||
|
|
||||||
|
// MCP Server actions
|
||||||
|
addMCPServer: (server: Omit<MCPServerConfig, 'id'>) => void;
|
||||||
|
updateMCPServer: (id: string, updates: Partial<MCPServerConfig>) => void;
|
||||||
|
removeMCPServer: (id: string) => void;
|
||||||
|
reorderMCPServers: (oldIndex: number, newIndex: number) => void;
|
||||||
|
|
||||||
|
// Project Analysis actions
|
||||||
|
setProjectAnalysis: (analysis: ProjectAnalysis | null) => void;
|
||||||
|
setIsAnalyzing: (analyzing: boolean) => void;
|
||||||
|
clearAnalysis: () => void;
|
||||||
|
|
||||||
|
// Agent Session actions
|
||||||
|
setLastSelectedSession: (projectPath: string, sessionId: string | null) => void;
|
||||||
|
getLastSelectedSession: (projectPath: string) => string | null;
|
||||||
|
|
||||||
|
// Board Background actions
|
||||||
|
setBoardBackground: (projectPath: string, imagePath: string | null) => void;
|
||||||
|
setCardOpacity: (projectPath: string, opacity: number) => void;
|
||||||
|
setColumnOpacity: (projectPath: string, opacity: number) => void;
|
||||||
|
setColumnBorderEnabled: (projectPath: string, enabled: boolean) => void;
|
||||||
|
getBoardBackground: (projectPath: string) => {
|
||||||
|
imagePath: string | null;
|
||||||
|
cardOpacity: number;
|
||||||
|
columnOpacity: number;
|
||||||
|
columnBorderEnabled: boolean;
|
||||||
|
cardGlassmorphism: boolean;
|
||||||
|
cardBorderEnabled: boolean;
|
||||||
|
cardBorderOpacity: number;
|
||||||
|
hideScrollbar: boolean;
|
||||||
|
};
|
||||||
|
setCardGlassmorphism: (projectPath: string, enabled: boolean) => void;
|
||||||
|
setCardBorderEnabled: (projectPath: string, enabled: boolean) => void;
|
||||||
|
setCardBorderOpacity: (projectPath: string, opacity: number) => void;
|
||||||
|
setHideScrollbar: (projectPath: string, hide: boolean) => void;
|
||||||
|
clearBoardBackground: (projectPath: string) => void;
|
||||||
|
|
||||||
|
// Terminal actions
|
||||||
|
setTerminalUnlocked: (unlocked: boolean, token?: string) => void;
|
||||||
|
setActiveTerminalSession: (sessionId: string | null) => void;
|
||||||
|
toggleTerminalMaximized: (sessionId: string) => void;
|
||||||
|
addTerminalToLayout: (
|
||||||
|
sessionId: string,
|
||||||
|
direction?: 'horizontal' | 'vertical',
|
||||||
|
targetSessionId?: string,
|
||||||
|
branchName?: string
|
||||||
|
) => void;
|
||||||
|
removeTerminalFromLayout: (sessionId: string) => void;
|
||||||
|
swapTerminals: (sessionId1: string, sessionId2: string) => void;
|
||||||
|
clearTerminalState: () => void;
|
||||||
|
setTerminalPanelFontSize: (sessionId: string, fontSize: number) => void;
|
||||||
|
setTerminalDefaultFontSize: (fontSize: number) => void;
|
||||||
|
setTerminalDefaultRunScript: (script: string) => void;
|
||||||
|
setTerminalScreenReaderMode: (enabled: boolean) => void;
|
||||||
|
setTerminalFontFamily: (fontFamily: string) => void;
|
||||||
|
setTerminalScrollbackLines: (lines: number) => void;
|
||||||
|
setTerminalLineHeight: (lineHeight: number) => void;
|
||||||
|
setTerminalMaxSessions: (maxSessions: number) => void;
|
||||||
|
setTerminalLastActiveProjectPath: (projectPath: string | null) => void;
|
||||||
|
setOpenTerminalMode: (mode: 'newTab' | 'split') => void;
|
||||||
|
addTerminalTab: (name?: string) => string;
|
||||||
|
removeTerminalTab: (tabId: string) => void;
|
||||||
|
setActiveTerminalTab: (tabId: string) => void;
|
||||||
|
renameTerminalTab: (tabId: string, name: string) => void;
|
||||||
|
reorderTerminalTabs: (fromTabId: string, toTabId: string) => void;
|
||||||
|
moveTerminalToTab: (sessionId: string, targetTabId: string | 'new') => void;
|
||||||
|
addTerminalToTab: (
|
||||||
|
sessionId: string,
|
||||||
|
tabId: string,
|
||||||
|
direction?: 'horizontal' | 'vertical',
|
||||||
|
branchName?: string
|
||||||
|
) => void;
|
||||||
|
setTerminalTabLayout: (
|
||||||
|
tabId: string,
|
||||||
|
layout: TerminalPanelContent,
|
||||||
|
activeSessionId?: string
|
||||||
|
) => void;
|
||||||
|
updateTerminalPanelSizes: (tabId: string, panelKeys: string[], sizes: number[]) => void;
|
||||||
|
saveTerminalLayout: (projectPath: string) => void;
|
||||||
|
getPersistedTerminalLayout: (projectPath: string) => PersistedTerminalState | null;
|
||||||
|
clearPersistedTerminalLayout: (projectPath: string) => void;
|
||||||
|
|
||||||
|
// Spec Creation actions
|
||||||
|
setSpecCreatingForProject: (projectPath: string | null) => void;
|
||||||
|
isSpecCreatingForProject: (projectPath: string) => boolean;
|
||||||
|
|
||||||
|
setDefaultPlanningMode: (mode: PlanningMode) => void;
|
||||||
|
setDefaultRequirePlanApproval: (require: boolean) => void;
|
||||||
|
setDefaultFeatureModel: (entry: PhaseModelEntry) => void;
|
||||||
|
|
||||||
|
// Plan Approval actions
|
||||||
|
setPendingPlanApproval: (
|
||||||
|
approval: {
|
||||||
|
featureId: string;
|
||||||
|
projectPath: string;
|
||||||
|
planContent: string;
|
||||||
|
planningMode: 'lite' | 'spec' | 'full';
|
||||||
|
} | null
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// Pipeline actions
|
||||||
|
setPipelineConfig: (projectPath: string, config: PipelineConfig) => void;
|
||||||
|
getPipelineConfig: (projectPath: string) => PipelineConfig | null;
|
||||||
|
addPipelineStep: (
|
||||||
|
projectPath: string,
|
||||||
|
step: Omit<PipelineStep, 'id' | 'createdAt' | 'updatedAt'>
|
||||||
|
) => PipelineStep;
|
||||||
|
updatePipelineStep: (
|
||||||
|
projectPath: string,
|
||||||
|
stepId: string,
|
||||||
|
updates: Partial<Omit<PipelineStep, 'id' | 'createdAt'>>
|
||||||
|
) => void;
|
||||||
|
deletePipelineStep: (projectPath: string, stepId: string) => void;
|
||||||
|
reorderPipelineSteps: (projectPath: string, stepIds: string[]) => void;
|
||||||
|
|
||||||
|
// Worktree Panel Visibility actions (per-project)
|
||||||
|
setWorktreePanelVisible: (projectPath: string, visible: boolean) => void;
|
||||||
|
getWorktreePanelVisible: (projectPath: string) => boolean;
|
||||||
|
|
||||||
|
// Init Script Indicator Visibility actions (per-project)
|
||||||
|
setShowInitScriptIndicator: (projectPath: string, visible: boolean) => void;
|
||||||
|
getShowInitScriptIndicator: (projectPath: string) => boolean;
|
||||||
|
|
||||||
|
// Default Delete Branch actions (per-project)
|
||||||
|
setDefaultDeleteBranch: (projectPath: string, deleteBranch: boolean) => void;
|
||||||
|
getDefaultDeleteBranch: (projectPath: string) => boolean;
|
||||||
|
|
||||||
|
// Auto-dismiss Init Script Indicator actions (per-project)
|
||||||
|
setAutoDismissInitScriptIndicator: (projectPath: string, autoDismiss: boolean) => void;
|
||||||
|
getAutoDismissInitScriptIndicator: (projectPath: string) => boolean;
|
||||||
|
|
||||||
|
// Use Worktrees Override actions (per-project)
|
||||||
|
setProjectUseWorktrees: (projectPath: string, useWorktrees: boolean | null) => void; // null = use global
|
||||||
|
getProjectUseWorktrees: (projectPath: string) => boolean | undefined; // undefined = using global
|
||||||
|
getEffectiveUseWorktrees: (projectPath: string) => boolean; // Returns actual value (project or global fallback)
|
||||||
|
|
||||||
|
// UI State actions (previously in localStorage, now synced via API)
|
||||||
|
setWorktreePanelCollapsed: (collapsed: boolean) => void;
|
||||||
|
setLastProjectDir: (dir: string) => void;
|
||||||
|
setRecentFolders: (folders: string[]) => void;
|
||||||
|
addRecentFolder: (folder: string) => void;
|
||||||
|
|
||||||
|
// Claude Usage Tracking actions
|
||||||
|
setClaudeRefreshInterval: (interval: number) => void;
|
||||||
|
setClaudeUsageLastUpdated: (timestamp: number) => void;
|
||||||
|
setClaudeUsage: (usage: ClaudeUsage | null) => void;
|
||||||
|
|
||||||
|
// Codex Usage Tracking actions
|
||||||
|
setCodexUsage: (usage: CodexUsage | null) => void;
|
||||||
|
|
||||||
|
// Codex Models actions
|
||||||
|
fetchCodexModels: (forceRefresh?: boolean) => Promise<void>;
|
||||||
|
setCodexModels: (
|
||||||
|
models: Array<{
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
hasThinking: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: 'premium' | 'standard' | 'basic';
|
||||||
|
isDefault: boolean;
|
||||||
|
}>
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
// OpenCode Models actions
|
||||||
|
fetchOpencodeModels: (forceRefresh?: boolean) => Promise<void>;
|
||||||
|
|
||||||
|
// Init Script State actions (keyed by projectPath::branch to support concurrent scripts)
|
||||||
|
setInitScriptState: (
|
||||||
|
projectPath: string,
|
||||||
|
branch: string,
|
||||||
|
state: Partial<InitScriptState>
|
||||||
|
) => void;
|
||||||
|
appendInitScriptOutput: (projectPath: string, branch: string, content: string) => void;
|
||||||
|
clearInitScriptState: (projectPath: string, branch: string) => void;
|
||||||
|
getInitScriptState: (projectPath: string, branch: string) => InitScriptState | null;
|
||||||
|
getInitScriptStatesForProject: (
|
||||||
|
projectPath: string
|
||||||
|
) => Array<{ key: string; state: InitScriptState }>;
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
82
apps/ui/src/store/types/terminal-types.ts
Normal file
82
apps/ui/src/store/types/terminal-types.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Terminal panel layout types (recursive for splits)
|
||||||
|
export type TerminalPanelContent =
|
||||||
|
| { type: 'terminal'; sessionId: string; size?: number; fontSize?: number; branchName?: string }
|
||||||
|
| { type: 'testRunner'; sessionId: string; size?: number; worktreePath: string }
|
||||||
|
| {
|
||||||
|
type: 'split';
|
||||||
|
id: string; // Stable ID for React key stability
|
||||||
|
direction: 'horizontal' | 'vertical';
|
||||||
|
panels: TerminalPanelContent[];
|
||||||
|
size?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Terminal tab - each tab has its own layout
|
||||||
|
export interface TerminalTab {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
layout: TerminalPanelContent | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TerminalState {
|
||||||
|
isUnlocked: boolean;
|
||||||
|
authToken: string | null;
|
||||||
|
tabs: TerminalTab[];
|
||||||
|
activeTabId: string | null;
|
||||||
|
activeSessionId: string | null;
|
||||||
|
maximizedSessionId: string | null; // Session ID of the maximized terminal pane (null if none)
|
||||||
|
defaultFontSize: number; // Default font size for new terminals
|
||||||
|
defaultRunScript: string; // Script to run when a new terminal is created (e.g., "claude" to start Claude Code)
|
||||||
|
screenReaderMode: boolean; // Enable screen reader accessibility mode
|
||||||
|
fontFamily: string; // Font family for terminal text
|
||||||
|
scrollbackLines: number; // Number of lines to keep in scrollback buffer
|
||||||
|
lineHeight: number; // Line height multiplier for terminal text
|
||||||
|
maxSessions: number; // Maximum concurrent terminal sessions (server setting)
|
||||||
|
lastActiveProjectPath: string | null; // Last project path to detect route changes vs project switches
|
||||||
|
openTerminalMode: 'newTab' | 'split'; // How to open terminals from "Open in Terminal" action
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persisted terminal layout - now includes sessionIds for reconnection
|
||||||
|
// Used to restore terminal layout structure when switching projects
|
||||||
|
export type PersistedTerminalPanel =
|
||||||
|
| { type: 'terminal'; size?: number; fontSize?: number; sessionId?: string; branchName?: string }
|
||||||
|
| { type: 'testRunner'; size?: number; sessionId?: string; worktreePath?: string }
|
||||||
|
| {
|
||||||
|
type: 'split';
|
||||||
|
id?: string; // Optional for backwards compatibility with older persisted layouts
|
||||||
|
direction: 'horizontal' | 'vertical';
|
||||||
|
panels: PersistedTerminalPanel[];
|
||||||
|
size?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to generate unique split IDs
|
||||||
|
export const generateSplitId = () =>
|
||||||
|
`split-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
|
export interface PersistedTerminalTab {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
layout: PersistedTerminalPanel | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistedTerminalState {
|
||||||
|
tabs: PersistedTerminalTab[];
|
||||||
|
activeTabIndex: number; // Use index instead of ID since IDs are regenerated
|
||||||
|
defaultFontSize: number;
|
||||||
|
defaultRunScript?: string; // Optional to support existing persisted data
|
||||||
|
screenReaderMode?: boolean; // Optional to support existing persisted data
|
||||||
|
fontFamily?: string; // Optional to support existing persisted data
|
||||||
|
scrollbackLines?: number; // Optional to support existing persisted data
|
||||||
|
lineHeight?: number; // Optional to support existing persisted data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persisted terminal settings - stored globally (not per-project)
|
||||||
|
export interface PersistedTerminalSettings {
|
||||||
|
defaultFontSize: number;
|
||||||
|
defaultRunScript: string;
|
||||||
|
screenReaderMode: boolean;
|
||||||
|
fontFamily: string;
|
||||||
|
scrollbackLines: number;
|
||||||
|
lineHeight: number;
|
||||||
|
maxSessions: number;
|
||||||
|
openTerminalMode: 'newTab' | 'split';
|
||||||
|
}
|
||||||
222
apps/ui/src/store/types/ui-types.ts
Normal file
222
apps/ui/src/store/types/ui-types.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
export type ViewMode =
|
||||||
|
| 'welcome'
|
||||||
|
| 'setup'
|
||||||
|
| 'spec'
|
||||||
|
| 'board'
|
||||||
|
| 'agent'
|
||||||
|
| 'settings'
|
||||||
|
| 'interview'
|
||||||
|
| 'context'
|
||||||
|
| 'running-agents'
|
||||||
|
| 'terminal'
|
||||||
|
| 'wiki'
|
||||||
|
| 'ideation';
|
||||||
|
|
||||||
|
export type ThemeMode =
|
||||||
|
// Special modes
|
||||||
|
| 'system'
|
||||||
|
// Dark themes
|
||||||
|
| 'dark'
|
||||||
|
| 'retro'
|
||||||
|
| 'dracula'
|
||||||
|
| 'nord'
|
||||||
|
| 'monokai'
|
||||||
|
| 'tokyonight'
|
||||||
|
| 'solarized'
|
||||||
|
| 'gruvbox'
|
||||||
|
| 'catppuccin'
|
||||||
|
| 'onedark'
|
||||||
|
| 'synthwave'
|
||||||
|
| 'red'
|
||||||
|
| 'sunset'
|
||||||
|
| 'gray'
|
||||||
|
| 'forest'
|
||||||
|
| 'ocean'
|
||||||
|
| 'ember'
|
||||||
|
| 'ayu-dark'
|
||||||
|
| 'ayu-mirage'
|
||||||
|
| 'matcha'
|
||||||
|
// Light themes
|
||||||
|
| 'light'
|
||||||
|
| 'cream'
|
||||||
|
| 'solarizedlight'
|
||||||
|
| 'github'
|
||||||
|
| 'paper'
|
||||||
|
| 'rose'
|
||||||
|
| 'mint'
|
||||||
|
| 'lavender'
|
||||||
|
| 'sand'
|
||||||
|
| 'sky'
|
||||||
|
| 'peach'
|
||||||
|
| 'snow'
|
||||||
|
| 'sepia'
|
||||||
|
| 'gruvboxlight'
|
||||||
|
| 'nordlight'
|
||||||
|
| 'blossom'
|
||||||
|
| 'ayu-light'
|
||||||
|
| 'onelight'
|
||||||
|
| 'bluloco'
|
||||||
|
| 'feather';
|
||||||
|
|
||||||
|
export type BoardViewMode = 'kanban' | 'graph';
|
||||||
|
|
||||||
|
// Keyboard Shortcut with optional modifiers
|
||||||
|
export interface ShortcutKey {
|
||||||
|
key: string; // The main key (e.g., "K", "N", "1")
|
||||||
|
shift?: boolean; // Shift key modifier
|
||||||
|
cmdCtrl?: boolean; // Cmd on Mac, Ctrl on Windows/Linux
|
||||||
|
alt?: boolean; // Alt/Option key modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to parse shortcut string to ShortcutKey object
|
||||||
|
export function parseShortcut(shortcut: string | undefined | null): ShortcutKey {
|
||||||
|
if (!shortcut) return { key: '' };
|
||||||
|
const parts = shortcut.split('+').map((p) => p.trim());
|
||||||
|
const result: ShortcutKey = { key: parts[parts.length - 1] };
|
||||||
|
|
||||||
|
// Normalize common OS-specific modifiers (Cmd/Ctrl/Win/Super symbols) into cmdCtrl
|
||||||
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
|
const modifier = parts[i].toLowerCase();
|
||||||
|
if (modifier === 'shift') result.shift = true;
|
||||||
|
else if (
|
||||||
|
modifier === 'cmd' ||
|
||||||
|
modifier === 'ctrl' ||
|
||||||
|
modifier === 'win' ||
|
||||||
|
modifier === 'super' ||
|
||||||
|
modifier === '⌘' ||
|
||||||
|
modifier === '^' ||
|
||||||
|
modifier === '⊞' ||
|
||||||
|
modifier === '◆'
|
||||||
|
)
|
||||||
|
result.cmdCtrl = true;
|
||||||
|
else if (modifier === 'alt' || modifier === 'opt' || modifier === 'option' || modifier === '⌥')
|
||||||
|
result.alt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to format ShortcutKey to display string
|
||||||
|
export function formatShortcut(shortcut: string | undefined | null, forDisplay = false): string {
|
||||||
|
if (!shortcut) return '';
|
||||||
|
const parsed = parseShortcut(shortcut);
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
// Prefer User-Agent Client Hints when available; fall back to legacy
|
||||||
|
const platform: 'darwin' | 'win32' | 'linux' = (() => {
|
||||||
|
if (typeof navigator === 'undefined') return 'linux';
|
||||||
|
|
||||||
|
const uaPlatform = (
|
||||||
|
navigator as Navigator & { userAgentData?: { platform?: string } }
|
||||||
|
).userAgentData?.platform?.toLowerCase?.();
|
||||||
|
const legacyPlatform = navigator.platform?.toLowerCase?.();
|
||||||
|
const platformString = uaPlatform || legacyPlatform || '';
|
||||||
|
|
||||||
|
if (platformString.includes('mac')) return 'darwin';
|
||||||
|
if (platformString.includes('win')) return 'win32';
|
||||||
|
return 'linux';
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Primary modifier - OS-specific
|
||||||
|
if (parsed.cmdCtrl) {
|
||||||
|
if (forDisplay) {
|
||||||
|
parts.push(platform === 'darwin' ? '⌘' : platform === 'win32' ? '⊞' : '◆');
|
||||||
|
} else {
|
||||||
|
parts.push(platform === 'darwin' ? 'Cmd' : platform === 'win32' ? 'Win' : 'Super');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alt/Option
|
||||||
|
if (parsed.alt) {
|
||||||
|
parts.push(
|
||||||
|
forDisplay ? (platform === 'darwin' ? '⌥' : 'Alt') : platform === 'darwin' ? 'Opt' : 'Alt'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift
|
||||||
|
if (parsed.shift) {
|
||||||
|
parts.push(forDisplay ? '⇧' : 'Shift');
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(parsed.key.toUpperCase());
|
||||||
|
|
||||||
|
// Add spacing when displaying symbols
|
||||||
|
return parts.join(forDisplay ? ' ' : '+');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard Shortcuts - stored as strings like "K", "Shift+N", "Cmd+K"
|
||||||
|
export interface KeyboardShortcuts {
|
||||||
|
// Navigation shortcuts
|
||||||
|
board: string;
|
||||||
|
graph: string;
|
||||||
|
agent: string;
|
||||||
|
spec: string;
|
||||||
|
context: string;
|
||||||
|
memory: string;
|
||||||
|
settings: string;
|
||||||
|
projectSettings: string;
|
||||||
|
terminal: string;
|
||||||
|
ideation: string;
|
||||||
|
notifications: string;
|
||||||
|
githubIssues: string;
|
||||||
|
githubPrs: string;
|
||||||
|
|
||||||
|
// UI shortcuts
|
||||||
|
toggleSidebar: string;
|
||||||
|
|
||||||
|
// Action shortcuts
|
||||||
|
addFeature: string;
|
||||||
|
addContextFile: string;
|
||||||
|
startNext: string;
|
||||||
|
newSession: string;
|
||||||
|
openProject: string;
|
||||||
|
projectPicker: string;
|
||||||
|
cyclePrevProject: string;
|
||||||
|
cycleNextProject: string;
|
||||||
|
|
||||||
|
// Terminal shortcuts
|
||||||
|
splitTerminalRight: string;
|
||||||
|
splitTerminalDown: string;
|
||||||
|
closeTerminal: string;
|
||||||
|
newTerminalTab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default keyboard shortcuts
|
||||||
|
export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
|
||||||
|
// Navigation
|
||||||
|
board: 'K',
|
||||||
|
graph: 'H',
|
||||||
|
agent: 'A',
|
||||||
|
spec: 'D',
|
||||||
|
context: 'C',
|
||||||
|
memory: 'Y',
|
||||||
|
settings: 'S',
|
||||||
|
projectSettings: 'Shift+S',
|
||||||
|
terminal: 'T',
|
||||||
|
ideation: 'I',
|
||||||
|
notifications: 'X',
|
||||||
|
githubIssues: 'G',
|
||||||
|
githubPrs: 'R',
|
||||||
|
|
||||||
|
// UI
|
||||||
|
toggleSidebar: '`',
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
// Note: Some shortcuts share the same key (e.g., "N" for addFeature, newSession)
|
||||||
|
// This is intentional as they are context-specific and only active in their respective views
|
||||||
|
addFeature: 'N', // Only active in board view
|
||||||
|
addContextFile: 'N', // Only active in context view
|
||||||
|
startNext: 'G', // Only active in board view
|
||||||
|
newSession: 'N', // Only active in agent view
|
||||||
|
openProject: 'O', // Global shortcut
|
||||||
|
projectPicker: 'P', // Global shortcut
|
||||||
|
cyclePrevProject: 'Q', // Global shortcut
|
||||||
|
cycleNextProject: 'E', // Global shortcut
|
||||||
|
|
||||||
|
// Terminal shortcuts (only active in terminal view)
|
||||||
|
// Using Alt modifier to avoid conflicts with both terminal signals AND browser shortcuts
|
||||||
|
splitTerminalRight: 'Alt+D',
|
||||||
|
splitTerminalDown: 'Alt+S',
|
||||||
|
closeTerminal: 'Alt+W',
|
||||||
|
newTerminalTab: 'Alt+T',
|
||||||
|
};
|
||||||
93
apps/ui/src/store/types/usage-types.ts
Normal file
93
apps/ui/src/store/types/usage-types.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Claude Usage interface matching the server response
|
||||||
|
export type ClaudeUsage = {
|
||||||
|
sessionTokensUsed: number;
|
||||||
|
sessionLimit: number;
|
||||||
|
sessionPercentage: number;
|
||||||
|
sessionResetTime: string;
|
||||||
|
sessionResetText: string;
|
||||||
|
|
||||||
|
weeklyTokensUsed: number;
|
||||||
|
weeklyLimit: number;
|
||||||
|
weeklyPercentage: number;
|
||||||
|
weeklyResetTime: string;
|
||||||
|
weeklyResetText: string;
|
||||||
|
|
||||||
|
sonnetWeeklyTokensUsed: number;
|
||||||
|
sonnetWeeklyPercentage: number;
|
||||||
|
sonnetResetText: string;
|
||||||
|
|
||||||
|
costUsed: number | null;
|
||||||
|
costLimit: number | null;
|
||||||
|
costCurrency: string | null;
|
||||||
|
|
||||||
|
lastUpdated: string;
|
||||||
|
userTimezone: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Response type for Claude usage API (can be success or error)
|
||||||
|
export type ClaudeUsageResponse = ClaudeUsage | { error: string; message?: string };
|
||||||
|
|
||||||
|
// Codex Usage types
|
||||||
|
export type CodexPlanType =
|
||||||
|
| 'free'
|
||||||
|
| 'plus'
|
||||||
|
| 'pro'
|
||||||
|
| 'team'
|
||||||
|
| 'business'
|
||||||
|
| 'enterprise'
|
||||||
|
| 'edu'
|
||||||
|
| 'unknown';
|
||||||
|
|
||||||
|
export interface CodexRateLimitWindow {
|
||||||
|
limit: number;
|
||||||
|
used: number;
|
||||||
|
remaining: number;
|
||||||
|
usedPercent: number; // Percentage used (0-100)
|
||||||
|
windowDurationMins: number; // Duration in minutes
|
||||||
|
resetsAt: number; // Unix timestamp in seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexUsage {
|
||||||
|
rateLimits: {
|
||||||
|
primary?: CodexRateLimitWindow;
|
||||||
|
secondary?: CodexRateLimitWindow;
|
||||||
|
planType?: CodexPlanType;
|
||||||
|
} | null;
|
||||||
|
lastUpdated: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response type for Codex usage API (can be success or error)
|
||||||
|
export type CodexUsageResponse = CodexUsage | { error: string; message?: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Claude usage is at its limit (any of: session >= 100%, weekly >= 100%, OR cost >= limit)
|
||||||
|
* Returns true if any limit is reached, meaning auto mode should pause feature pickup.
|
||||||
|
*/
|
||||||
|
export function isClaudeUsageAtLimit(claudeUsage: ClaudeUsage | null): boolean {
|
||||||
|
if (!claudeUsage) {
|
||||||
|
// No usage data available - don't block
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check session limit (5-hour window)
|
||||||
|
if (claudeUsage.sessionPercentage >= 100) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check weekly limit
|
||||||
|
if (claudeUsage.weeklyPercentage >= 100) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cost limit (if configured)
|
||||||
|
if (
|
||||||
|
claudeUsage.costLimit !== null &&
|
||||||
|
claudeUsage.costLimit > 0 &&
|
||||||
|
claudeUsage.costUsed !== null &&
|
||||||
|
claudeUsage.costUsed >= claudeUsage.costLimit
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user