feat: add terminal panel components and Zustand store for state management

- Created a barrel export file for terminal panel components.
- Implemented Zustand store for managing terminal panel UI state, including visibility, active terminal, view mode, and terminal ordering.
- Added actions for opening/closing the terminal panel, setting the active terminal, changing view modes, and managing terminal order.
- Introduced selectors for accessing terminal panel state properties.
This commit is contained in:
catlog22
2026-02-12 23:53:11 +08:00
parent e44a97e812
commit ddbe12b7af
72 changed files with 1055 additions and 254 deletions

View File

@@ -37,14 +37,6 @@ type ModeType = 'provider-based' | 'direct';
// ========== Helper Functions ========== // ========== Helper Functions ==========
function safeStringifyConfig(config: unknown): string {
try {
return JSON.stringify(config ?? {}, null, 2);
} catch {
return '{}';
}
}
function parseConfigJson( function parseConfigJson(
configJson: string configJson: string
): { ok: true; value: Record<string, unknown> } | { ok: false; errorKey: string } { ): { ok: true; value: Record<string, unknown> } | { ok: false; errorKey: string } {

View File

@@ -127,7 +127,7 @@ describe('ExecutionGroup', () => {
render(<ExecutionGroup {...defaultProps} type="parallel" />, { locale: 'en' }); render(<ExecutionGroup {...defaultProps} type="parallel" />, { locale: 'en' });
// Parallel items should not have numbers in the numbering position // Parallel items should not have numbers in the numbering position
const numberElements = document.querySelectorAll('.text-muted-foreground.text-xs'); document.querySelectorAll('.text-muted-foreground.text-xs');
// In parallel mode, the numbering position should be empty // In parallel mode, the numbering position should be empty
}); });
}); });

View File

