From 7ebe674b6207125d792e696c81e6970b1e1fec2e Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 25 Feb 2026 10:38:47 +0800 Subject: [PATCH] feat(issue-panel): add multi-select functionality and send to terminal feature feat(config-store): add dashboard feature flags to initial state fix(skill-hub-routes): update skill index path for GitHub configuration --- .../terminal-dashboard/IssuePanel.tsx | 155 +++++++++++++----- ccw/frontend/src/stores/configStore.ts | 3 + ccw/src/core/routes/skill-hub-routes.ts | 2 +- 3 files changed, 118 insertions(+), 42 deletions(-) diff --git a/ccw/frontend/src/components/terminal-dashboard/IssuePanel.tsx b/ccw/frontend/src/components/terminal-dashboard/IssuePanel.tsx index 5d6fafda..dd5bd2e5 100644 --- a/ccw/frontend/src/components/terminal-dashboard/IssuePanel.tsx +++ b/ccw/frontend/src/components/terminal-dashboard/IssuePanel.tsx @@ -6,14 +6,14 @@ // Integrates with issueQueueIntegrationStore for selection state // and association chain highlighting. -import { useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback } from 'react'; import { useIntl } from 'react-intl'; import { AlertCircle, - ArrowRightToLine, Loader2, AlertTriangle, CircleDot, + Terminal, } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; import { cn } from '@/lib/utils'; @@ -23,7 +23,11 @@ import { selectSelectedIssueId, selectAssociationChain, } from '@/stores/issueQueueIntegrationStore'; +import { executeInCliSession } from '@/lib/api'; import type { Issue } from '@/lib/api'; +import { useTerminalGridStore, selectTerminalGridFocusedPaneId, selectTerminalGridPanes } from '@/stores/terminalGridStore'; +import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore'; +import { toast } from '@/stores/notificationStore'; // ========== Priority Badge ========== @@ -62,25 +66,17 @@ function IssueItem({ issue, isSelected, isHighlighted, + isChecked, onSelect, - onSendToQueue, + onToggleCheck, }: { issue: Issue; isSelected: boolean; isHighlighted: boolean; + isChecked: boolean; onSelect: () => void; - onSendToQueue: () => void; + onToggleCheck: () => void; }) { - const { formatMessage } = useIntl(); - - const handleSendToQueue = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - onSendToQueue(); - }, - [onSendToQueue] - ); - return (
+ { e.stopPropagation(); onToggleCheck(); }} + onClick={(e) => e.stopPropagation()} + className="w-3.5 h-3.5 rounded border-border accent-primary shrink-0" + /> {issue.title} @@ -103,26 +106,14 @@ function IssueItem({
-
{issue.context && ( -

+

{issue.context}

)} -
+
{issue.id} {issue.labels && issue.labels.length > 0 && ( <> @@ -178,6 +169,16 @@ export function IssuePanel() { const setSelectedIssue = useIssueQueueIntegrationStore((s) => s.setSelectedIssue); const buildAssociationChain = useIssueQueueIntegrationStore((s) => s.buildAssociationChain); + // Multi-select state + const [selectedIds, setSelectedIds] = useState>(new Set()); + const [isSending, setIsSending] = useState(false); + + // Terminal refs + const focusedPaneId = useTerminalGridStore(selectTerminalGridFocusedPaneId); + const panes = useTerminalGridStore(selectTerminalGridPanes); + const projectPath = useWorkflowStore(selectProjectPath); + const sessionKey = focusedPaneId ? panes[focusedPaneId]?.sessionId : null; + // Sort: open/in_progress first, then by priority (critical > high > medium > low) const sortedIssues = useMemo(() => { const priorityOrder: Record = { @@ -214,13 +215,41 @@ export function IssuePanel() { [selectedIssueId, setSelectedIssue, buildAssociationChain] ); - const handleSendToQueue = useCallback( - (issueId: string) => { - // Select the issue and build chain - queue creation is handled elsewhere - buildAssociationChain(issueId, 'issue'); - }, - [buildAssociationChain] - ); + const handleToggleSelect = useCallback((issueId: string) => { + setSelectedIds(prev => { + const next = new Set(prev); + if (next.has(issueId)) next.delete(issueId); + else next.add(issueId); + return next; + }); + }, []); + + const handleSelectAll = useCallback(() => { + setSelectedIds(new Set(sortedIssues.map(i => i.id))); + }, [sortedIssues]); + + const handleDeselectAll = useCallback(() => { + setSelectedIds(new Set()); + }, []); + + const handleSendToTerminal = useCallback(async () => { + if (!sessionKey || selectedIds.size === 0) return; + setIsSending(true); + try { + await executeInCliSession(sessionKey, { + tool: 'claude', + prompt: Array.from(selectedIds).join(' '), + instructionType: 'skill', + skillName: 'team-issue', + }, projectPath || undefined); + toast.success('Sent to terminal', `/team-issue ${Array.from(selectedIds).join(' ')}`); + setSelectedIds(new Set()); + } catch (err) { + toast.error('Failed to send', err instanceof Error ? err.message : String(err)); + } finally { + setIsSending(false); + } + }, [sessionKey, selectedIds, projectPath]); // Loading state if (isLoading) { @@ -262,11 +291,22 @@ export function IssuePanel() { {formatMessage({ id: 'terminalDashboard.issuePanel.title' })} - {openCount > 0 && ( - - {openCount} - - )} +
+ {sortedIssues.length > 0 && ( + + )} + {openCount > 0 && ( + + {openCount} + + )} +
{/* Issue List */} @@ -280,12 +320,45 @@ export function IssuePanel() { issue={issue} isSelected={selectedIssueId === issue.id} isHighlighted={associationChain?.issueId === issue.id} + isChecked={selectedIds.has(issue.id)} onSelect={() => handleSelect(issue.id)} - onSendToQueue={() => handleSendToQueue(issue.id)} + onToggleCheck={() => handleToggleSelect(issue.id)} /> ))}
)} + + {/* Send to Terminal bar */} + {selectedIds.size > 0 && ( +
+
+ + {selectedIds.size} selected + + +
+ +
+ )}
); } diff --git a/ccw/frontend/src/stores/configStore.ts b/ccw/frontend/src/stores/configStore.ts index 9e72f88e..f79041b3 100644 --- a/ccw/frontend/src/stores/configStore.ts +++ b/ccw/frontend/src/stores/configStore.ts @@ -100,6 +100,9 @@ const initialState: ConfigState = { darkModeEnabled: true, notificationsEnabled: true, experimentalFeatures: false, + dashboardQueuePanelEnabled: false, + dashboardInspectorEnabled: false, + dashboardExecutionMonitorEnabled: false, }, }; diff --git a/ccw/src/core/routes/skill-hub-routes.ts b/ccw/src/core/routes/skill-hub-routes.ts index a9ac7299..91510105 100644 --- a/ccw/src/core/routes/skill-hub-routes.ts +++ b/ccw/src/core/routes/skill-hub-routes.ts @@ -175,7 +175,7 @@ const GITHUB_CONFIG = { owner: 'catlog22', repo: 'skill-hub', branch: 'main', - skillIndexPath: 'skill-hub/index.json' + skillIndexPath: 'index.json' }; /**