@@ -17,8 +17,13 @@ describe('QueueCard', () => {
}; };
const mockQueue: IssueQueue = { const mockQueue: IssueQueue = {
tasks: ['task1', 'task2'], tasks: [
solutions: ['solution1'], { item_id: 'task1', issue_id: 'issue-1', solution_id: 'sol-1', status: 'pending', execution_order: 1, execution_group: 'group-1', depends_on: [], semantic_priority: 1 },
{ item_id: 'task2', issue_id: 'issue-1', solution_id: 'sol-1', status: 'pending', execution_order: 2, execution_group: 'group-1', depends_on: [], semantic_priority: 1 },
],
solutions: [
{ item_id: 'solution1', issue_id: 'issue-1', solution_id: 'sol-1', status: 'pending', execution_order: 1, execution_group: 'group-1', depends_on: [], semantic_priority: 1 },
],
conflicts: [], conflicts: [],
execution_groups: ['group-1'], execution_groups: ['group-1'],
grouped_items: mockQueueItems, grouped_items: mockQueueItems,

View File

@@ -127,10 +127,6 @@ export function AppShell({
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
const handleMenuClick = useCallback(() => {
setMobileOpen((prev) => !prev);
}, []);
const handleMobileClose = useCallback(() => { const handleMobileClose = useCallback(() => {
setMobileOpen(false); setMobileOpen(false);
}, []); }, []);

View File

@@ -42,7 +42,7 @@ describe('Header Component - i18n Tests', () => {
describe('translated aria-labels', () => { describe('translated aria-labels', () => {
it('should have translated aria-label for menu toggle', () => { it('should have translated aria-label for menu toggle', () => {
render(<Header onMenuClick={vi.fn()} />); render(<Header />);
const menuButton = screen.getByRole('button', { name: /toggle navigation/i }); const menuButton = screen.getByRole('button', { name: /toggle navigation/i });
expect(menuButton).toBeInTheDocument(); expect(menuButton).toBeInTheDocument();

View File

@@ -15,8 +15,6 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from '@/components/ui/AlertDialog'; } from '@/components/ui/AlertDialog';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
// ========== Types ========== // ========== Types ==========

View File

@@ -30,8 +30,6 @@ interface ServerCheckboxItem {
selected: boolean; selected: boolean;
} }
type CopyDirection = 'to-codex' | 'from-codex';
// ========== Component ========== // ========== Component ==========
export function CrossCliSyncPanel({ onSuccess, className }: CrossCliSyncPanelProps) { export function CrossCliSyncPanel({ onSuccess, className }: CrossCliSyncPanelProps) {

View File

@@ -10,7 +10,6 @@ import {
Globe, Globe,
Sparkles, Sparkles,
Download, Download,
Check,
Settings, Settings,
Key, Key,
Zap, Zap,

View File

@@ -21,7 +21,6 @@ import {
Loader2, Loader2,
RotateCcw, RotateCcw,
Code, Code,
Image as ImageIcon,
Database, Database,
Mail, Mail,
MailOpen, MailOpen,
@@ -67,15 +66,7 @@ function formatTimeAgo(timestamp: string, formatMessage: (message: { id: string;
return new Date(timestamp).toLocaleDateString(); return new Date(timestamp).toLocaleDateString();
} }
function formatDetails(details: unknown): string { // ========== Main Types ==========
// eslint-disable-next-line @typescript-eslint/no-unused-vars
if (typeof details === 'string') return details;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
if (typeof details === 'object' && details !== null) {
return JSON.stringify(details, null, 2);
}
return String(details);
}
function getNotificationIcon(type: Toast['type']) { function getNotificationIcon(type: Toast['type']) {
const iconClassName = 'h-4 w-4 shrink-0'; const iconClassName = 'h-4 w-4 shrink-0';
@@ -718,7 +709,6 @@ export interface NotificationPanelProps {
} }
export function NotificationPanel({ isOpen, onClose }: NotificationPanelProps) { export function NotificationPanel({ isOpen, onClose }: NotificationPanelProps) {
const { formatMessage } = useIntl();
// Store state // Store state
const persistentNotifications = useNotificationStore(selectPersistentNotifications); const persistentNotifications = useNotificationStore(selectPersistentNotifications);

View File

@@ -15,6 +15,7 @@ import {
Terminal, Terminal,
Wrench, Wrench,
FileEdit, FileEdit,
FileText,
Brain, Brain,
Search, Search,
} from 'lucide-react'; } from 'lucide-react';

View File

@@ -3,7 +3,7 @@
// ======================================== // ========================================
// Vertical timeline displaying tool calls in chronological order // Vertical timeline displaying tool calls in chronological order
import React, { memo, useMemo, useCallback, useEffect, useRef } from 'react'; import { memo, useMemo, useCallback, useEffect, useRef } from 'react';
import { Wrench, Loader2 } from 'lucide-react'; import { Wrench, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ToolCallCard } from './ToolCallCard'; import { ToolCallCard } from './ToolCallCard';

View File

@@ -10,12 +10,9 @@ import {
Info, Info,
Code, Code,
Copy, Copy,
ChevronRight,
AlertTriangle, AlertTriangle,
Brain, Brain,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { JsonField } from './JsonField'; import { JsonField } from './JsonField';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
@@ -94,8 +91,6 @@ const TYPE_CONFIGS: Record<string, TypeConfig> = {
export function JsonCard({ export function JsonCard({
data, data,
type, type,
timestamp,
onCopy,
}: JsonCardProps) { }: JsonCardProps) {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(true);

View File

@@ -9,7 +9,7 @@ export interface JsonFieldProps {
export function JsonField({ fieldName, value }: JsonFieldProps) { export function JsonField({ fieldName, value }: JsonFieldProps) {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [copied, setCopied] = useState(false); const [, setCopied] = useState(false);
const isObject = value !== null && typeof value === 'object'; const isObject = value !== null && typeof value === 'object';
const isNested = isObject && (Array.isArray(value) || Object.keys(value).length > 0); const isNested = isObject && (Array.isArray(value) || Object.keys(value).length > 0);

View File

@@ -5,7 +5,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Brain, Settings, AlertCircle, Info, MessageCircle, Wrench } from 'lucide-react'; import { Brain, Settings, AlertCircle, Info, MessageCircle, Wrench } from 'lucide-react';
import { cn } from '@/lib/utils';
import { JsonCard } from './JsonCard'; import { JsonCard } from './JsonCard';
import { detectJsonInLine } from '../utils/jsonDetector'; import { detectJsonInLine } from '../utils/jsonDetector';

View File

@@ -91,7 +91,6 @@ export function AssistantMessage({
onCopy, onCopy,
className className
}: AssistantMessageProps) { }: AssistantMessageProps) {
const { formatMessage } = useIntl();
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(true);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);

View File

@@ -19,7 +19,6 @@ export interface ErrorMessageProps {
export function ErrorMessage({ export function ErrorMessage({
title, title,
message, message,
timestamp,
onRetry, onRetry,
onDismiss, onDismiss,
className className

View File

@@ -18,7 +18,6 @@ export interface UserMessageProps {
export function UserMessage({ export function UserMessage({
content, content,
timestamp,
onCopy, onCopy,
onViewRaw, onViewRaw,
className className

View File

@@ -154,7 +154,7 @@ interface OutputLineCardProps {
onCopy?: (content: string) => void; onCopy?: (content: string) => void;
} }
function OutputLineCard({ group, onCopy }: OutputLineCardProps) { function OutputLineCard({ group }: OutputLineCardProps) {
const borderColor = getBorderColorForType(group.type); const borderColor = getBorderColorForType(group.type);
// Extract content from all lines in the group // Extract content from all lines in the group
@@ -357,12 +357,6 @@ export function CliStreamMonitor({ isOpen, onClose }: CliStreamMonitorProps) {
}, 50); // 50ms debounce }, 50); // 50ms debounce
}, []); }, []);
// Scroll to bottom handler
const scrollToBottom = useCallback(() => {
logsEndRef.current?.scrollIntoView({ behavior: 'smooth' });
setIsUserScrolling(false);
}, []);
// Handle closing an execution tab // Handle closing an execution tab
const handleCloseExecution = useCallback((executionId: string) => { const handleCloseExecution = useCallback((executionId: string) => {
// Mark as closed by user so it won't be re-added by server sync // Mark as closed by user so it won't be re-added by server sync

View File

@@ -3,7 +3,6 @@
// ======================================== // ========================================
// Display detailed view of a single insight with patterns, suggestions, and metadata // Display detailed view of a single insight with patterns, suggestions, and metadata
import * as React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import {
@@ -133,8 +132,7 @@ function formatRelativeTime(timestamp: string, locale: string): string {
/** /**
* PatternItem component for displaying a single pattern * PatternItem component for displaying a single pattern
*/ */
function PatternItem({ pattern, locale }: { pattern: Pattern; locale: string }) { function PatternItem({ pattern }: { pattern: Pattern; locale: string }) {
const { formatMessage } = useIntl();
const severity = pattern.severity ?? 'info'; const severity = pattern.severity ?? 'info';
const config = severityConfig[severity] ?? severityConfig.default; const config = severityConfig[severity] ?? severityConfig.default;
@@ -177,7 +175,7 @@ function PatternItem({ pattern, locale }: { pattern: Pattern; locale: string })
/** /**
* SuggestionItem component for displaying a single suggestion * SuggestionItem component for displaying a single suggestion
*/ */
function SuggestionItem({ suggestion, locale }: { suggestion: Suggestion; locale: string }) { function SuggestionItem({ suggestion }: { suggestion: Suggestion; locale: string }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const config = suggestionTypeConfig[suggestion.type] ?? suggestionTypeConfig.refactor; const config = suggestionTypeConfig[suggestion.type] ?? suggestionTypeConfig.refactor;
const typeLabel = formatMessage({ id: `prompts.suggestions.types.${suggestion.type}` }); const typeLabel = formatMessage({ id: `prompts.suggestions.types.${suggestion.type}` });

View File

@@ -3,7 +3,6 @@
// ======================================== // ========================================
// AI insights panel for prompt history analysis // AI insights panel for prompt history analysis
import * as React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';

View File

@@ -2,7 +2,7 @@
// LogBlock Component // LogBlock Component
// ======================================== // ========================================
import React, { memo } from 'react'; import { memo } from 'react';
import { import {
ChevronDown, ChevronDown,
ChevronUp, ChevronUp,

View File

@@ -3,7 +3,7 @@
// ======================================== // ========================================
// Container component for displaying grouped CLI output blocks // Container component for displaying grouped CLI output blocks
import { useState, useCallback, useMemo } from 'react'; import { useState, useCallback } from 'react';
import { useCliStreamStore, type LogBlockData } from '@/stores/cliStreamStore'; import { useCliStreamStore, type LogBlockData } from '@/stores/cliStreamStore';
import { LogBlock } from './LogBlock'; import { LogBlock } from './LogBlock';

View File

@@ -3,7 +3,6 @@
// ======================================== // ========================================
// Statistics display for prompt history // Statistics display for prompt history
import * as React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { StatCard, StatCardSkeleton } from '@/components/shared/StatCard'; import { StatCard, StatCardSkeleton } from '@/components/shared/StatCard';
import { MessageSquare, FileType, Hash, Star } from 'lucide-react'; import { MessageSquare, FileType, Hash, Star } from 'lucide-react';

View File

@@ -3,14 +3,12 @@
// ======================================== // ========================================
// Real-time scrolling ticker with CSS marquee animation and WebSocket messages // Real-time scrolling ticker with CSS marquee animation and WebSocket messages
import * as React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useRealtimeUpdates, type TickerMessage } from '@/hooks/useRealtimeUpdates'; import { useRealtimeUpdates, type TickerMessage } from '@/hooks/useRealtimeUpdates';
import { import {
Play, Play,
CheckCircle2, CheckCircle2,
XCircle,
Workflow, Workflow,
Activity, Activity,
WifiOff, WifiOff,

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Badge } from '../ui/badge'; import { Badge } from '../ui/Badge';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { VersionCheckModal } from './VersionCheckModal'; import { VersionCheckModal } from './VersionCheckModal';

View File

@@ -3,7 +3,7 @@ import {
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '../ui/dialog'; } from '../ui/Dialog';
interface VersionCheckModalProps { interface VersionCheckModalProps {
currentVersion: string; currentVersion: string;

View File

@@ -4,7 +4,7 @@
// Team selector, stats chips, and controls // Team selector, stats chips, and controls
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { Users, MessageSquare, Clock, RefreshCw } from 'lucide-react'; import { Users, MessageSquare, RefreshCw } from 'lucide-react';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
import { Switch } from '@/components/ui/Switch'; import { Switch } from '@/components/ui/Switch';
import { Label } from '@/components/ui/Label'; import { Label } from '@/components/ui/Label';

View File

@@ -7,7 +7,6 @@ import { useState, useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { ChevronDown, ChevronUp, FileText, Filter, X } from 'lucide-react'; import { ChevronDown, ChevronUp, FileText, Filter, X } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/Card'; import { Card, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { import {
Select, Select,
@@ -17,7 +16,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/Select'; } from '@/components/ui/Select';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { TeamMessage, TeamMessageType, TeamMessageFilter } from '@/types/team'; import type { TeamMessage, TeamMessageFilter } from '@/types/team';
interface TeamMessageFeedProps { interface TeamMessageFeedProps {
messages: TeamMessage[]; messages: TeamMessage[];

View File

@@ -104,18 +104,6 @@ function Arrow() {
); );
} }
function ForkArrow() {
return (
<div className="flex items-center px-1">
<div className="w-4 h-0.5 bg-border" />
<div className="flex flex-col gap-1">
<div className="w-3 h-0.5 bg-border -rotate-20" />
<div className="w-3 h-0.5 bg-border rotate-20" />
</div>
</div>
);
}
export function TeamPipeline({ messages }: TeamPipelineProps) { export function TeamPipeline({ messages }: TeamPipelineProps) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const stageStatus = derivePipelineStatus(messages); const stageStatus = derivePipelineStatus(messages);

View File

@@ -0,0 +1,323 @@
// ========================================
// TerminalMainArea Component
// ========================================
// Main display area for the terminal panel.
// Shows header with session info, tab switcher (terminal/queue), and
// embedded xterm.js terminal with command input. Reuses the xterm rendering
// pattern from IssueTerminalTab (init, FitAddon, output streaming, PTY input).
import { useEffect, useRef, useState } from 'react';
import { Terminal as XTerm } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { X, Send } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/Tabs';
import { cn } from '@/lib/utils';
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
import { useTerminalPanelStore } from '@/stores/terminalPanelStore';
import { useCliSessionStore } from '@/stores/cliSessionStore';
import type { PanelView } from '@/stores/terminalPanelStore';
import {
fetchCliSessionBuffer,
sendCliSessionText,
resizeCliSession,
executeInCliSession,
} from '@/lib/api';
// ========== Types ==========
export interface TerminalMainAreaProps {
onClose: () => void;
}
// ========== Component ==========
export function TerminalMainArea({ onClose }: TerminalMainAreaProps) {
const projectPath = useWorkflowStore(selectProjectPath);
const activeTerminalId = useTerminalPanelStore((s) => s.activeTerminalId);
const panelView = useTerminalPanelStore((s) => s.panelView);
const setPanelView = useTerminalPanelStore((s) => s.setPanelView);
const sessionsByKey = useCliSessionStore((s) => s.sessions);
const outputChunks = useCliSessionStore((s) => s.outputChunks);
const setBuffer = useCliSessionStore((s) => s.setBuffer);
const clearOutput = useCliSessionStore((s) => s.clearOutput);
const activeSession = activeTerminalId ? sessionsByKey[activeTerminalId] : null;
const [prompt, setPrompt] = useState('');
const [isExecuting, setIsExecuting] = useState(false);
const [error, setError] = useState<string | null>(null);
// xterm refs
const terminalHostRef = useRef<HTMLDivElement | null>(null);
const xtermRef = useRef<XTerm | null>(null);
const fitAddonRef = useRef<FitAddon | null>(null);
const lastChunkIndexRef = useRef<number>(0);
// Input batching refs (same pattern as IssueTerminalTab)
const pendingInputRef = useRef<string>('');
const flushTimerRef = useRef<number | null>(null);
const activeSessionKeyRef = useRef<string | null>(null);
// Keep ref in sync with activeTerminalId for closures
useEffect(() => {
activeSessionKeyRef.current = activeTerminalId;
}, [activeTerminalId]);
const flushInput = async () => {
const sessionKey = activeSessionKeyRef.current;
if (!sessionKey) return;
const pending = pendingInputRef.current;
pendingInputRef.current = '';
if (!pending) return;
try {
await sendCliSessionText(sessionKey, { text: pending, appendNewline: false }, projectPath || undefined);
} catch {
// Ignore transient failures
}
};
const scheduleFlush = () => {
if (flushTimerRef.current !== null) return;
flushTimerRef.current = window.setTimeout(async () => {
flushTimerRef.current = null;
await flushInput();
}, 30);
};
// ========== xterm Initialization ==========
useEffect(() => {
if (!terminalHostRef.current) return;
if (xtermRef.current) return;
const term = new XTerm({
convertEol: true,
cursorBlink: true,
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
fontSize: 12,
scrollback: 5000,
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalHostRef.current);
fitAddon.fit();
// Forward keystrokes to backend (batched)
term.onData((data) => {
if (!activeSessionKeyRef.current) return;
pendingInputRef.current += data;
scheduleFlush();
});
xtermRef.current = term;
fitAddonRef.current = fitAddon;
return () => {
try {
term.dispose();
} finally {
xtermRef.current = null;
fitAddonRef.current = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ========== Attach to Active Session ==========
useEffect(() => {
const term = xtermRef.current;
const fitAddon = fitAddonRef.current;
if (!term || !fitAddon) return;
lastChunkIndexRef.current = 0;
term.reset();
term.clear();
if (!activeTerminalId) return;
clearOutput(activeTerminalId);
fetchCliSessionBuffer(activeTerminalId, projectPath || undefined)
.then(({ buffer }) => {
setBuffer(activeTerminalId, buffer || '');
})
.catch(() => {
// ignore
})
.finally(() => {
fitAddon.fit();
});
}, [activeTerminalId, projectPath, setBuffer, clearOutput]);
// ========== Stream Output Chunks ==========
useEffect(() => {
const term = xtermRef.current;
if (!term) return;
if (!activeTerminalId) return;
const chunks = outputChunks[activeTerminalId] ?? [];
const start = lastChunkIndexRef.current;
if (start >= chunks.length) return;
for (let i = start; i < chunks.length; i++) {
term.write(chunks[i].data);
}
lastChunkIndexRef.current = chunks.length;
}, [outputChunks, activeTerminalId]);
// ========== Resize Observer ==========
useEffect(() => {
const host = terminalHostRef.current;
const term = xtermRef.current;
const fitAddon = fitAddonRef.current;
if (!host || !term || !fitAddon) return;
const resize = () => {
fitAddon.fit();
const sessionKey = activeSessionKeyRef.current;
if (sessionKey) {
void (async () => {
try {
await resizeCliSession(sessionKey, { cols: term.cols, rows: term.rows }, projectPath || undefined);
} catch {
// ignore
}
})();
}
};
const ro = new ResizeObserver(resize);
ro.observe(host);
return () => ro.disconnect();
}, [projectPath]);
// ========== Execute Command ==========
const handleExecute = async () => {
if (!activeTerminalId) return;
if (!prompt.trim()) return;
setIsExecuting(true);
setError(null);
try {
await executeInCliSession(activeTerminalId, {
tool: activeSession?.tool || 'claude',
prompt: prompt.trim(),
mode: 'analysis',
category: 'user',
}, projectPath || undefined);
setPrompt('');
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
} finally {
setIsExecuting(false);
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Ctrl+Enter or Cmd+Enter to execute
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
void handleExecute();
}
};
// ========== Render ==========
return (
<div className="flex-1 flex flex-col min-w-0">
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-border bg-card">
<div className="flex items-center gap-3 min-w-0">
<h3 className="text-sm font-semibold text-foreground truncate">
{activeSession
? `${activeSession.tool || 'cli'} - ${activeSession.sessionKey}`
: 'Terminal Panel'}
</h3>
{activeSession?.tool && (
<span className="text-xs text-muted-foreground flex-shrink-0">
{activeSession.workingDir}
</span>
)}
</div>
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="flex-shrink-0 hover:bg-secondary"
>
<X className="h-4 w-4" />
</Button>
</div>
{/* Tabs */}
<Tabs
value={panelView}
onValueChange={(v) => setPanelView(v as PanelView)}
className="flex-1 flex flex-col min-h-0"
>
<div className="px-4 pt-2 bg-card">
<TabsList>
<TabsTrigger value="terminal">Terminal</TabsTrigger>
<TabsTrigger value="queue">Queue</TabsTrigger>
</TabsList>
</div>
{/* Terminal View */}
<TabsContent value="terminal" className="flex-1 flex flex-col min-h-0 mt-0 p-0">
{activeTerminalId ? (
<div className="flex-1 flex flex-col min-h-0">
{/* xterm container */}
<div className="flex-1 min-h-0 bg-black/90">
<div ref={terminalHostRef} className="h-full w-full" />
</div>
{/* Command input area */}
<div className="border-t border-border p-3 bg-card">
{error && (
<div className="text-xs text-destructive mb-2">{error}</div>
)}
<div className="flex gap-2">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Enter command... (Ctrl+Enter to execute)"
className={cn(
'flex-1 min-h-[60px] max-h-[120px] p-2 bg-background border border-input rounded-md text-sm resize-none',
'focus:outline-none focus:ring-2 focus:ring-primary'
)}
/>
<Button
onClick={handleExecute}
disabled={!activeTerminalId || isExecuting || !prompt.trim()}
className="self-end"
>
<Send className="w-4 h-4 mr-1" />
Execute
</Button>
</div>
</div>
</div>
) : (
<div className="flex-1 flex items-center justify-center text-muted-foreground">
<p className="text-sm">No terminal selected</p>
</div>
)}
</TabsContent>
{/* Queue View (placeholder for Phase 2) */}
<TabsContent value="queue" className="flex-1 flex items-center justify-center mt-0 p-0">
<div className="text-center text-muted-foreground">
<p className="text-sm">Execution Queue Management</p>
<p className="text-xs mt-1">Coming in Phase 2</p>
</div>
</TabsContent>
</Tabs>
</div>
);
}

View File

@@ -0,0 +1,142 @@
// ========================================
// TerminalNavBar Component
// ========================================
// Left navigation bar for the terminal panel.
// Shows queue entry icon at top, separator, and dynamic terminal session icons
// with status badges. Reads session data from cliSessionStore and panel state
// from terminalPanelStore.
import { useMemo } from 'react';
import {
ClipboardList,
Terminal,
Loader2,
CheckCircle,
XCircle,
Circle,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTerminalPanelStore } from '@/stores/terminalPanelStore';
import { useCliSessionStore } from '@/stores/cliSessionStore';
// ========== Status Badge Configuration ==========
type SessionStatus = 'running' | 'completed' | 'failed' | 'idle';
interface StatusBadgeConfig {
icon: React.ComponentType<{ className?: string }>;
colorClass: string;
}
const statusBadgeMap: Record<SessionStatus, StatusBadgeConfig> = {
running: { icon: Loader2, colorClass: 'bg-blue-500' },
completed: { icon: CheckCircle, colorClass: 'bg-green-500' },
failed: { icon: XCircle, colorClass: 'bg-red-500' },
idle: { icon: Circle, colorClass: 'bg-gray-500' },
};
// ========== Helpers ==========
/**
* Derive a simple session status from the session metadata.
* This is a heuristic based on available data - the shellKind and updatedAt fields
* provide indirect clues about activity. A more precise status would require
* backend support for explicit session state tracking.
*/
function deriveSessionStatus(_sessionKey: string, _shellKind: string): SessionStatus {
// For now, default to idle. In Phase 2 we can refine this
// based on active execution tracking from the backend.
return 'idle';
}
// ========== Component ==========
export function TerminalNavBar() {
const panelView = useTerminalPanelStore((s) => s.panelView);
const activeTerminalId = useTerminalPanelStore((s) => s.activeTerminalId);
const terminalOrder = useTerminalPanelStore((s) => s.terminalOrder);
const setActiveTerminal = useTerminalPanelStore((s) => s.setActiveTerminal);
const setPanelView = useTerminalPanelStore((s) => s.setPanelView);
const sessionsByKey = useCliSessionStore((s) => s.sessions);
// Build ordered list of sessions that exist in the store
const orderedSessions = useMemo(() => {
return terminalOrder
.map((key) => sessionsByKey[key])
.filter(Boolean);
}, [terminalOrder, sessionsByKey]);
const handleQueueClick = () => {
setPanelView('queue');
};
const handleTerminalClick = (sessionKey: string) => {
setPanelView('terminal');
setActiveTerminal(sessionKey);
};
return (
<div className="w-16 flex-shrink-0 flex flex-col border-r border-border bg-muted/30">
{/* Queue entry icon */}
<div className="flex items-center justify-center py-3">
<button
type="button"
onClick={handleQueueClick}
className={cn(
'w-10 h-10 flex items-center justify-center rounded-md transition-colors',
'hover:bg-accent hover:text-accent-foreground',
panelView === 'queue' && 'bg-accent text-accent-foreground'
)}
title="Execution Queue"
>
<ClipboardList className="w-5 h-5" />
</button>
</div>
{/* Separator */}
<div className="mx-3 border-t border-border" />
{/* Terminal session icons (scrollable) */}
<div className="flex-1 overflow-y-auto py-2 space-y-1">
{orderedSessions.map((session) => {
const isActive = activeTerminalId === session.sessionKey && panelView === 'terminal';
const status = deriveSessionStatus(session.sessionKey, session.shellKind);
const badge = statusBadgeMap[status];
const BadgeIcon = badge.icon;
return (
<div key={session.sessionKey} className="flex items-center justify-center">
<button
type="button"
onClick={() => handleTerminalClick(session.sessionKey)}
className={cn(
'relative w-10 h-10 flex items-center justify-center rounded-md transition-colors',
'hover:bg-accent hover:text-accent-foreground',
isActive && 'bg-accent text-accent-foreground'
)}
title={`${session.tool || 'cli'} - ${session.sessionKey}`}
>
<Terminal className="w-5 h-5" />
{/* Status badge overlay */}
<span
className={cn(
'absolute bottom-0.5 right-0.5 w-3.5 h-3.5 rounded-full flex items-center justify-center',
badge.colorClass
)}
>
<BadgeIcon
className={cn(
'w-2 h-2 text-white',
status === 'running' && 'animate-spin'
)}
/>
</span>
</button>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,71 @@
// ========================================
// TerminalPanel Component
// ========================================
// Right-side overlay panel for terminal monitoring.
// Follows the IssueDrawer pattern: fixed overlay + translate-x slide animation.
// Contains TerminalNavBar (left icon strip) and TerminalMainArea (main content).
// All state is read from terminalPanelStore - no props needed.
import { useEffect, useCallback } from 'react';
import { cn } from '@/lib/utils';
import { useTerminalPanelStore } from '@/stores/terminalPanelStore';
import { TerminalNavBar } from './TerminalNavBar';
import { TerminalMainArea } from './TerminalMainArea';
// ========== Component ==========
export function TerminalPanel() {
const isPanelOpen = useTerminalPanelStore((s) => s.isPanelOpen);
const closePanel = useTerminalPanelStore((s) => s.closePanel);
const handleClose = useCallback(() => {
closePanel();
}, [closePanel]);
// ESC key to close
useEffect(() => {
if (!isPanelOpen) return;
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape') handleClose();
};
window.addEventListener('keydown', handleEsc);
return () => window.removeEventListener('keydown', handleEsc);
}, [isPanelOpen, handleClose]);
if (!isPanelOpen) {
return null;
}
return (
<>
{/* Overlay */}
<div
className={cn(
'fixed inset-0 bg-black/40 transition-opacity z-40',
isPanelOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
)}
onClick={handleClose}
aria-hidden="true"
/>
{/* Panel */}
<div
className={cn(
'fixed top-0 right-0 h-full w-1/2 bg-background border-l border-border shadow-2xl z-50',
'flex flex-row transition-transform duration-300 ease-in-out',
isPanelOpen ? 'translate-x-0' : 'translate-x-full'
)}
role="dialog"
aria-modal="true"
aria-label="Terminal Panel"
style={{ minWidth: '400px', maxWidth: '800px' }}
>
{/* Left navigation bar */}
<TerminalNavBar />
{/* Main display area */}
<TerminalMainArea onClose={handleClose} />
</div>
</>
);
}

View File

@@ -0,0 +1,8 @@
// ========================================
// Terminal Panel - Barrel Exports
// ========================================
// Re-exports all terminal panel components for convenient imports.
export { TerminalPanel } from './TerminalPanel';
export { TerminalNavBar } from './TerminalNavBar';
export { TerminalMainArea } from './TerminalMainArea';

View File

@@ -6,7 +6,7 @@
import * as React from "react"; import * as React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { Info, Plus, Trash2 } from "lucide-react"; import { Info, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export interface ContextRule { export interface ContextRule {

View File

@@ -19,7 +19,7 @@ export interface MultiNodeSelectorProps {
} }
const MultiNodeSelector = React.forwardRef<HTMLDivElement, MultiNodeSelectorProps>( const MultiNodeSelector = React.forwardRef<HTMLDivElement, MultiNodeSelectorProps>(
({ availableNodes, selectedNodes, onChange, placeholder, emptyMessage, className }, ref) => { ({ availableNodes, selectedNodes, onChange, emptyMessage, className }, ref) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const isSelected = (nodeId: string) => selectedNodes.includes(nodeId); const isSelected = (nodeId: string) => selectedNodes.includes(nodeId);

View File

@@ -108,7 +108,6 @@ export function useActiveCliExecutions(
const removeExecution = useCliStreamStore(state => state.removeExecution); const removeExecution = useCliStreamStore(state => state.removeExecution);
const executions = useCliStreamStore(state => state.executions); const executions = useCliStreamStore(state => state.executions);
const setCurrentExecution = useCliStreamStore(state => state.setCurrentExecution); const setCurrentExecution = useCliStreamStore(state => state.setCurrentExecution);
const markExecutionClosedByUser = useCliStreamStore(state => state.markExecutionClosedByUser);
const isExecutionClosedByUser = useCliStreamStore(state => state.isExecutionClosedByUser); const isExecutionClosedByUser = useCliStreamStore(state => state.isExecutionClosedByUser);
const cleanupUserClosedExecutions = useCliStreamStore(state => state.cleanupUserClosedExecutions); const cleanupUserClosedExecutions = useCliStreamStore(state => state.cleanupUserClosedExecutions);

View File

@@ -44,10 +44,8 @@ import {
type ProviderCredential, type ProviderCredential,
type CustomEndpoint, type CustomEndpoint,
type CacheStats, type CacheStats,
type GlobalCacheSettings,
type ModelPoolConfig, type ModelPoolConfig,
type ModelPoolType, type ModelPoolType,
type DiscoveredProvider,
type CliSettingsEndpoint, type CliSettingsEndpoint,
type SaveCliSettingsRequest, type SaveCliSettingsRequest,
} from '../lib/api'; } from '../lib/api';

View File

@@ -9,15 +9,10 @@ import {
fetchGraphImpact, fetchGraphImpact,
type GraphDependenciesRequest, type GraphDependenciesRequest,
type GraphDependenciesResponse, type GraphDependenciesResponse,
type GraphImpactRequest,
type GraphImpactResponse,
} from '../lib/api'; } from '../lib/api';
import type { import type {
GraphData, GraphData,
GraphNode,
GraphEdge,
GraphFilters, GraphFilters,
GraphMetadata,
NodeType, NodeType,
EdgeType, EdgeType,
} from '../types/graph-explorer'; } from '../types/graph-explorer';
@@ -132,7 +127,7 @@ function filterGraphData(
// Filter by minimum complexity // Filter by minimum complexity
if (filters.minComplexity !== undefined) { if (filters.minComplexity !== undefined) {
filteredNodes = filteredNodes.filter(node => { filteredNodes = filteredNodes.filter(_node => {
// This would require complexity data to be available // This would require complexity data to be available
// For now, we'll skip this filter // For now, we'll skip this filter
return true; return true;
@@ -239,7 +234,6 @@ export function useGraphData(options: UseGraphDataOptions = {}): UseGraphDataRet
rootPath, rootPath,
maxDepth, maxDepth,
nodeTypes, nodeTypes,
edgeTypes,
} = options; } = options;
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@@ -8,7 +8,6 @@ import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { import {
useIssueQueue, useIssueQueue,
useIssueMutations,
useQueueMutations, useQueueMutations,
useIssueDiscovery, useIssueDiscovery,
} from './useIssues'; } from './useIssues';

View File

@@ -25,7 +25,6 @@ import {
exportDiscoveryFindingsAsIssues, exportDiscoveryFindingsAsIssues,
type Issue, type Issue,
type IssueQueue, type IssueQueue,
type IssuesResponse,
type QueueHistoryIndex, type QueueHistoryIndex,
type DiscoverySession, type DiscoverySession,
type Finding, type Finding,

View File

@@ -24,7 +24,6 @@ import {
type McpProjectConfigType, type McpProjectConfigType,
type McpTemplate, type McpTemplate,
type McpTemplateInstallRequest, type McpTemplateInstallRequest,
type AllProjectsResponse,
type OtherProjectsServersResponse, type OtherProjectsServersResponse,
type CrossCliCopyRequest, type CrossCliCopyRequest,
type CrossCliCopyResponse, type CrossCliCopyResponse,
@@ -439,7 +438,7 @@ export function useCodexMutations(): UseCodexMutationsReturn {
// Optimistic update could be added here if needed // Optimistic update could be added here if needed
return { serverName, enabled }; return { serverName, enabled };
}, },
onError: (_error, _vars, context) => { onError: (_error, _vars, _context) => {
// Rollback on error // Rollback on error
console.error('Failed to toggle Codex MCP server:', _error); console.error('Failed to toggle Codex MCP server:', _error);
}, },

View File

@@ -168,7 +168,7 @@ export function useCreateSession(): UseCreateSessionReturn {
const mutation = useMutation({ const mutation = useMutation({
mutationFn: createSession, mutationFn: createSession,
onSuccess: (newSession) => { onSuccess: () => {
// Invalidate sessions cache to trigger refetch // Invalidate sessions cache to trigger refetch
queryClient.invalidateQueries({ queryKey: ['workspace'] }); queryClient.invalidateQueries({ queryKey: ['workspace'] });
// Invalidate dashboard stats // Invalidate dashboard stats

View File

@@ -9,7 +9,6 @@ import {
enableSkill, enableSkill,
disableSkill, disableSkill,
type Skill, type Skill,
type SkillsResponse,
} from '../lib/api'; } from '../lib/api';
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
import { workspaceQueryKeys } from '@/lib/queryKeys'; import { workspaceQueryKeys } from '@/lib/queryKeys';

View File

@@ -15,7 +15,7 @@ import {
type ExecutionLog, type ExecutionLog,
} from '../types/execution'; } from '../types/execution';
import { SurfaceUpdateSchema } from '../packages/a2ui-runtime/core/A2UITypes'; import { SurfaceUpdateSchema } from '../packages/a2ui-runtime/core/A2UITypes';
import type { ToolCallKind } from '../types/toolCall'; import type { ToolCallKind, ToolCallExecution } from '../types/toolCall';
// Constants // Constants
const RECONNECT_DELAY_BASE = 1000; // 1 second const RECONNECT_DELAY_BASE = 1000; // 1 second
@@ -242,7 +242,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
const currentNodeId = stores.currentExecution?.currentNodeId; const currentNodeId = stores.currentExecution?.currentNodeId;
if (currentNodeId && (unitType === 'stdout' || unitType === 'stderr')) { if (currentNodeId && (unitType === 'stdout' || unitType === 'stderr')) {
const toolCalls = stores.getToolCallsForNode?.(currentNodeId); const toolCalls = stores.getToolCallsForNode?.(currentNodeId);
const activeCall = toolCalls?.find(c => c.status === 'executing'); const activeCall = toolCalls?.find((c: ToolCallExecution) => c.status === 'executing');
if (activeCall) { if (activeCall) {
stores.updateToolCall(currentNodeId, activeCall.callId, { stores.updateToolCall(currentNodeId, activeCall.callId, {

View File

@@ -3,9 +3,8 @@
// ======================================== // ========================================
// TanStack Query hook for fetching workflow status distribution // TanStack Query hook for fetching workflow status distribution
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
import { workspaceQueryKeys } from '@/lib/queryKeys';
/** /**
* Workflow status count data structure * Workflow status count data structure

View File

@@ -74,7 +74,7 @@ describe('MCP API (frontend ↔ backend contract)', () => {
it('toggleMcpServer uses /api/mcp-toggle with { projectPath, serverName, enable }', async () => { it('toggleMcpServer uses /api/mcp-toggle with { projectPath, serverName, enable }', async () => {
const fetchMock = vi const fetchMock = vi
.spyOn(globalThis, 'fetch') .spyOn(globalThis, 'fetch')
.mockImplementation(async (input, init) => { .mockImplementation(async (input, _init) => {
if (input === '/api/mcp-toggle') { if (input === '/api/mcp-toggle') {
return jsonResponse({ success: true, serverName: 'global1', enabled: false }); return jsonResponse({ success: true, serverName: 'global1', enabled: false });
} }

View File

@@ -261,7 +261,7 @@ describe('Component Renderer Interface', () => {
}); });
it('should support async action handlers', async () => { it('should support async action handlers', async () => {
const asyncAction: ActionHandler = async (actionId, params) => { const asyncAction: ActionHandler = async (_actionId, _params) => {
await Promise.resolve(); await Promise.resolve();
return; return;
}; };

View File

@@ -5,8 +5,7 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { z } from 'zod'; import { z } from 'zod';
import { A2UIParser, a2uiParser, A2UIParseError } from '../core/A2UIParser'; import { a2uiParser, A2UIParseError } from '../core/A2UIParser';
import type { SurfaceUpdate, A2UIComponent } from '../core/A2UITypes';
// Import component renderers to trigger auto-registration // Import component renderers to trigger auto-registration
import '../renderer/components'; import '../renderer/components';

View File

@@ -4,7 +4,7 @@
// Tests for all A2UI component renderers // Tests for all A2UI component renderers
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, cleanup, within } from '@testing-library/react'; import { render, screen, cleanup } from '@testing-library/react';
import type { A2UIComponent } from '../core/A2UITypes'; import type { A2UIComponent } from '../core/A2UITypes';
import type { A2UIState, ActionHandler, BindingResolver } from '../core/A2UIComponentRegistry'; import type { A2UIState, ActionHandler, BindingResolver } from '../core/A2UIComponentRegistry';
import type { TextComponent, ButtonComponent, DropdownComponent, CLIOutputComponent, DateTimeInputComponent } from '../core/A2UITypes'; import type { TextComponent, ButtonComponent, DropdownComponent, CLIOutputComponent, DateTimeInputComponent } from '../core/A2UITypes';
@@ -653,7 +653,7 @@ describe('A2UI Component Integration', () => {
}); });
it('should handle async action handlers', async () => { it('should handle async action handlers', async () => {
const asyncOnAction: ActionHandler = async (actionId, params) => { const asyncOnAction: ActionHandler = async (_actionId, _params) => {
await new Promise((resolve) => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
}; };

View File

@@ -3,8 +3,8 @@
// ======================================== // ========================================
// React component that renders A2UI surfaces // React component that renders A2UI surfaces
import React, { useState, useCallback, useMemo } from 'react'; import { useState, useCallback } from 'react';
import type { SurfaceUpdate, SurfaceComponent, A2UIComponent, LiteralString, Binding } from '../core/A2UITypes'; import type { SurfaceUpdate, A2UIComponent, LiteralString, Binding } from '../core/A2UITypes';
import { a2uiRegistry, type A2UIState, type ActionHandler, type BindingResolver } from '../core/A2UIComponentRegistry'; import { a2uiRegistry, type A2UIState, type ActionHandler, type BindingResolver } from '../core/A2UIComponentRegistry';
// ========== Renderer Props ========== // ========== Renderer Props ==========
@@ -26,7 +26,7 @@ interface A2UIRendererProps {
*/ */
export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRendererProps) { export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRendererProps) {
// Local state initialized with surface's initial state // Local state initialized with surface's initial state
const [localState, setLocalState] = useState<A2UIState>(surface.initialState || {}); const [localState] = useState<A2UIState>(surface.initialState || {});
// Handle action from components // Handle action from components
const handleAction = useCallback<ActionHandler>( const handleAction = useCallback<ActionHandler>(
@@ -57,21 +57,6 @@ export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRenderer
[localState] [localState]
); );
// Update state from external source
const updateState = useCallback((updates: Partial<A2UIState>) => {
setLocalState((prev) => ({ ...prev, ...updates }));
}, []);
// Memoize context for components
const contextValue = useMemo(
() => ({
state: localState,
resolveBinding,
updateState,
}),
[localState, resolveBinding, updateState]
);
return ( return (
<div className={`a2ui-surface ${className}`} data-surface-id={surface.surfaceId}> <div className={`a2ui-surface ${className}`} data-surface-id={surface.surfaceId}>
{surface.components.map((comp) => ( {surface.components.map((comp) => (

View File

@@ -3,25 +3,16 @@
// ======================================== // ========================================
// Maps A2UI Button component to shadcn/ui Button // Maps A2UI Button component to shadcn/ui Button
import React from 'react';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import type { A2UIState, ActionHandler, BindingResolver } from '../../core/A2UIComponentRegistry'; import type { ButtonComponent } from '../../core/A2UITypes';
import type { ButtonComponent, A2UIComponent } from '../../core/A2UITypes';
import { resolveLiteralOrBinding } from '../A2UIRenderer'; import { resolveLiteralOrBinding } from '../A2UIRenderer';
interface A2UIButtonRendererProps {
component: A2UIComponent;
state: A2UIState;
onAction: ActionHandler;
resolveBinding: BindingResolver;
}
/** /**
* A2UI Button Component Renderer * A2UI Button Component Renderer
* Maps A2UI variants (primary/secondary/destructive) to shadcn/ui variants (default/secondary/destructive/ghost) * Maps A2UI variants (primary/secondary/destructive) to shadcn/ui variants (default/secondary/destructive/ghost)
*/ */
export const A2UIButton: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UIButton: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const buttonComp = component as ButtonComponent; const buttonComp = component as ButtonComponent;
const { Button: buttonConfig } = buttonComp; const { Button: buttonConfig } = buttonComp;

View File

@@ -101,7 +101,7 @@ function StreamingIndicator() {
* A2UI CLIOutput Component Renderer * A2UI CLIOutput Component Renderer
* Displays CLI output with optional syntax highlighting and streaming indicator * Displays CLI output with optional syntax highlighting and streaming indicator
*/ */
export const A2UICLIOutput: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UICLIOutput: ComponentRenderer = ({ component, resolveBinding }) => {
const cliOutputComp = component as CLIOutputComponent; const cliOutputComp = component as CLIOutputComponent;
const { CLIOutput: config } = cliOutputComp; const { CLIOutput: config } = cliOutputComp;

View File

@@ -3,24 +3,16 @@
// ======================================== // ========================================
// Maps A2UI Card component to shadcn/ui Card // Maps A2UI Card component to shadcn/ui Card
import React from 'react';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/Card'; import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/Card';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveTextContent } from '../A2UIRenderer'; import { resolveTextContent } from '../A2UIRenderer';
import type { CardComponent } from '../../core/A2UITypes'; import type { CardComponent } from '../../core/A2UITypes';
interface A2UICardProps {
component: CardComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI Card Component Renderer * A2UI Card Component Renderer
* Container component with optional title and description * Container component with optional title and description
*/ */
export const A2UICard: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UICard: ComponentRenderer = ({ component, resolveBinding }) => {
const cardComp = component as CardComponent; const cardComp = component as CardComponent;
const { Card: cardConfig } = cardComp; const { Card: cardConfig } = cardComp;

View File

@@ -3,25 +3,18 @@
// ======================================== // ========================================
// Maps A2UI Checkbox component to shadcn/ui Checkbox // Maps A2UI Checkbox component to shadcn/ui Checkbox
import React, { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Checkbox } from '@/components/ui/Checkbox'; import { Checkbox } from '@/components/ui/Checkbox';
import { Label } from '@/components/ui/Label'; import { Label } from '@/components/ui/Label';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer'; import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
import type { CheckboxComponent } from '../../core/A2UITypes'; import type { CheckboxComponent } from '../../core/A2UITypes';
interface A2UICheckboxProps {
component: CheckboxComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI Checkbox Component Renderer * A2UI Checkbox Component Renderer
* Boolean state binding with onChange handler * Boolean state binding with onChange handler
*/ */
export const A2UICheckbox: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UICheckbox: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const checkboxComp = component as CheckboxComponent; const checkboxComp = component as CheckboxComponent;
const { Checkbox: checkboxConfig } = checkboxComp; const { Checkbox: checkboxConfig } = checkboxComp;

View File

@@ -3,9 +3,9 @@
// ======================================== // ========================================
// Date/time picker with ISO string format support // Date/time picker with ISO string format support
import React, { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer'; import { resolveTextContent } from '../A2UIRenderer';
import type { DateTimeInputComponent } from '../../core/A2UITypes'; import type { DateTimeInputComponent } from '../../core/A2UITypes';
/** /**
@@ -30,7 +30,7 @@ function isoToDateTimeLocal(isoString: string): string {
/** /**
* Convert datetime-local input format to ISO string * Convert datetime-local input format to ISO string
*/ */
function dateTimeLocalToIso(dateTimeLocal: string, includeTime: boolean): string { function dateTimeLocalToIso(dateTimeLocal: string, _includeTime: boolean): string {
if (!dateTimeLocal) return ''; if (!dateTimeLocal) return '';
const date = new Date(dateTimeLocal); const date = new Date(dateTimeLocal);
@@ -43,7 +43,7 @@ function dateTimeLocalToIso(dateTimeLocal: string, includeTime: boolean): string
* A2UI DateTimeInput Component Renderer * A2UI DateTimeInput Component Renderer
* Uses native input[type="datetime-local"] or input[type="date"] based on includeTime * Uses native input[type="datetime-local"] or input[type="date"] based on includeTime
*/ */
export const A2UIDateTimeInput: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UIDateTimeInput: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const dateTimeComp = component as DateTimeInputComponent; const dateTimeComp = component as DateTimeInputComponent;
const { DateTimeInput: config } = dateTimeComp; const { DateTimeInput: config } = dateTimeComp;
const includeTime = config.includeTime ?? true; const includeTime = config.includeTime ?? true;

View File

@@ -3,24 +3,16 @@
// ======================================== // ========================================
// Maps A2UI Progress component to shadcn/ui Progress // Maps A2UI Progress component to shadcn/ui Progress
import React from 'react';
import { Progress } from '@/components/ui/Progress'; import { Progress } from '@/components/ui/Progress';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding } from '../A2UIRenderer'; import { resolveLiteralOrBinding } from '../A2UIRenderer';
import type { ProgressComponent } from '../../core/A2UITypes'; import type { ProgressComponent } from '../../core/A2UITypes';
interface A2UIProgressProps {
component: ProgressComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI Progress Component Renderer * A2UI Progress Component Renderer
* For CLI output progress display * For CLI output progress display
*/ */
export const A2UIProgress: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UIProgress: ComponentRenderer = ({ component, resolveBinding }) => {
const progressComp = component as ProgressComponent; const progressComp = component as ProgressComponent;
const { Progress: progressConfig } = progressComp; const { Progress: progressConfig } = progressComp;

View File

@@ -4,25 +4,18 @@
// Maps A2UI RadioGroup component to shadcn/ui RadioGroup // Maps A2UI RadioGroup component to shadcn/ui RadioGroup
// Used for single-select questions with visible options // Used for single-select questions with visible options
import React, { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { RadioGroup, RadioGroupItem } from '@/components/ui/RadioGroup'; import { RadioGroup, RadioGroupItem } from '@/components/ui/RadioGroup';
import { Label } from '@/components/ui/Label'; import { Label } from '@/components/ui/Label';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer'; import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
import type { RadioGroupComponent } from '../../core/A2UITypes'; import type { RadioGroupComponent } from '../../core/A2UITypes';
interface A2UIRadioGroupProps {
component: RadioGroupComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI RadioGroup Component Renderer * A2UI RadioGroup Component Renderer
* Single selection from visible options with onChange handler * Single selection from visible options with onChange handler
*/ */
export const A2UIRadioGroup: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UIRadioGroup: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const radioGroupComp = component as RadioGroupComponent; const radioGroupComp = component as RadioGroupComponent;
const { RadioGroup: radioConfig } = radioGroupComp; const { RadioGroup: radioConfig } = radioGroupComp;

View File

@@ -6,20 +6,12 @@
import React from 'react'; import React from 'react';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveTextContent } from '../A2UIRenderer'; import { resolveTextContent } from '../A2UIRenderer';
import type { TextComponent } from '../../core/A2UITypes';
interface A2UITextProps {
component: TextComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI Text Component Renderer * A2UI Text Component Renderer
* Maps A2UI Text usageHint to HTML elements (h1, h2, h3, p, span, code) * Maps A2UI Text usageHint to HTML elements (h1, h2, h3, p, span, code)
*/ */
export const A2UIText: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UIText: ComponentRenderer = ({ component, resolveBinding }) => {
const { Text } = component as { Text: { text: unknown; usageHint?: string } }; const { Text } = component as { Text: { text: unknown; usageHint?: string } };
// Resolve text content // Resolve text content

View File

@@ -3,24 +3,17 @@
// ======================================== // ========================================
// Maps A2UI TextArea component to shadcn/ui Textarea // Maps A2UI TextArea component to shadcn/ui Textarea
import React, { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Textarea } from '@/components/ui/Textarea'; import { Textarea } from '@/components/ui/Textarea';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding } from '../A2UIRenderer'; import { resolveLiteralOrBinding } from '../A2UIRenderer';
import type { TextAreaComponent } from '../../core/A2UITypes'; import type { TextAreaComponent } from '../../core/A2UITypes';
interface A2UITextAreaProps {
component: TextAreaComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI TextArea Component Renderer * A2UI TextArea Component Renderer
* Two-way binding via onChange updates to local state * Two-way binding via onChange updates to local state
*/ */
export const A2UITextArea: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UITextArea: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const areaComp = component as TextAreaComponent; const areaComp = component as TextAreaComponent;
const { TextArea: areaConfig } = areaComp; const { TextArea: areaConfig } = areaComp;

View File

@@ -3,24 +3,17 @@
// ======================================== // ========================================
// Maps A2UI TextField component to shadcn/ui Input // Maps A2UI TextField component to shadcn/ui Input
import React, { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry'; import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
import { resolveLiteralOrBinding } from '../A2UIRenderer'; import { resolveLiteralOrBinding } from '../A2UIRenderer';
import type { TextFieldComponent } from '../../core/A2UITypes'; import type { TextFieldComponent } from '../../core/A2UITypes';
interface A2UITextFieldProps {
component: TextFieldComponent;
state: Record<string, unknown>;
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
resolveBinding: (binding: { path: string }) => unknown;
}
/** /**
* A2UI TextField Component Renderer * A2UI TextField Component Renderer
* Two-way binding via onChange updates to local state * Two-way binding via onChange updates to local state
*/ */
export const A2UITextField: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => { export const A2UITextField: ComponentRenderer = ({ component, onAction, resolveBinding }) => {
const fieldComp = component as TextFieldComponent; const fieldComp = component as TextFieldComponent;
const { TextField: fieldConfig } = fieldComp; const { TextField: fieldConfig } = fieldComp;

View File

@@ -188,7 +188,7 @@ export function CliViewerPage() {
const lastMessage = useNotificationStore(selectWsLastMessage); const lastMessage = useNotificationStore(selectWsLastMessage);
// Active execution sync from server // Active execution sync from server
const { isLoading: isSyncing } = useActiveCliExecutions(true); // Always sync when page is open const { isLoading: _isSyncing } = useActiveCliExecutions(true); // Always sync when page is open
const invalidateActive = useInvalidateActiveCliExecutions(); const invalidateActive = useInvalidateActiveCliExecutions();
// Detect current layout type from store // Detect current layout type from store

View File

@@ -7,7 +7,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, waitFor } from '@/test/i18n'; import { render, screen, waitFor } from '@/test/i18n';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { CodexLensManagerPage } from './CodexLensManagerPage'; import { CodexLensManagerPage } from './CodexLensManagerPage';
import * as api from '@/lib/api';
// Mock api module // Mock api module
vi.mock('@/lib/api', () => ({ vi.mock('@/lib/api', () => ({

View File

@@ -15,7 +15,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { TabsNavigation, type TabItem } from '@/components/ui/TabsNavigation'; import { TabsNavigation } from '@/components/ui/TabsNavigation';
import { import {
AlertDialog, AlertDialog,
AlertDialogTrigger, AlertDialogTrigger,
@@ -30,7 +30,6 @@ import {
import { OverviewTab } from '@/components/codexlens/OverviewTab'; import { OverviewTab } from '@/components/codexlens/OverviewTab';
import { SettingsTab } from '@/components/codexlens/SettingsTab'; import { SettingsTab } from '@/components/codexlens/SettingsTab';
import { AdvancedTab } from '@/components/codexlens/AdvancedTab'; import { AdvancedTab } from '@/components/codexlens/AdvancedTab';
import { GpuSelector } from '@/components/codexlens/GpuSelector';
import { ModelsTab } from '@/components/codexlens/ModelsTab'; import { ModelsTab } from '@/components/codexlens/ModelsTab';
import { SearchTab } from '@/components/codexlens/SearchTab'; import { SearchTab } from '@/components/codexlens/SearchTab';
import { SemanticInstallDialog } from '@/components/codexlens/SemanticInstallDialog'; import { SemanticInstallDialog } from '@/components/codexlens/SemanticInstallDialog';

View File

@@ -4,7 +4,7 @@
// Tests for the issue discovery page with i18n // Tests for the issue discovery page with i18n
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, waitFor } from '@/test/i18n'; import { render, screen } from '@/test/i18n';
import { DiscoveryPage } from './DiscoveryPage'; import { DiscoveryPage } from './DiscoveryPage';
import { useWorkflowStore } from '@/stores/workflowStore'; import { useWorkflowStore } from '@/stores/workflowStore';
import type { DiscoverySession } from '@/lib/api'; import type { DiscoverySession } from '@/lib/api';

View File

@@ -13,14 +13,13 @@ import {
XCircle, XCircle,
BarChart3, BarChart3,
Calendar, Calendar,
Filter,
ListTree, ListTree,
History, History,
List, List,
Monitor, Monitor,
} from 'lucide-react'; } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { TabsNavigation, type TabItem } from '@/components/ui/TabsNavigation'; import { TabsNavigation } from '@/components/ui/TabsNavigation';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { ExecutionMonitor } from './orchestrator/ExecutionMonitor'; import { ExecutionMonitor } from './orchestrator/ExecutionMonitor';

View File

@@ -3,7 +3,7 @@
// ======================================== // ========================================
// Unified page for issues, queue, and discovery with tab navigation // Unified page for issues, queue, and discovery with tab navigation
import { useState, useCallback, useRef } from 'react'; import { useState, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { import {

View File

@@ -386,7 +386,7 @@ export function IssueManagerPage() {
try { try {
const result = await pullIssuesFromGitHub({ state: 'open', limit: 100 }); const result = await pullIssuesFromGitHub({ state: 'open', limit: 100 });
await refetch(); await refetch();
toast.success(formatMessage({ id: 'issues.messages.githubSyncSuccess' }, result)); toast.success(formatMessage({ id: 'issues.messages.githubSyncSuccess' }, { ...result }));
} catch (err) { } catch (err) {
console.error('GitHub sync failed:', err); console.error('GitHub sync failed:', err);
toast.error(formatMessage({ id: 'issues.messages.githubSyncError' })); toast.error(formatMessage({ id: 'issues.messages.githubSyncError' }));

View File

@@ -15,11 +15,8 @@ import {
ArrowLeft, ArrowLeft,
FileEdit, FileEdit,
Wrench, Wrench,
Calendar,
Loader2,
XCircle, XCircle,
CheckCircle, CheckCircle,
Clock,
Code, Code,
Zap, Zap,
ListTodo, ListTodo,
@@ -31,7 +28,6 @@ import {
Folder, Folder,
MessageSquare, MessageSquare,
FileText, FileText,
ChevronDown,
ChevronRight, ChevronRight,
Ruler, Ruler,
Stethoscope, Stethoscope,
@@ -41,10 +37,8 @@ import { Flowchart } from '@/components/shared/Flowchart';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { Tabs, TabsContent } from '@/components/ui/Tabs';
import { TabsNavigation } from '@/components/ui/TabsNavigation'; import { TabsNavigation } from '@/components/ui/TabsNavigation';
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/Collapsible'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/Collapsible';
import type { LiteTask } from '@/lib/api';
// ======================================== // ========================================
// Type Definitions // Type Definitions
@@ -64,43 +58,6 @@ interface Exploration {
content?: string; content?: string;
} }
interface ExplorationAngle {
findings: string[];
recommendations: string[];
patterns: string[];
risks: string[];
}
interface ImplementationTask {
id: string;
title: string;
description?: string;
status?: string;
assignee?: string;
}
interface Milestone {
id: string;
name: string;
description?: string;
target_date?: string;
}
interface DiscussionSolution {
id: string;
name: string;
summary: string | { en: string; zh: string };
feasibility: number;
effort: 'low' | 'medium' | 'high';
risk: 'low' | 'medium' | 'high';
source_cli: string[];
implementation_plan: {
approach: string;
tasks: ImplementationTask[];
milestones: Milestone[];
};
}
// ======================================== // ========================================
// Main Component // Main Component
// ======================================== // ========================================

View File

@@ -91,6 +91,24 @@ export {
selectActiveTab, selectActiveTab,
} from './viewerStore'; } from './viewerStore';
// Terminal Panel Store
export {
useTerminalPanelStore,
selectIsPanelOpen as selectIsTerminalPanelOpen,
selectActiveTerminalId,
selectPanelView,
selectTerminalOrder,
selectTerminalCount,
} from './terminalPanelStore';
// Terminal Panel Store Types
export type {
PanelView,
TerminalPanelState,
TerminalPanelActions,
TerminalPanelStore,
} from './terminalPanelStore';
// Re-export types for convenience // Re-export types for convenience
export type { export type {
// App Store Types // App Store Types

View File

@@ -0,0 +1,147 @@
// ========================================
// Terminal Panel Store
// ========================================
// Zustand store for terminal panel UI state management.
// Manages panel visibility, active terminal, view mode, and terminal ordering.
// Separated from cliSessionStore to keep UI state independent of data state.
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
// ========== Types ==========
export type PanelView = 'terminal' | 'queue';
export interface TerminalPanelState {
/** Whether the bottom terminal panel is open */
isPanelOpen: boolean;
/** The sessionKey of the currently active terminal */
activeTerminalId: string | null;
/** Current panel view mode */
panelView: PanelView;
/** Ordered list of terminal sessionKeys (tab order) */
terminalOrder: string[];
}
export interface TerminalPanelActions {
/** Open panel and activate the given terminal; adds it to order if new */
openTerminal: (sessionKey: string) => void;
/** Close the terminal panel (keeps terminal order intact) */
closePanel: () => void;
/** Switch active terminal without opening/closing */
setActiveTerminal: (sessionKey: string) => void;
/** Switch panel view between 'terminal' and 'queue' */
setPanelView: (view: PanelView) => void;
/** Add a terminal to the order list (no-op if already present) */
addTerminal: (sessionKey: string) => void;
/** Remove a terminal from the order list and adjust active if needed */
removeTerminal: (sessionKey: string) => void;
}
export type TerminalPanelStore = TerminalPanelState & TerminalPanelActions;
// ========== Initial State ==========
const initialState: TerminalPanelState = {
isPanelOpen: false,
activeTerminalId: null,
panelView: 'terminal',
terminalOrder: [],
};
// ========== Store ==========
export const useTerminalPanelStore = create<TerminalPanelStore>()(
devtools(
(set, get) => ({
...initialState,
// ========== Panel Lifecycle ==========
openTerminal: (sessionKey: string) => {
const { terminalOrder } = get();
const nextOrder = terminalOrder.includes(sessionKey)
? terminalOrder
: [...terminalOrder, sessionKey];
set(
{
isPanelOpen: true,
activeTerminalId: sessionKey,
panelView: 'terminal',
terminalOrder: nextOrder,
},
false,
'openTerminal'
);
},
closePanel: () => {
set({ isPanelOpen: false }, false, 'closePanel');
},
// ========== Terminal Selection ==========
setActiveTerminal: (sessionKey: string) => {
set({ activeTerminalId: sessionKey }, false, 'setActiveTerminal');
},
// ========== View Mode ==========
setPanelView: (view: PanelView) => {
set({ panelView: view }, false, 'setPanelView');
},
// ========== Terminal Order Management ==========
addTerminal: (sessionKey: string) => {
const { terminalOrder } = get();
if (terminalOrder.includes(sessionKey)) return;
set(
{ terminalOrder: [...terminalOrder, sessionKey] },
false,
'addTerminal'
);
},
removeTerminal: (sessionKey: string) => {
const { terminalOrder, activeTerminalId } = get();
const nextOrder = terminalOrder.filter((key) => key !== sessionKey);
// If removed terminal was active, activate the previous or next neighbor
let nextActive = activeTerminalId;
if (activeTerminalId === sessionKey) {
const removedIndex = terminalOrder.indexOf(sessionKey);
if (nextOrder.length === 0) {
nextActive = null;
} else if (removedIndex >= nextOrder.length) {
nextActive = nextOrder[nextOrder.length - 1];
} else {
nextActive = nextOrder[removedIndex];
}
}
set(
{
terminalOrder: nextOrder,
activeTerminalId: nextActive,
// Auto-close panel when no terminals remain
isPanelOpen: nextOrder.length > 0 ? get().isPanelOpen : false,
},
false,
'removeTerminal'
);
},
}),
{ name: 'TerminalPanelStore' }
)
);
// ========== Selectors ==========
export const selectIsPanelOpen = (state: TerminalPanelStore) => state.isPanelOpen;
export const selectActiveTerminalId = (state: TerminalPanelStore) => state.activeTerminalId;
export const selectPanelView = (state: TerminalPanelStore) => state.panelView;
export const selectTerminalOrder = (state: TerminalPanelStore) => state.terminalOrder;
export const selectTerminalCount = (state: TerminalPanelStore) => state.terminalOrder.length;

View File

@@ -206,6 +206,9 @@ export interface ExecutionStoreActions {
completeToolCall: (nodeId: string, callId: string, result: { status: ToolCallExecution['status']; exitCode?: number; error?: string; result?: unknown }) => void; completeToolCall: (nodeId: string, callId: string, result: { status: ToolCallExecution['status']; exitCode?: number; error?: string; result?: unknown }) => void;
toggleToolCallExpanded: (nodeId: string, callId: string) => void; toggleToolCallExpanded: (nodeId: string, callId: string) => void;
// Tool call getters
getToolCallsForNode: (nodeId: string) => ToolCallExecution[];
// Node selection (new) // Node selection (new)
selectNode: (nodeId: string | null) => void; selectNode: (nodeId: string | null) => void;

281
ccw/frontend/tsc-errors.txt Normal file
View File

@@ -0,0 +1,281 @@
src/components/api-settings/CliSettingsModal.tsx(40,10): error TS6133: 'safeStringifyConfig' is declared but its value is never read.
src/components/issue/queue/ExecutionGroup.test.tsx(130,13): error TS6133: 'numberElements' is declared but its value is never read.
src/components/issue/queue/QueueCard.test.tsx(20,13): error TS2322: Type 'string' is not assignable to type 'QueueItem'.
src/components/issue/queue/QueueCard.test.tsx(20,22): error TS2322: Type 'string' is not assignable to type 'QueueItem'.
src/components/issue/queue/QueueCard.test.tsx(21,17): error TS2322: Type 'string' is not assignable to type 'QueueItem'.
src/components/layout/AppShell.tsx(130,9): error TS6133: 'handleMenuClick' is declared but its value is never read.
src/components/layout/Header.test.tsx(45,22): error TS2322: Type '{ onMenuClick: Mock<Procedure>; }' is not assignable to type 'IntrinsicAttributes & HeaderProps'.
Property 'onMenuClick' does not exist on type 'IntrinsicAttributes & HeaderProps'.
src/components/mcp/ConfigTypeToggle.tsx(18,1): error TS6133: 'Button' is declared but its value is never read.
src/components/mcp/ConfigTypeToggle.tsx(19,1): error TS6133: 'Badge' is declared but its value is never read.
src/components/mcp/CrossCliSyncPanel.tsx(33,6): error TS6196: 'CopyDirection' is declared but never used.
src/components/mcp/RecommendedMcpSection.tsx(13,3): error TS6133: 'Check' is declared but its value is never read.
src/components/notification/NotificationPanel.tsx(24,12): error TS6133: 'ImageIcon' is declared but its value is never read.
src/components/notification/NotificationPanel.tsx(70,10): error TS6133: 'formatDetails' is declared but its value is never read.
src/components/notification/NotificationPanel.tsx(721,9): error TS6133: 'formatMessage' is declared but its value is never read.
src/components/orchestrator/ToolCallCard.tsx(64,15): error TS2304: Cannot find name 'FileText'.
src/components/orchestrator/ToolCallsTimeline.tsx(6,8): error TS6133: 'React' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonCard.tsx(13,3): error TS6133: 'ChevronRight' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonCard.tsx(17,1): error TS6133: 'Button' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonCard.tsx(18,1): error TS6133: 'Badge' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonCard.tsx(97,3): error TS6133: 'timestamp' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonCard.tsx(98,3): error TS6133: 'onCopy' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/JsonField.tsx(12,10): error TS6133: 'copied' is declared but its value is never read.
src/components/shared/CliStreamMonitor/components/OutputLine.tsx(8,1): error TS6133: 'cn' is declared but its value is never read.
src/components/shared/CliStreamMonitor/messages/AssistantMessage.tsx(94,9): error TS6133: 'formatMessage' is declared but its value is never read.
src/components/shared/CliStreamMonitor/messages/ErrorMessage.tsx(22,3): error TS6133: 'timestamp' is declared but its value is never read.
src/components/shared/CliStreamMonitor/messages/UserMessage.tsx(21,3): error TS6133: 'timestamp' is declared but its value is never read.
src/components/shared/CliStreamMonitorLegacy.tsx(157,34): error TS6133: 'onCopy' is declared but its value is never read.
src/components/shared/CliStreamMonitorLegacy.tsx(361,9): error TS6133: 'scrollToBottom' is declared but its value is never read.
src/components/shared/InsightDetailPanel.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/components/shared/InsightDetailPanel.tsx(136,33): error TS6133: 'locale' is declared but its value is never read.
src/components/shared/InsightDetailPanel.tsx(137,9): error TS6133: 'formatMessage' is declared but its value is never read.
src/components/shared/InsightDetailPanel.tsx(180,39): error TS6133: 'locale' is declared but its value is never read.
src/components/shared/InsightsPanel.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/components/shared/LogBlock/LogBlock.tsx(5,8): error TS6133: 'React' is declared but its value is never read.
src/components/shared/LogBlock/LogBlockList.tsx(6,33): error TS6133: 'useMemo' is declared but its value is never read.
src/components/shared/PromptStats.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/components/shared/TickerMarquee.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/components/shared/TickerMarquee.tsx(13,3): error TS6133: 'XCircle' is declared but its value is never read.
src/components/shared/VersionCheck.tsx(2,23): error TS1149: File name 'D:/Claude_dms3/ccw/frontend/src/components/ui/badge.tsx' differs from already included file name 'D:/Claude_dms3/ccw/frontend/src/components/ui/Badge.tsx' only in casing.
The file is in the program because:
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/layout/Header.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/CliStreamMonitorLegacy.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/LogBlock/LogBlock.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/CliStreamMonitor/components/JsonCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/notification/NotificationPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/dashboard/widgets/RecentSessionsWidget.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/SessionCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/SessionsPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/FixSessionPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/ProjectOverviewPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/tasks/TaskStatusDropdown.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/session-detail/ContextTab.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/FieldRenderer.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/ExplorationsSection.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/AssetsCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/DependenciesCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/TestContextCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/session-detail/context/ConflictDetectionCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/session-detail/ConflictTab.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/session-detail/ReviewTab.tsx'
Imported via '../ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/TaskDrawer.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/SessionDetailPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/ConversationCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/CliStreamPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/NativeSessionPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/orchestrator/InlineTemplatePanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/orchestrator/TemplateLibrary.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/orchestrator/ExecutionMonitor.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/LoopMonitorPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/KanbanBoard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/hub/IssuesPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/IssueCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/hub/IssueDrawer.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/hub/QueuePanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/queue/QueueCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/queue/ExecutionGroup.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/queue/QueueBoard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/queue/SolutionDrawer.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/hub/DiscoveryPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/discovery/DiscoveryCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/discovery/DiscoveryDetail.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/discovery/FindingList.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/hub/ObservabilityPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/QueuePage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/DiscoveryPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/SkillsManagerPage.tsx'
Imported via "./Badge" from file 'D:/Claude_dms3/ccw/frontend/src/components/ui/index.ts'
Imported via "./Badge" from file 'D:/Claude_dms3/ccw/frontend/src/components/ui/index.ts'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/SkillCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/SkillDetailPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/RuleCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/PromptCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/QualityBadge.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/GraphToolbar.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/GraphSidebar.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/InsightsPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/IndexManager.tsx'
Imported via '../ui/badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/VersionCheck.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/ConfigSync.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/CommandsManagerPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/commands/CommandGroupAccordion.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/MemoryPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/SettingsPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/HookManagerPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/HookCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/EventGroup.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/HookQuickTemplates.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/HookWizard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/LiteTasksPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/lite-tasks/LiteContextContent.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/ReviewSessionPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/McpManagerPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/McpServerDialog.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/ConfigTypeToggle.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CodexMcpEditableCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CcwToolsMcpCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/McpTemplatesSection.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/RecommendedMcpSection.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/WindowsCompatibilityWarning.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CrossCliSyncPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/AllProjectsTable.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/OtherProjectsSection.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/EndpointsPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/InstallationsPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/RulesManagerPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/PromptHistoryPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/FileWatcherCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/AdvancedTab.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/GpuSelector.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/ModelsTab.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/ModelCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ProviderList.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/EndpointList.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ModelPoolList.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/CliSettingsList.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ManageModelsModal.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/CliSessionSharePage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/IssueManagerPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/team/TeamHeader.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/team/TeamMembersPanel.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/team/TeamMessageFeed.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/dashboard/widgets/TaskMarqueeWidget.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/dashboard/widgets/WorkflowStatusProgressWidget.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CodexMcpCard.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CrossCliCopyButton.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/EnterpriseMcpBadge.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/InstallCommandDialog.tsx'
Matched by include pattern 'src' in 'D:/Claude_dms3/ccw/frontend/tsconfig.json'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/ExecutionMonitorPage.tsx'
Imported via '@/components/ui/Badge' from file 'D:/Claude_dms3/ccw/frontend/src/pages/LiteTaskDetailPage.tsx'
src/components/shared/VersionCheckModal.tsx(6,8): error TS1149: File name 'D:/Claude_dms3/ccw/frontend/src/components/ui/dialog.tsx' differs from already included file name 'D:/Claude_dms3/ccw/frontend/src/components/ui/Dialog.tsx' only in casing.
The file is in the program because:
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/workspace/WorkspaceSelector.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/a2ui/AskQuestionDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/a2ui/A2UIPopupCard.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/SessionsPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/MarkdownModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/CliStreamPanel.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/NativeSessionPanel.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/HistoryPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/orchestrator/TemplateLibrary.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/LoopMonitorPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/issue/queue/QueueActions.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/IssueHubPage.tsx'
Imported via "./Dialog" from file 'D:/Claude_dms3/ccw/frontend/src/components/ui/index.ts'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/SkillCreateDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/RuleDialog.tsx'
Imported via '../ui/dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/VersionCheckModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/shared/ConfigSyncModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/MemoryPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/HookFormDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/hook/HookWizard.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/McpServerDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/McpTemplatesSection.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/RecommendedMcpWizard.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/cli-endpoints/CliEndpointFormDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/RulesManagerPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/PromptHistoryPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/SemanticInstallDialog.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/codexlens/InstallProgressOverlay.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ProviderModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/EndpointModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ModelPoolModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/CliSettingsModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/MultiKeySettingsModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/api-settings/ManageModelsModal.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/cli-viewer/ExecutionPicker.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/pages/IssueManagerPage.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/CrossCliCopyButton.tsx'
Imported via '@/components/ui/Dialog' from file 'D:/Claude_dms3/ccw/frontend/src/components/mcp/InstallCommandDialog.tsx'
Matched by include pattern 'src' in 'D:/Claude_dms3/ccw/frontend/tsconfig.json'
src/components/team/TeamHeader.tsx(7,32): error TS6133: 'Clock' is declared but its value is never read.
src/components/team/TeamMessageFeed.tsx(10,1): error TS6133: 'Badge' is declared but its value is never read.
src/components/team/TeamMessageFeed.tsx(20,28): error TS6196: 'TeamMessageType' is declared but never used.
src/components/team/TeamPipeline.tsx(107,10): error TS6133: 'ForkArrow' is declared but its value is never read.
src/components/ui/ContextAssembler.tsx(9,16): error TS6133: 'Plus' is declared but its value is never read.
src/components/ui/MultiNodeSelector.tsx(22,47): error TS6133: 'placeholder' is declared but its value is never read.
src/hooks/useActiveCliExecutions.ts(111,9): error TS6133: 'markExecutionClosedByUser' is declared but its value is never read.
src/hooks/useApiSettings.ts(47,8): error TS6133: 'GlobalCacheSettings' is declared but its value is never read.
src/hooks/useApiSettings.ts(50,8): error TS6133: 'DiscoveredProvider' is declared but its value is never read.
src/hooks/useGraphData.ts(12,8): error TS6133: 'GraphImpactRequest' is declared but its value is never read.
src/hooks/useGraphData.ts(13,8): error TS6133: 'GraphImpactResponse' is declared but its value is never read.
src/hooks/useGraphData.ts(17,3): error TS6196: 'GraphNode' is declared but never used.
src/hooks/useGraphData.ts(18,3): error TS6196: 'GraphEdge' is declared but never used.
src/hooks/useGraphData.ts(20,3): error TS6196: 'GraphMetadata' is declared but never used.
src/hooks/useGraphData.ts(135,42): error TS6133: 'node' is declared but its value is never read.
src/hooks/useGraphData.ts(242,5): error TS6133: 'edgeTypes' is declared but its value is never read.
src/hooks/useIssues.test.tsx(11,3): error TS6133: 'useIssueMutations' is declared but its value is never read.
src/hooks/useIssues.ts(28,8): error TS6133: 'IssuesResponse' is declared but its value is never read.
src/hooks/useMcpServers.ts(27,8): error TS6133: 'AllProjectsResponse' is declared but its value is never read.
src/hooks/useMcpServers.ts(442,30): error TS6133: 'context' is declared but its value is never read.
src/hooks/useSessions.ts(171,17): error TS6133: 'newSession' is declared but its value is never read.
src/hooks/useSkills.ts(12,8): error TS6133: 'SkillsResponse' is declared but its value is never read.
src/hooks/useWebSocket.ts(54,36): error TS2339: Property 'getToolCallsForNode' does not exist on type 'ExecutionStore'.
src/hooks/useWebSocket.ts(245,52): error TS7006: Parameter 'c' implicitly has an 'any' type.
src/hooks/useWorkflowStatusCounts.ts(6,20): error TS6133: 'useQueryClient' is declared but its value is never read.
src/hooks/useWorkflowStatusCounts.ts(8,1): error TS6133: 'workspaceQueryKeys' is declared but its value is never read.
src/lib/api.mcp.test.ts(77,41): error TS6133: 'init' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/A2UIComponentRegistry.test.ts(264,47): error TS6133: 'actionId' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/A2UIComponentRegistry.test.ts(264,57): error TS6133: 'params' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/A2UIParser.test.ts(8,10): error TS6133: 'A2UIParser' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/A2UIParser.test.ts(9,1): error TS6192: All imports in import declaration are unused.
src/packages/a2ui-runtime/__tests__/components.test.tsx(7,35): error TS6133: 'within' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/components.test.tsx(656,49): error TS6133: 'actionId' is declared but its value is never read.
src/packages/a2ui-runtime/__tests__/components.test.tsx(656,59): error TS6133: 'params' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/A2UIRenderer.tsx(6,8): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/A2UIRenderer.tsx(7,30): error TS6196: 'SurfaceComponent' is declared but never used.
src/packages/a2ui-runtime/renderer/A2UIRenderer.tsx(66,9): error TS6133: 'contextValue' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIButton.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIButton.tsx(13,11): error TS6196: 'A2UIButtonRendererProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UIButton.tsx(24,60): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICard.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICard.tsx(12,11): error TS6196: 'A2UICardProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UICard.tsx(23,58): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICard.tsx(23,65): error TS6133: 'onAction' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICheckbox.tsx(6,8): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICheckbox.tsx(13,11): error TS6196: 'A2UICheckboxProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UICheckbox.tsx(24,62): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICLIOutput.tsx(104,63): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UICLIOutput.tsx(104,70): error TS6133: 'onAction' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIDateTimeInput.tsx(8,10): error TS6133: 'resolveLiteralOrBinding' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIDateTimeInput.tsx(33,52): error TS6133: 'includeTime' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIDateTimeInput.tsx(46,67): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIProgress.tsx(6,1): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIProgress.tsx(12,11): error TS6196: 'A2UIProgressProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UIProgress.tsx(23,62): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIProgress.tsx(23,69): error TS6133: 'onAction' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIRadioGroup.tsx(7,8): error TS6133: 'React' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIRadioGroup.tsx(14,11): error TS6196: 'A2UIRadioGroupProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UIRadioGroup.tsx(25,64): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIText.tsx(11,11): error TS6196: 'A2UITextProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UIText.tsx(22,58): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UIText.tsx(22,65): error TS6133: 'onAction' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UITextArea.tsx(12,11): error TS6196: 'A2UITextAreaProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UITextArea.tsx(23,62): error TS6133: 'state' is declared but its value is never read.
src/packages/a2ui-runtime/renderer/components/A2UITextField.tsx(12,11): error TS6196: 'A2UITextFieldProps' is declared but never used.
src/packages/a2ui-runtime/renderer/components/A2UITextField.tsx(23,63): error TS6133: 'state' is declared but its value is never read.
src/pages/CliViewerPage.tsx(191,9): error TS6133: 'isSyncing' is declared but its value is never read.
src/pages/CodexLensManagerPage.test.tsx(10,1): error TS6133: 'api' is declared but its value is never read.
src/pages/CodexLensManagerPage.tsx(18,31): error TS6133: 'TabItem' is declared but its value is never read.
src/pages/CodexLensManagerPage.tsx(33,1): error TS6133: 'GpuSelector' is declared but its value is never read.
src/pages/DiscoveryPage.test.tsx(7,26): error TS6133: 'waitFor' is declared but its value is never read.
src/pages/ExecutionMonitorPage.tsx(16,3): error TS6133: 'Filter' is declared but its value is never read.
src/pages/ExecutionMonitorPage.tsx(23,31): error TS6133: 'TabItem' is declared but its value is never read.
src/pages/IssueHubPage.tsx(6,33): error TS6133: 'useRef' is declared but its value is never read.
src/pages/IssueManagerPage.tsx(389,80): error TS2769: No overload matches this call.
Overload 1 of 2, '(this: void, descriptor: MessageDescriptor, values?: Record<string, PrimitiveType | FormatXMLElementFn<string, string>> | undefined, opts?: Options | undefined): string', gave the following error.
Argument of type 'GitHubPullResponse' is not assignable to parameter of type 'Record<string, PrimitiveType | FormatXMLElementFn<string, string>>'.
Index signature for type 'string' is missing in type 'GitHubPullResponse'.
Overload 2 of 2, '(this: void, descriptor: MessageDescriptor, values?: Record<string, string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ... 5 more ... | undefined> | undefined, opts?: Options | undefined): ReactNode[]', gave the following error.
Argument of type 'GitHubPullResponse' is not assignable to parameter of type 'Record<string, string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ... 4 more ... | undefined>'.
Index signature for type 'string' is missing in type 'GitHubPullResponse'.
src/pages/LiteTaskDetailPage.tsx(18,3): error TS6133: 'Calendar' is declared but its value is never read.
src/pages/LiteTaskDetailPage.tsx(19,3): error TS6133: 'Loader2' is declared but its value is never read.
src/pages/LiteTaskDetailPage.tsx(22,3): error TS6133: 'Clock' is declared but its value is never read.
src/pages/LiteTaskDetailPage.tsx(34,3): error TS6133: 'ChevronDown' is declared but its value is never read.
src/pages/LiteTaskDetailPage.tsx(44,1): error TS6192: All imports in import declaration are unused.
src/pages/LiteTaskDetailPage.tsx(47,1): error TS6133: 'LiteTask' is declared but its value is never read.
src/pages/LiteTaskDetailPage.tsx(67,11): error TS6196: 'ExplorationAngle' is declared but never used.
src/pages/LiteTaskDetailPage.tsx(89,11): error TS6196: 'DiscussionSolution' is declared but never used.