mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-02 15:23:19 +08:00
feat: add queue management and terminal dashboard documentation in Chinese
- Introduced comprehensive documentation for the queue management feature, detailing its pain points, core functionalities, and component structure. - Added terminal dashboard documentation, highlighting its layout, core features, and usage examples. - Created an index page in Chinese for Claude Code Workflow, summarizing its purpose and core features, along with quick links to installation and guides.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
# Dashboard
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Dashboard provides an at-a-glance overview of your project's workflow status, statistics, and recent activity through an intuitive widget-based interface.**
|
||||
|
||||
---
|
||||
@@ -18,16 +17,15 @@
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/HomePage.tsx`
|
||||
|
||||
**Purpose**: Dashboard home page providing project overview, statistics, workflow status, and recent activity monitoring.
|
||||
|
||||
**Access**: Navigation → Dashboard (default home page)
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Dashboard (default home page at `/`)
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Dashboard Header (title + refresh) |
|
||||
@@ -55,6 +53,149 @@
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo DashboardOverview
|
||||
# dashboard-overview.tsx
|
||||
/**
|
||||
* Dashboard Overview Demo
|
||||
* Shows the main dashboard layout with widgets
|
||||
*/
|
||||
export function DashboardOverview() {
|
||||
return (
|
||||
<div className="space-y-6 p-6 bg-background min-h-[600px]">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Dashboard</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Project overview and activity monitoring
|
||||
</p>
|
||||
</div>
|
||||
<button className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Workflow Stats Widget */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="p-4 border-b bg-muted/30">
|
||||
<h2 className="font-semibold">Project Overview & Statistics</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Statistics</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
{ label: 'Active Sessions', value: '12', color: 'text-blue-500' },
|
||||
{ label: 'Total Tasks', value: '48', color: 'text-green-500' },
|
||||
{ label: 'Completed', value: '35', color: 'text-emerald-500' },
|
||||
{ label: 'Pending', value: '8', color: 'text-amber-500' },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="p-2 bg-muted/50 rounded">
|
||||
<div className={`text-lg font-bold ${stat.color}`}>{stat.value}</div>
|
||||
<div className="text-xs text-muted-foreground truncate">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Workflow Status</div>
|
||||
<div className="flex items-center justify-center h-24">
|
||||
<div className="relative w-20 h-20">
|
||||
<svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-muted opacity-20"/>
|
||||
<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" strokeWidth="3" className="text-blue-500" strokeDasharray="70 100"/>
|
||||
</svg>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-xs font-bold">70%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-center space-y-1">
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>Completed: 70%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="text-xs font-medium text-muted-foreground">Recent Session</div>
|
||||
<div className="p-3 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">Feature: Auth Flow</span>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-600">Running</span>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-green-500"/>
|
||||
<span>Implement login</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-amber-500"/>
|
||||
<span>Add OAuth</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<div className="w-3 h-3 rounded bg-muted"/>
|
||||
<span className="text-muted-foreground">Test flow</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Sessions Widget */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="border-b bg-muted/30">
|
||||
<div className="flex gap-1 p-2">
|
||||
{['All Tasks', 'Workflow', 'Lite Tasks'].map((tab, i) => (
|
||||
<button
|
||||
key={tab}
|
||||
className={`px-3 py-1.5 text-xs rounded-md transition-colors ${
|
||||
i === 0 ? 'bg-background text-foreground' : 'text-muted-foreground hover:bg-foreground/5'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ name: 'Refactor UI Components', status: 'In Progress', progress: 65 },
|
||||
{ name: 'Fix Login Bug', status: 'Pending', progress: 0 },
|
||||
{ name: 'Add Dark Mode', status: 'Completed', progress: 100 },
|
||||
].map((task, i) => (
|
||||
<div key={i} className="p-3 bg-muted/30 rounded border cursor-pointer hover:border-primary/30">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium line-clamp-1">{task.name}</span>
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
task.status === 'Completed' ? 'bg-green-500/20 text-green-600' :
|
||||
task.status === 'In Progress' ? 'bg-blue-500/20 text-blue-600' :
|
||||
'bg-gray-500/20 text-gray-600'
|
||||
}`}>{task.status}</span>
|
||||
</div>
|
||||
{task.progress > 0 && task.progress < 100 && (
|
||||
<div className="w-full h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 rounded-full" style={{ width: `${task.progress}%` }}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
@@ -68,85 +209,399 @@
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **View Project Overview**: Check the project info banner for tech stack and development index
|
||||
2. **Monitor Statistics**: Review mini stat cards for current project metrics and trends
|
||||
3. **Track Workflow Status**: View pie chart for session status distribution
|
||||
4. **Browse Active Sessions**: Use session carousel to see task details and progress
|
||||
5. **Access Recent Work**: Switch between All Tasks/Workflow/Lite Tasks tabs to find specific sessions
|
||||
|
||||
### Key Interactions
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Expand Project Details** | Click the chevron button in the project banner to show architecture, components, patterns |
|
||||
| **Navigate Sessions** | Click arrow buttons or wait for auto-rotation (5s interval) in the carousel |
|
||||
| **View Session Details** | Click on any session card to navigate to session detail page |
|
||||
| **Filter Recent Tasks** | Click tab buttons to filter by task type (All/Workflow/Lite) |
|
||||
| **Refresh Dashboard** | Click the refresh button in the header to reload all data |
|
||||
|
||||
### Index Status Indicator
|
||||
|
||||
| Status | Icon | Meaning |
|
||||
|--------|------|---------|
|
||||
| **Building** | Pulsing blue dot | Code index is being built/updated |
|
||||
| **Completed** | Green dot | Index is up-to-date |
|
||||
| **Idle** | Gray dot | Index status is unknown/idle |
|
||||
| **Failed** | Red dot | Index build failed |
|
||||
```
|
||||
HomePage
|
||||
├── DashboardHeader
|
||||
│ ├── Title
|
||||
│ └── Refresh Action Button
|
||||
├── WorkflowTaskWidget
|
||||
│ ├── ProjectInfoBanner (expandable)
|
||||
│ │ ├── Project Name & Description
|
||||
│ │ ├── Tech Stack Badges
|
||||
│ │ ├── Quick Stats Cards
|
||||
│ │ ├── Index Status Indicator
|
||||
│ │ ├── Architecture Section
|
||||
│ │ ├── Key Components
|
||||
│ │ └── Design Patterns
|
||||
│ ├── Stats Section
|
||||
│ │ └── MiniStatCard (6 cards with Sparkline)
|
||||
│ ├── WorkflowStatusChart
|
||||
│ │ └── Pie Chart with Legend
|
||||
│ └── SessionCarousel
|
||||
│ ├── Navigation Arrows
|
||||
│ └── Session Cards (Task List)
|
||||
└── RecentSessionsWidget
|
||||
├── Tab Navigation (All | Workflow | Lite)
|
||||
├── Task Grid
|
||||
│ └── TaskItemCard
|
||||
└── Loading/Empty States
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Reference
|
||||
## Props API
|
||||
|
||||
### Main Components
|
||||
### HomePage Component
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `DashboardHeader` | `@/components/dashboard/DashboardHeader.tsx` | Page header with title and refresh action |
|
||||
| `WorkflowTaskWidget` | `@/components/dashboard/widgets/WorkflowTaskWidget.tsx` | Combined widget with project info, stats, workflow status, and session carousel |
|
||||
| `RecentSessionsWidget` | `@/components/dashboard/widgets/RecentSessionsWidget.tsx` | Recent sessions across workflow and lite tasks |
|
||||
| `MiniStatCard` | (internal to WorkflowTaskWidget) | Individual stat card with optional sparkline |
|
||||
| `HomeEmptyState` | (internal to WorkflowTaskWidget) | Empty state display when no sessions exist |
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This page component accepts no props (data fetched via hooks) |
|
||||
|
||||
### State Management
|
||||
### WorkflowTaskWidget
|
||||
|
||||
- **Local state**:
|
||||
- `hasError` - Error tracking for critical failures
|
||||
- `projectExpanded` - Project info banner expansion state
|
||||
- `currentSessionIndex` - Active session in carousel
|
||||
- `activeTab` - Recent sessions widget filter tab
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `className` | `string` | `undefined` | Additional CSS classes for styling |
|
||||
|
||||
- **Custom hooks**:
|
||||
- `useWorkflowStatusCounts` - Session status distribution data
|
||||
- `useDashboardStats` - Statistics with auto-refresh (60s)
|
||||
- `useProjectOverview` - Project information and tech stack
|
||||
- `useIndexStatus` - Real-time index status (30s refresh)
|
||||
- `useSessions` - Active sessions data
|
||||
- `useLiteTasks` - Lite tasks data for recent widget
|
||||
### RecentSessionsWidget
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `className` | `string` | `undefined` | Additional CSS classes |
|
||||
| `maxItems` | `number` | `6` | Maximum number of items to display |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
## Usage Examples
|
||||
|
||||
No configuration required. The dashboard automatically fetches data from the backend.
|
||||
### Basic Dashboard
|
||||
|
||||
### Auto-Refresh Intervals
|
||||
```tsx
|
||||
import { HomePage } from '@/pages/HomePage'
|
||||
|
||||
| Data Type | Interval |
|
||||
|-----------|----------|
|
||||
| Dashboard stats | 60 seconds |
|
||||
| Index status | 30 seconds |
|
||||
| Discovery sessions | 3 seconds (on discovery page) |
|
||||
// The dashboard is automatically rendered at the root route (/)
|
||||
// No props needed - data is fetched via hooks
|
||||
```
|
||||
|
||||
### Embedding WorkflowTaskWidget
|
||||
|
||||
```tsx
|
||||
import { WorkflowTaskWidget } from '@/components/dashboard/widgets/WorkflowTaskWidget'
|
||||
|
||||
function CustomDashboard() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<WorkflowTaskWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Recent Sessions Widget
|
||||
|
||||
```tsx
|
||||
import { RecentSessionsWidget } from '@/components/dashboard/widgets/RecentSessionsWidget'
|
||||
|
||||
function ActivityFeed() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<RecentSessionsWidget maxItems={10} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State
|
||||
|
||||
| State | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `hasError` | `boolean` | Error tracking for critical failures |
|
||||
| `projectExpanded` | `boolean` | Project info banner expansion state |
|
||||
| `currentSessionIndex` | `number` | Active session index in carousel |
|
||||
| `activeTab` | `'all' \| 'workflow' \| 'lite'` | Recent sessions widget filter tab |
|
||||
|
||||
### Store Selectors (Zustand)
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `appStore` | `selectIsImmersiveMode` | Check if immersive mode is active |
|
||||
|
||||
### Custom Hooks (Data Fetching)
|
||||
|
||||
| Hook | Description | Refetch Interval |
|
||||
|------|-------------|------------------|
|
||||
| `useWorkflowStatusCounts` | Session status distribution data | - |
|
||||
| `useDashboardStats` | Statistics with sparkline data | 60 seconds |
|
||||
| `useProjectOverview` | Project information and tech stack | - |
|
||||
| `useIndexStatus` | Real-time index status | 30 seconds |
|
||||
| `useSessions` | Active sessions data | - |
|
||||
| `useLiteTasks` | Lite tasks data for recent widget | - |
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Statistics Cards Demo
|
||||
|
||||
:::demo MiniStatCards
|
||||
# mini-stat-cards.tsx
|
||||
/**
|
||||
* Mini Stat Cards Demo
|
||||
* Individual statistics cards with sparkline trends
|
||||
*/
|
||||
export function MiniStatCards() {
|
||||
const stats = [
|
||||
{ label: 'Active Sessions', value: 12, trend: [8, 10, 9, 11, 10, 12, 12], color: 'blue' },
|
||||
{ label: 'Total Tasks', value: 48, trend: [40, 42, 45, 44, 46, 47, 48], color: 'green' },
|
||||
{ label: 'Completed', value: 35, trend: [25, 28, 30, 32, 33, 34, 35], color: 'emerald' },
|
||||
{ label: 'Pending', value: 8, trend: [12, 10, 11, 9, 8, 7, 8], color: 'amber' },
|
||||
{ label: 'Failed', value: 5, trend: [3, 4, 3, 5, 4, 5, 5], color: 'red' },
|
||||
{ label: 'Today Activity', value: 23, trend: [5, 10, 15, 18, 20, 22, 23], color: 'purple' },
|
||||
]
|
||||
|
||||
const colorMap = {
|
||||
blue: 'text-blue-500 bg-blue-500/10',
|
||||
green: 'text-green-500 bg-green-500/10',
|
||||
emerald: 'text-emerald-500 bg-emerald-500/10',
|
||||
amber: 'text-amber-500 bg-amber-500/10',
|
||||
red: 'text-red-500 bg-red-500/10',
|
||||
purple: 'text-purple-500 bg-purple-500/10',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Statistics with Sparklines</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{stats.map((stat, i) => (
|
||||
<div key={i} className="p-4 border rounded-lg bg-card">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs text-muted-foreground">{stat.label}</span>
|
||||
<div className={`w-2 h-2 rounded-full ${colorMap[stat.color].split(' ')[1]}`}/>
|
||||
</div>
|
||||
<div className={`text-2xl font-bold ${colorMap[stat.color].split(' ')[0]}`}>{stat.value}</div>
|
||||
<div className="mt-2 h-8 flex items-end gap-0.5">
|
||||
{stat.trend.map((v, j) => (
|
||||
<div
|
||||
key={j}
|
||||
className="flex-1 rounded-t"
|
||||
style={{
|
||||
height: `${(v / Math.max(...stat.trend)) * 100}%`,
|
||||
backgroundColor: v === stat.value ? 'currentColor' : 'rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Project Info Banner Demo
|
||||
|
||||
:::demo ProjectInfoBanner
|
||||
# project-info-banner.tsx
|
||||
/**
|
||||
* Project Info Banner Demo
|
||||
* Expandable project information with tech stack
|
||||
*/
|
||||
export function ProjectInfoBanner() {
|
||||
const [expanded, setExpanded] = React.useState(false)
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Project Info Banner</h3>
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
{/* Banner Header */}
|
||||
<div className="p-4 bg-muted/30 flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-semibold">My Awesome Project</h4>
|
||||
<p className="text-sm text-muted-foreground">A modern web application built with React</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="p-2 rounded-md hover:bg-accent"
|
||||
>
|
||||
<svg className={`w-5 h-5 transition-transform ${expanded ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tech Stack Badges */}
|
||||
<div className="px-4 pb-3 flex flex-wrap gap-2">
|
||||
{['TypeScript', 'React', 'Vite', 'Tailwind CSS', 'Zustand'].map((tech) => (
|
||||
<span key={tech} className="px-2 py-1 text-xs rounded-full bg-primary/10 text-primary">
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{expanded && (
|
||||
<div className="p-4 border-t bg-muted/20 space-y-4">
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">Architecture</h5>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>• Component-based UI architecture</div>
|
||||
<div>• Centralized state management</div>
|
||||
<div>• RESTful API integration</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="text-xs font-semibold mb-2">Key Components</h5>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
{['Session Manager', 'Dashboard', 'Task Scheduler', 'Analytics'].map((comp) => (
|
||||
<div key={comp} className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-primary"/>
|
||||
{comp}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Session Carousel Demo
|
||||
|
||||
:::demo SessionCarousel
|
||||
# session-carousel.tsx
|
||||
/**
|
||||
* Session Carousel Demo
|
||||
* Auto-rotating session cards with navigation
|
||||
*/
|
||||
export function SessionCarousel() {
|
||||
const [currentIndex, setCurrentIndex] = React.useState(0)
|
||||
const sessions = [
|
||||
{
|
||||
name: 'Feature: User Authentication',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: 'Implement login form', status: 'completed' },
|
||||
{ name: 'Add OAuth provider', status: 'in-progress' },
|
||||
{ name: 'Create session management', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Bug Fix: Memory Leak',
|
||||
status: 'running',
|
||||
tasks: [
|
||||
{ name: 'Identify leak source', status: 'completed' },
|
||||
{ name: 'Fix cleanup handlers', status: 'in-progress' },
|
||||
{ name: 'Add unit tests', status: 'pending' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Refactor: API Layer',
|
||||
status: 'planning',
|
||||
tasks: [
|
||||
{ name: 'Design new interface', status: 'pending' },
|
||||
{ name: 'Migrate existing endpoints', status: 'pending' },
|
||||
{ name: 'Update documentation', status: 'pending' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const statusColors = {
|
||||
completed: 'bg-green-500',
|
||||
'in-progress': 'bg-amber-500',
|
||||
pending: 'bg-muted',
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setCurrentIndex((i) => (i + 1) % sessions.length)
|
||||
}, 5000)
|
||||
return () => clearInterval(timer)
|
||||
}, [sessions.length])
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background">
|
||||
<h3 className="text-sm font-semibold mb-4">Session Carousel (auto-rotates every 5s)</h3>
|
||||
<div className="border rounded-lg p-4 bg-card">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-sm font-medium">Session {currentIndex + 1} of {sessions.length}</span>
|
||||
<div className="flex gap-1">
|
||||
{sessions.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentIndex(i)}
|
||||
className={`w-2 h-2 rounded-full transition-colors ${
|
||||
i === currentIndex ? 'bg-primary' : 'bg-muted-foreground/30'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-accent/20 rounded border">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="font-medium">{sessions[currentIndex].name}</span>
|
||||
<span className={`text-xs px-2 py-1 rounded-full ${
|
||||
sessions[currentIndex].status === 'running' ? 'bg-green-500/20 text-green-600' : 'bg-blue-500/20 text-blue-600'
|
||||
}`}>
|
||||
{sessions[currentIndex].status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{sessions[currentIndex].tasks.map((task, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-sm">
|
||||
<div className={`w-3 h-3 rounded ${statusColors[task.status]}`}/>
|
||||
<span className={task.status === 'pending' ? 'text-muted-foreground' : ''}>{task.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-3">
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i - 1 + sessions.length) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentIndex((i) => (i + 1) % sessions.length)}
|
||||
className="px-3 py-1.5 text-sm border rounded-md hover:bg-accent"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through interactive elements
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate buttons and cards
|
||||
- <kbd>Arrow</kbd> keys - Navigate carousel sessions
|
||||
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on navigation buttons
|
||||
- `aria-expanded` on expandable sections
|
||||
- `aria-live` regions for real-time updates
|
||||
|
||||
- **Screen Reader Support**:
|
||||
- All charts have text descriptions
|
||||
- Status indicators include text labels
|
||||
- Navigation is announced properly
|
||||
|
||||
---
|
||||
|
||||
## Related Links
|
||||
|
||||
- [Sessions](/features/sessions) - View and manage all sessions
|
||||
- [Terminal Dashboard](/features/terminal) - Terminal-first monitoring interface
|
||||
- [Queue](/features/queue) - Issue execution queue management
|
||||
- [Discovery](/features/discovery) - Discovery session tracking
|
||||
- [Memory](/features/memory) - Persistent memory management
|
||||
- [System Settings](/features/system-settings) - Global application settings
|
||||
- [Settings](/features/settings) - Global application settings
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Queue
|
||||
# Queue Management
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Queue page manages issue execution queues, displaying grouped tasks and solutions with conflict detection and merge capabilities.**
|
||||
**The Queue Management page provides centralized control over issue execution queues with scheduler controls, status monitoring, and session pool management.**
|
||||
|
||||
---
|
||||
|
||||
@@ -11,153 +10,627 @@
|
||||
| Pain Point | Current State | Queue Solution |
|
||||
|------------|---------------|----------------|
|
||||
| **Disorganized execution** | No unified task queue | Centralized queue with grouped items |
|
||||
| **Unknown queue status** | Can't tell if queue is ready | Status indicator with conflicts warning |
|
||||
| **Manual queue management** | No way to control execution | Activate/deactivate/delete with actions |
|
||||
| **Duplicate handling** | Confusing duplicate items | Merge queues functionality |
|
||||
| **No visibility** | Don't know what's queued | Stats cards with items/groups/tasks/solutions counts |
|
||||
| **Unknown scheduler status** | Can't tell if scheduler is running | Real-time status indicator (idle/running/paused) |
|
||||
| **No execution control** | Can't start/stop queue processing | Start/Pause/Stop controls with confirmation |
|
||||
| **Concurrency limits** | Too many simultaneous sessions | Configurable max concurrent sessions |
|
||||
| **No visibility** | Don't know what's queued | Stats cards + item list with status tracking |
|
||||
| **Resource waste** | Idle sessions consuming resources | Session pool overview with timeout config |
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/QueuePage.tsx`
|
||||
**Location**: `ccw/frontend/src/pages/QueuePage.tsx` (legacy), `ccw/frontend/src/components/terminal-dashboard/QueuePanel.tsx` (current)
|
||||
|
||||
**Purpose**: View and manage issue execution queues with stats, conflict detection, and queue operations.
|
||||
**Purpose**: View and manage issue execution queues with scheduler controls, progress tracking, and session pool management.
|
||||
|
||||
**Access**: Navigation → Issues → Queue tab OR directly via `/queue` route
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Issues → Queue tab OR Terminal Dashboard → Queue floating panel
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Queue Title [Refresh] |
|
||||
| Queue Panel Header |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Stats Cards |
|
||||
| +-------------+ +-------------+ +-------------+ +-------------+ |
|
||||
| | Total Items | | Groups | | Tasks | | Solutions | |
|
||||
| +-------------+ +-------------+ +-------------+ +-------------+ |
|
||||
| Scheduler Status Bar |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
| | Status Badge | | Progress | | Concurrency (2/2) | |
|
||||
| +----------------+ +-------------+ +-------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Conflicts Warning (when conflicts exist) |
|
||||
| Scheduler Controls |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
| | Start | | Pause | | Stop | | Config | |
|
||||
| +--------+ +--------+ +--------+ +-----------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Queue Cards (1-2 columns) |
|
||||
| Queue Items List |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | QueueCard | |
|
||||
| | - Queue info | |
|
||||
| | - Grouped items preview | |
|
||||
| | - Action buttons (Activate/Deactivate/Delete/Merge) | |
|
||||
| | QueueItemRow (status, issue_id, session_key, actions) | |
|
||||
| | - Status icon (pending/executing/completed/blocked/failed) | |
|
||||
| | - Issue ID / Item ID display | |
|
||||
| | - Session binding info | |
|
||||
| | - Progress indicator (for executing items) | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
| | [More queue items...] | |
|
||||
| +---------------------------------------------------------------------+ |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Status Footer (Ready/Pending indicator) |
|
||||
+--------------------------------------------------------------------------+
|
||||
| Session Pool Overview (optional) |
|
||||
| +--------------------------------------------------------------------------+
|
||||
| | Active Sessions | Idle Sessions | Total Sessions |
|
||||
| +--------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo QueueManagementDemo
|
||||
# queue-management-demo.tsx
|
||||
/**
|
||||
* Queue Management Demo
|
||||
* Shows scheduler controls and queue items list
|
||||
*/
|
||||
export function QueueManagementDemo() {
|
||||
const [schedulerStatus, setSchedulerStatus] = React.useState('idle')
|
||||
const [progress, setProgress] = React.useState(0)
|
||||
|
||||
const queueItems = [
|
||||
{ id: '1', status: 'completed', issueId: 'ISSUE-1', sessionKey: 'session-1' },
|
||||
{ id: '2', status: 'executing', issueId: 'ISSUE-2', sessionKey: 'session-2' },
|
||||
{ id: '3', status: 'pending', issueId: 'ISSUE-3', sessionKey: null },
|
||||
{ id: '4', status: 'pending', issueId: 'ISSUE-4', sessionKey: null },
|
||||
{ id: '5', status: 'blocked', issueId: 'ISSUE-5', sessionKey: null },
|
||||
]
|
||||
|
||||
const statusConfig = {
|
||||
idle: { label: 'Idle', color: 'bg-gray-500/20 text-gray-600 border-gray-500' },
|
||||
running: { label: 'Running', color: 'bg-green-500/20 text-green-600 border-green-500' },
|
||||
paused: { label: 'Paused', color: 'bg-amber-500/20 text-amber-600 border-amber-500' },
|
||||
}
|
||||
|
||||
const itemStatusConfig = {
|
||||
completed: { icon: '✓', color: 'text-green-500', label: 'Completed' },
|
||||
executing: { icon: '▶', color: 'text-blue-500', label: 'Executing' },
|
||||
pending: { icon: '○', color: 'text-gray-400', label: 'Pending' },
|
||||
blocked: { icon: '✕', color: 'text-red-500', label: 'Blocked' },
|
||||
failed: { icon: '!', color: 'text-red-500', label: 'Failed' },
|
||||
}
|
||||
|
||||
// Simulate progress when running
|
||||
React.useEffect(() => {
|
||||
if (schedulerStatus === 'running') {
|
||||
const interval = setInterval(() => {
|
||||
setProgress((p) => (p >= 100 ? 0 : p + 10))
|
||||
}, 500)
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [schedulerStatus])
|
||||
|
||||
const handleStart = () => {
|
||||
if (schedulerStatus === 'idle' || schedulerStatus === 'paused') {
|
||||
setSchedulerStatus('running')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePause = () => {
|
||||
if (schedulerStatus === 'running') {
|
||||
setSchedulerStatus('paused')
|
||||
}
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
setSchedulerStatus('idle')
|
||||
setProgress(0)
|
||||
}
|
||||
|
||||
const currentConfig = statusConfig[schedulerStatus]
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Queue Management</h3>
|
||||
<p className="text-sm text-muted-foreground">Manage issue execution queue</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Scheduler Status Bar */}
|
||||
<div className="border rounded-lg p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={`px-3 py-1 rounded text-xs font-medium border ${currentConfig.color}`}>
|
||||
{currentConfig.label}
|
||||
</span>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span>{queueItems.filter((i) => i.status === 'completed').length}/{queueItems.length} items</span>
|
||||
<span>2/2 concurrent</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
{schedulerStatus === 'running' && (
|
||||
<div className="space-y-1">
|
||||
<div className="h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{progress}% complete</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Scheduler Controls */}
|
||||
<div className="flex items-center gap-2">
|
||||
{(schedulerStatus === 'idle' || schedulerStatus === 'paused') && (
|
||||
<button
|
||||
onClick={handleStart}
|
||||
className="px-4 py-2 text-sm bg-green-500 text-white rounded hover:bg-green-600 flex items-center gap-2"
|
||||
>
|
||||
<span>▶</span> Start
|
||||
</button>
|
||||
)}
|
||||
{schedulerStatus === 'running' && (
|
||||
<button
|
||||
onClick={handlePause}
|
||||
className="px-4 py-2 text-sm bg-amber-500 text-white rounded hover:bg-amber-600 flex items-center gap-2"
|
||||
>
|
||||
<span>⏸</span> Pause
|
||||
</button>
|
||||
)}
|
||||
{schedulerStatus !== 'idle' && (
|
||||
<button
|
||||
onClick={handleStop}
|
||||
className="px-4 py-2 text-sm bg-red-500 text-white rounded hover:bg-red-600 flex items-center gap-2"
|
||||
>
|
||||
<span>⬛</span> Stop
|
||||
</button>
|
||||
)}
|
||||
<button className="px-4 py-2 text-sm border rounded hover:bg-accent">
|
||||
Config
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Queue Items List */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="px-4 py-3 border-b bg-muted/30">
|
||||
<h4 className="text-sm font-semibold">Queue Items</h4>
|
||||
</div>
|
||||
<div className="divide-y max-h-80 overflow-auto">
|
||||
{queueItems.map((item) => {
|
||||
const config = itemStatusConfig[item.status]
|
||||
return (
|
||||
<div key={item.id} className="px-4 py-3 flex items-center gap-4 hover:bg-accent/50">
|
||||
<span className={`text-lg ${config.color}`}>{config.icon}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">{item.issueId}</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${config.color} bg-opacity-10`}>
|
||||
{config.label}
|
||||
</span>
|
||||
</div>
|
||||
{item.sessionKey && (
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Session: {item.sessionKey}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{item.status === 'executing' && (
|
||||
<div className="w-20 h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 animate-pulse" style={{ width: '60%' }}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Stats Cards** | Four metric cards showing total items, groups, tasks, and solutions counts |
|
||||
| **Conflicts Warning** | Banner alert when conflicts exist, showing count and description |
|
||||
| **Queue Card** | Displays queue information with grouped items preview and action buttons |
|
||||
| **Activate/Deactivate** | Toggle queue active state for execution control |
|
||||
| **Delete Queue** | Remove queue with confirmation dialog |
|
||||
| **Merge Queues** | Combine multiple queues (if multiple exist) |
|
||||
| **Status Footer** | Visual indicator showing if queue is ready (active) or pending (inactive/conflicts) |
|
||||
| **Loading State** | Skeleton placeholders during data fetch |
|
||||
| **Empty State** | Friendly message when no queue exists |
|
||||
| **Scheduler Status** | Real-time status indicator (idle/running/paused) with visual badge |
|
||||
| **Progress Tracking** | Progress bar showing overall queue completion percentage |
|
||||
| **Start/Pause/Stop Controls** | Control queue execution with confirmation dialog for stop action |
|
||||
| **Concurrency Display** | Shows current active sessions vs max concurrent sessions |
|
||||
| **Queue Items List** | Scrollable list of all queue items with status, issue ID, and session binding |
|
||||
| **Status Icons** | Visual indicators for item status (pending/executing/completed/blocked/failed) |
|
||||
| **Session Pool** | Overview of active, idle, and total sessions in the pool |
|
||||
| **Config Panel** | Adjust max concurrent sessions and timeout settings |
|
||||
| **Empty State** | Friendly message when queue is empty with instructions to add items |
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **Check Queue Status**: Review stats cards and status footer to understand queue state
|
||||
2. **Review Conflicts**: If conflicts warning is shown, resolve conflicts before activation
|
||||
3. **Activate Queue**: Click "Activate" to enable queue for execution (only if no conflicts)
|
||||
4. **Deactivate Queue**: Click "Deactivate" to pause execution
|
||||
5. **Delete Queue**: Click "Delete" to remove the queue (requires confirmation)
|
||||
6. **Merge Queues**: Use merge action to combine multiple queues when applicable
|
||||
|
||||
### Key Interactions
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Refresh queue** | Click the refresh button to reload queue data |
|
||||
| **Activate queue** | Click the Activate button on the queue card |
|
||||
| **Deactivate queue** | Click the Deactivate button to pause the queue |
|
||||
| **Delete queue** | Click the Delete button, confirm in the dialog |
|
||||
| **Merge queues** | Select source and target queues, click merge |
|
||||
| **View status** | Check the status footer for ready/pending indication |
|
||||
|
||||
### Queue Status
|
||||
|
||||
| Status | Condition | Appearance |
|
||||
|--------|-----------|------------|
|
||||
| **Ready/Active** | Total items > 0 AND no conflicts | Green badge with checkmark |
|
||||
| **Pending/Inactive** | No items OR conflicts exist | Gray/amber badge with clock icon |
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
When conflicts are detected:
|
||||
1. A warning banner appears showing conflict count
|
||||
2. Queue cannot be activated until conflicts are resolved
|
||||
3. Status footer shows "Pending" state
|
||||
4. Resolve conflicts through the Issues panel or related workflows
|
||||
```
|
||||
QueuePage (legacy) / QueuePanel (current)
|
||||
├── QueuePanelHeader
|
||||
│ ├── Title
|
||||
│ └── Tab Switcher (Queue | Orchestrator)
|
||||
├── SchedulerBar (inline in QueueListColumn)
|
||||
│ ├── Status Badge
|
||||
│ ├── Progress + Concurrency
|
||||
│ └── Control Buttons (Play/Pause/Stop)
|
||||
├── QueueItemsList
|
||||
│ └── QueueItemRow (repeating)
|
||||
│ ├── Status Icon
|
||||
│ ├── Issue ID / Item ID
|
||||
│ ├── Session Binding
|
||||
│ └── Progress (for executing items)
|
||||
└── SchedulerPanel (standalone)
|
||||
├── Status Section
|
||||
├── Progress Bar
|
||||
├── Control Buttons
|
||||
├── Config Section
|
||||
│ ├── Max Concurrent Sessions
|
||||
│ ├── Session Idle Timeout
|
||||
│ └── Resume Key Binding Timeout
|
||||
└── Session Pool Overview
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Reference
|
||||
## Props API
|
||||
|
||||
### Main Components
|
||||
### QueuePanel
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `QueuePage` | `@/pages/QueuePage.tsx` | Main queue management page |
|
||||
| `QueueCard` | `@/components/issue/queue/QueueCard.tsx` | Queue display with actions |
|
||||
| `QueuePageSkeleton` | (internal to QueuePage) | Loading placeholder |
|
||||
| `QueueEmptyState` | (internal to QueuePage) | Empty state display |
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `embedded` | `boolean` | `false` | Whether panel is embedded in another component |
|
||||
|
||||
### State Management
|
||||
### SchedulerPanel
|
||||
|
||||
- **Local state**:
|
||||
- None (all data from hooks)
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This component accepts no props (all data from Zustand store) |
|
||||
|
||||
- **Custom hooks**:
|
||||
- `useIssueQueue` - Queue data fetching
|
||||
- `useQueueMutations` - Queue operations (activate, deactivate, delete, merge)
|
||||
### QueueListColumn
|
||||
|
||||
### Mutation States
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This component accepts no props (all data from Zustand store) |
|
||||
|
||||
| State | Loading Indicator |
|
||||
|-------|------------------|
|
||||
| `isActivating` | Disable activate button during activation |
|
||||
| `isDeactivating` | Disable deactivate button during deactivation |
|
||||
| `isDeleting` | Disable delete button during deletion |
|
||||
| `isMerging` | Disable merge button during merge operation |
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `queueSchedulerStore` | `selectQueueSchedulerStatus` | Current scheduler status (idle/running/paused) |
|
||||
| `queueSchedulerStore` | `selectSchedulerProgress` | Overall queue completion percentage |
|
||||
| `queueSchedulerStore` | `selectQueueItems` | List of all queue items |
|
||||
| `queueSchedulerStore` | `selectCurrentConcurrency` | Active sessions count |
|
||||
| `queueSchedulerStore` | `selectSchedulerConfig` | Scheduler configuration |
|
||||
| `queueSchedulerStore` | `selectSessionPool` | Session pool overview |
|
||||
| `queueSchedulerStore` | `selectSchedulerError` | Error message if any |
|
||||
| `issueQueueIntegrationStore` | `selectAssociationChain` | Current association chain for highlighting |
|
||||
| `queueExecutionStore` | `selectByQueueItem` | Execution data for queue item |
|
||||
|
||||
### Queue Item Status
|
||||
|
||||
```typescript
|
||||
type QueueItemStatus =
|
||||
| 'pending' // Waiting to be executed
|
||||
| 'executing' // Currently being processed
|
||||
| 'completed' // Finished successfully
|
||||
| 'blocked' // Blocked by dependency
|
||||
| 'failed'; // Failed with error
|
||||
```
|
||||
|
||||
### Scheduler Status
|
||||
|
||||
```typescript
|
||||
type QueueSchedulerStatus =
|
||||
| 'idle' // No items or stopped
|
||||
| 'running' // Actively processing items
|
||||
| 'paused'; // Temporarily paused
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Queue Panel
|
||||
|
||||
```tsx
|
||||
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel'
|
||||
|
||||
function QueueSection() {
|
||||
return <QueuePanel />
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Scheduler Panel
|
||||
|
||||
```tsx
|
||||
import { SchedulerPanel } from '@/components/terminal-dashboard/SchedulerPanel'
|
||||
|
||||
function SchedulerControls() {
|
||||
return <SchedulerPanel />
|
||||
}
|
||||
```
|
||||
|
||||
### Queue List Column (Embedded)
|
||||
|
||||
```tsx
|
||||
import { QueueListColumn } from '@/components/terminal-dashboard/QueueListColumn'
|
||||
|
||||
function EmbeddedQueue() {
|
||||
return <QueueListColumn />
|
||||
}
|
||||
```
|
||||
|
||||
### Queue Store Actions
|
||||
|
||||
```tsx
|
||||
import { useQueueSchedulerStore } from '@/stores/queueSchedulerStore'
|
||||
|
||||
function QueueActions() {
|
||||
const startQueue = useQueueSchedulerStore((s) => s.startQueue)
|
||||
const pauseQueue = useQueueSchedulerStore((s) => s.pauseQueue)
|
||||
const stopQueue = useQueueSchedulerStore((s) => s.stopQueue)
|
||||
const updateConfig = useQueueSchedulerStore((s) => s.updateConfig)
|
||||
|
||||
const handleStart = () => startQueue()
|
||||
const handlePause = () => pauseQueue()
|
||||
const handleStop = () => stopQueue()
|
||||
const handleConfig = (config) => updateConfig(config)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleStart}>Start</button>
|
||||
<button onClick={handlePause}>Pause</button>
|
||||
<button onClick={handleStop}>Stop</button>
|
||||
<button onClick={() => handleConfig({ maxConcurrentSessions: 4 })}>
|
||||
Set Max 4
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Queue Item Status Demo
|
||||
|
||||
:::demo QueueItemStatusDemo
|
||||
# queue-item-status-demo.tsx
|
||||
/**
|
||||
* Queue Item Status Demo
|
||||
* Shows all possible queue item states
|
||||
*/
|
||||
export function QueueItemStatusDemo() {
|
||||
const itemStates = [
|
||||
{ status: 'pending', issueId: 'ISSUE-101', sessionKey: null },
|
||||
{ status: 'executing', issueId: 'ISSUE-102', sessionKey: 'cli-session-abc' },
|
||||
{ status: 'completed', issueId: 'ISSUE-103', sessionKey: 'cli-session-def' },
|
||||
{ status: 'blocked', issueId: 'ISSUE-104', sessionKey: null },
|
||||
{ status: 'failed', issueId: 'ISSUE-105', sessionKey: 'cli-session-ghi' },
|
||||
]
|
||||
|
||||
const statusConfig = {
|
||||
pending: { icon: '○', color: 'text-gray-400', bg: 'bg-gray-500/10', label: 'Pending' },
|
||||
executing: { icon: '▶', color: 'text-blue-500', bg: 'bg-blue-500/10', label: 'Executing' },
|
||||
completed: { icon: '✓', color: 'text-green-500', bg: 'bg-green-500/10', label: 'Completed' },
|
||||
blocked: { icon: '✕', color: 'text-red-500', bg: 'bg-red-500/10', label: 'Blocked' },
|
||||
failed: { icon: '!', color: 'text-red-500', bg: 'bg-red-500/10', label: 'Failed' },
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-4">
|
||||
<h3 className="text-sm font-semibold">Queue Item Status States</h3>
|
||||
<div className="space-y-2">
|
||||
{itemStates.map((item) => {
|
||||
const config = statusConfig[item.status]
|
||||
return (
|
||||
<div key={item.status} className="border rounded-lg p-4 flex items-center gap-4">
|
||||
<span className={`text-2xl ${config.color}`}>{config.icon}</span>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{item.issueId}</span>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${config.bg} ${config.color}`}>
|
||||
{config.label}
|
||||
</span>
|
||||
</div>
|
||||
{item.sessionKey && (
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
Bound session: <code className="text-xs bg-muted px-1 rounded">{item.sessionKey}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{item.status === 'executing' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-24 h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div className="h-full bg-blue-500 animate-pulse" style={{ width: '60%' }}/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">60%</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Scheduler Config Demo
|
||||
|
||||
:::demo SchedulerConfigDemo
|
||||
# scheduler-config-demo.tsx
|
||||
/**
|
||||
* Scheduler Config Demo
|
||||
* Interactive configuration panel
|
||||
*/
|
||||
export function SchedulerConfigDemo() {
|
||||
const [config, setConfig] = React.useState({
|
||||
maxConcurrentSessions: 2,
|
||||
sessionIdleTimeoutMs: 60000,
|
||||
resumeKeySessionBindingTimeoutMs: 300000,
|
||||
})
|
||||
|
||||
const formatMs = (ms) => {
|
||||
if (ms >= 60000) return `${ms / 60000}m`
|
||||
if (ms >= 1000) return `${ms / 1000}s`
|
||||
return `${ms}ms`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-6 max-w-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Scheduler Configuration</h3>
|
||||
<button className="px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded hover:opacity-90">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Max Concurrent Sessions */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Max Concurrent Sessions</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="8"
|
||||
value={config.maxConcurrentSessions}
|
||||
onChange={(e) => setConfig({ ...config, maxConcurrentSessions: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-8 text-center">{config.maxConcurrentSessions}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Maximum number of sessions to run simultaneously
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Session Idle Timeout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Session Idle Timeout</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="10000"
|
||||
max="300000"
|
||||
step="10000"
|
||||
value={config.sessionIdleTimeoutMs}
|
||||
onChange={(e) => setConfig({ ...config, sessionIdleTimeoutMs: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-12 text-right">{formatMs(config.sessionIdleTimeoutMs)}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Time before idle session is terminated
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Resume Key Binding Timeout */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Resume Key Binding Timeout</label>
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min="60000"
|
||||
max="600000"
|
||||
step="60000"
|
||||
value={config.resumeKeySessionBindingTimeoutMs}
|
||||
onChange={(e) => setConfig({ ...config, resumeKeySessionBindingTimeoutMs: parseInt(e.target.value) })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm font-medium w-12 text-right">{formatMs(config.resumeKeySessionBindingTimeoutMs)}</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Time to preserve resume key session binding
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Config Display */}
|
||||
<div className="border rounded-lg p-4 bg-muted/30">
|
||||
<h4 className="text-xs font-semibold mb-3">Current Configuration</h4>
|
||||
<dl className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Max Concurrent</dt>
|
||||
<dd className="font-medium">{config.maxConcurrentSessions} sessions</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Idle Timeout</dt>
|
||||
<dd className="font-medium">{formatMs(config.sessionIdleTimeoutMs)}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-muted-foreground">Binding Timeout</dt>
|
||||
<dd className="font-medium">{formatMs(config.resumeKeySessionBindingTimeoutMs)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
No configuration required. Queue data is automatically fetched from the backend.
|
||||
### Scheduler Config
|
||||
|
||||
### Queue Data Structure
|
||||
| Setting | Type | Default | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `maxConcurrentSessions` | `number` | `2` | Maximum sessions running simultaneously |
|
||||
| `sessionIdleTimeoutMs` | `number` | `60000` | Idle session timeout in milliseconds |
|
||||
| `resumeKeySessionBindingTimeoutMs` | `number` | `300000` | Resume key binding timeout in milliseconds |
|
||||
|
||||
### Queue Item Structure
|
||||
|
||||
```typescript
|
||||
interface QueueData {
|
||||
tasks: Task[];
|
||||
solutions: Solution[];
|
||||
conflicts: Conflict[];
|
||||
grouped_items: Record<string, GroupedItem>;
|
||||
interface QueueItem {
|
||||
item_id: string;
|
||||
issue_id?: string;
|
||||
sessionKey?: string;
|
||||
status: QueueItemStatus;
|
||||
execution_order: number;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through queue items and controls
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate buttons
|
||||
- <kbd>Escape</kbd> - Close dialogs
|
||||
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on control buttons
|
||||
- `aria-live` regions for status updates
|
||||
- `aria-current` for active queue item
|
||||
- `role="list"` on queue items list
|
||||
|
||||
- **Screen Reader Support**:
|
||||
- Status changes announced
|
||||
- Progress updates spoken
|
||||
- Error messages announced
|
||||
|
||||
---
|
||||
|
||||
## Related Links
|
||||
|
||||
- [Issue Hub](/features/issue-hub) - Unified issues, queue, and discovery management
|
||||
- [Terminal Dashboard](/features/terminal) - Terminal-first workspace with integrated queue panel
|
||||
- [Discovery](/features/discovery) - Discovery session tracking
|
||||
- [Issues Panel](/features/issue-hub) - Issue list and GitHub sync
|
||||
- [Sessions](/features/sessions) - Session management and details
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Terminal Dashboard
|
||||
|
||||
## One-Liner
|
||||
|
||||
**The Terminal Dashboard provides a terminal-first workspace with resizable panes, floating panels, and integrated tools for session monitoring and orchestration.**
|
||||
|
||||
---
|
||||
@@ -18,16 +17,15 @@
|
||||
|
||||
---
|
||||
|
||||
## Page Overview
|
||||
## Overview
|
||||
|
||||
**Location**: `ccw/frontend/src/pages/TerminalDashboardPage.tsx`
|
||||
|
||||
**Purpose**: Terminal-first layout for multi-terminal session management with integrated tools and resizable panels.
|
||||
|
||||
**Access**: Navigation → Terminal Dashboard
|
||||
|
||||
### Layout
|
||||
**Access**: Navigation → Terminal Dashboard (`/terminal-dashboard`)
|
||||
|
||||
**Layout**:
|
||||
```
|
||||
+--------------------------------------------------------------------------+
|
||||
| Dashboard Toolbar (panel toggles, layout presets, fullscreen) |
|
||||
@@ -48,6 +46,134 @@
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
:::demo TerminalDashboardOverview
|
||||
# terminal-dashboard-overview.tsx
|
||||
/**
|
||||
* Terminal Dashboard Overview Demo
|
||||
* Shows the three-column layout with resizable panes and toolbar
|
||||
*/
|
||||
export function TerminalDashboardOverview() {
|
||||
const [fileSidebarOpen, setFileSidebarOpen] = React.useState(true)
|
||||
const [sessionSidebarOpen, setSessionSidebarOpen] = React.useState(true)
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
return (
|
||||
<div className="h-[600px] flex flex-col bg-background">
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b bg-muted/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium">Terminal Dashboard</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{['Sessions', 'Files', 'Issues', 'Queue', 'Inspector', 'Scheduler'].map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
onClick={() => {
|
||||
if (item === 'Sessions') setSessionSidebarOpen(!sessionSidebarOpen)
|
||||
else if (item === 'Files') setFileSidebarOpen(!fileSidebarOpen)
|
||||
else setActivePanel(activePanel === item.toLowerCase() ? null : item.toLowerCase())
|
||||
}}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors ${
|
||||
(item === 'Sessions' && sessionSidebarOpen) ||
|
||||
(item === 'Files' && fileSidebarOpen) ||
|
||||
activePanel === item.toLowerCase()
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Layout */}
|
||||
<div className="flex-1 flex min-h-0">
|
||||
{/* Session Sidebar */}
|
||||
{sessionSidebarOpen && (
|
||||
<div className="w-60 border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 space-y-1 text-sm overflow-auto">
|
||||
{['Active Sessions', 'Completed', 'Archived'].map((group) => (
|
||||
<div key={group}>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded hover:bg-accent cursor-pointer">
|
||||
<span className="text-xs">▼</span>
|
||||
<span>{group}</span>
|
||||
</div>
|
||||
<div className="ml-4 space-y-0.5">
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 1
|
||||
</div>
|
||||
<div className="px-2 py-1 text-xs text-muted-foreground hover:bg-accent rounded cursor-pointer">
|
||||
Session 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terminal Grid */}
|
||||
<div className="flex-1 bg-muted/20 p-2">
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-2 h-full">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="bg-background border rounded p-3 font-mono text-xs">
|
||||
<div className="text-green-500 mb-2">$ Terminal {i}</div>
|
||||
<div className="text-muted-foreground">
|
||||
<div>Working directory: /project</div>
|
||||
<div>Type a command to begin...</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Sidebar */}
|
||||
{fileSidebarOpen && (
|
||||
<div className="w-64 border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm overflow-auto">
|
||||
<div className="space-y-1">
|
||||
{['src', 'docs', 'tests', 'package.json', 'README.md'].map((item) => (
|
||||
<div key={item} className="px-2 py-1 rounded hover:bg-accent cursor-pointer flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">📁</span>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Floating Panel */}
|
||||
{activePanel && (
|
||||
<div className="absolute top-12 right-4 w-80 bg-background border rounded-lg shadow-lg">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium capitalize">{activePanel} Panel</span>
|
||||
<button onClick={() => setActivePanel(null)} className="text-xs hover:bg-accent px-2 py-1 rounded">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
{activePanel} content placeholder
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Core Features
|
||||
|
||||
| Feature | Description |
|
||||
@@ -64,38 +190,396 @@
|
||||
|
||||
---
|
||||
|
||||
## Usage Guide
|
||||
## Component Hierarchy
|
||||
|
||||
### Basic Workflow
|
||||
```
|
||||
TerminalDashboardPage
|
||||
├── AssociationHighlightProvider (context)
|
||||
├── DashboardToolbar
|
||||
│ ├── Layout Preset Buttons (Single | Split-H | Split-V | Grid-2x2)
|
||||
│ ├── Panel Toggles (Sessions | Files | Issues | Queue | Inspector | Execution | Scheduler)
|
||||
│ ├── Fullscreen Toggle
|
||||
│ └── Launch CLI Button
|
||||
├── Allotment (Three-Column Layout)
|
||||
│ ├── SessionGroupTree
|
||||
│ │ └── Session Group Items (collapsible)
|
||||
│ ├── TerminalGrid
|
||||
│ │ ├── GridGroupRenderer (recursive)
|
||||
│ │ └── TerminalPane
|
||||
│ └── FileSidebarPanel
|
||||
│ └── File Tree View
|
||||
└── FloatingPanel (multiple, mutually exclusive)
|
||||
├── Issues+Queue (split panel)
|
||||
│ ├── IssuePanel
|
||||
│ └── QueueListColumn
|
||||
├── QueuePanel (feature flag)
|
||||
├── InspectorContent (feature flag)
|
||||
├── ExecutionMonitorPanel (feature flag)
|
||||
└── SchedulerPanel
|
||||
```
|
||||
|
||||
1. **Launch CLI Session**: Click "Launch CLI" button, configure options (tool, model, shell, working directory)
|
||||
2. **Arrange Terminals**: Use layout presets or manually split panes
|
||||
3. **Navigate Sessions**: Browse session groups in the left tree
|
||||
4. **Toggle Panels**: Click toolbar buttons to show/hide floating panels
|
||||
5. **Inspect Issues**: Open Issues panel to view associated issues
|
||||
6. **Monitor Execution**: Use Execution Monitor panel for real-time tracking
|
||||
---
|
||||
|
||||
### Key Interactions
|
||||
## Props API
|
||||
|
||||
| Interaction | How to Use |
|
||||
|-------------|------------|
|
||||
| **Launch CLI** | Click "Launch CLI" button, configure session in modal |
|
||||
| **Toggle sidebar** | Click Sessions/Files button in toolbar |
|
||||
| **Open floating panel** | Click Issues/Queue/Inspector/Execution/Scheduler button |
|
||||
| **Close floating panel** | Click X on panel or toggle button again |
|
||||
| **Resize panes** | Drag the divider between panes |
|
||||
| **Change layout** | Click layout preset buttons (single/split/grid) |
|
||||
| **Fullscreen mode** | Click fullscreen button to hide app chrome |
|
||||
### TerminalDashboardPage
|
||||
|
||||
### Panel Types
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| - | - | - | This page component accepts no props (state managed via hooks and Zustand stores) |
|
||||
|
||||
| Panel | Content | Position |
|
||||
|-------|---------|----------|
|
||||
| **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) |
|
||||
| **Queue** | Full queue management panel | Right (overlay, feature flag) |
|
||||
| **Inspector** | Association chain inspector | Right (overlay, feature flag) |
|
||||
| **Execution Monitor** | Real-time execution tracking | Right (overlay, feature flag) |
|
||||
| **Scheduler** | Queue scheduler controls | Right (overlay) |
|
||||
### DashboardToolbar
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `activePanel` | `PanelId \| null` | `null` | Currently active floating panel |
|
||||
| `onTogglePanel` | `(panelId: PanelId) => void` | - | Callback to toggle panel visibility |
|
||||
| `isFileSidebarOpen` | `boolean` | `true` | File sidebar visibility state |
|
||||
| `onToggleFileSidebar` | `() => void` | - | Toggle file sidebar callback |
|
||||
| `isSessionSidebarOpen` | `boolean` | `true` | Session sidebar visibility state |
|
||||
| `onToggleSessionSidebar` | `() => void` | - | Toggle session sidebar callback |
|
||||
| `isFullscreen` | `boolean` | `false` | Fullscreen mode state |
|
||||
| `onToggleFullscreen` | `() => void` | - | Toggle fullscreen callback |
|
||||
|
||||
### FloatingPanel
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `isOpen` | `boolean` | `false` | Panel open state |
|
||||
| `onClose` | `() => void` | - | Close callback |
|
||||
| `title` | `string` | - | Panel title |
|
||||
| `side` | `'left' \| 'right'` | `'left'` | Panel side |
|
||||
| `width` | `number` | `400` | Panel width in pixels |
|
||||
| `children` | `ReactNode` | - | Panel content |
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Local State
|
||||
|
||||
| State | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `activePanel` | `PanelId \| null` | Currently active floating panel (mutually exclusive) |
|
||||
| `isFileSidebarOpen` | `boolean` | File sidebar visibility |
|
||||
| `isSessionSidebarOpen` | `boolean` | Session sidebar visibility |
|
||||
|
||||
### Zustand Stores
|
||||
|
||||
| Store | Selector | Purpose |
|
||||
|-------|----------|---------|
|
||||
| `workflowStore` | `selectProjectPath` | Current project path for file sidebar |
|
||||
| `appStore` | `selectIsImmersiveMode` | Fullscreen mode state |
|
||||
| `configStore` | `featureFlags` | Feature flag configuration |
|
||||
| `terminalGridStore` | Grid layout and focused pane state |
|
||||
| `executionMonitorStore` | Active execution count |
|
||||
| `queueSchedulerStore` | Scheduler status and settings |
|
||||
|
||||
### Panel ID Type
|
||||
|
||||
```typescript
|
||||
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Terminal Dashboard
|
||||
|
||||
```tsx
|
||||
import { TerminalDashboardPage } from '@/pages/TerminalDashboardPage'
|
||||
|
||||
// The terminal dashboard is automatically rendered at /terminal-dashboard
|
||||
// No props needed - layout state managed internally
|
||||
```
|
||||
|
||||
### Using FloatingPanel Component
|
||||
|
||||
```tsx
|
||||
import { FloatingPanel } from '@/components/terminal-dashboard/FloatingPanel'
|
||||
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel'
|
||||
|
||||
function CustomLayout() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<FloatingPanel
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title="Issues"
|
||||
side="left"
|
||||
width={700}
|
||||
>
|
||||
<IssuePanel />
|
||||
</FloatingPanel>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Panel Toggle Pattern
|
||||
|
||||
```tsx
|
||||
import { useState, useCallback } from 'react'
|
||||
|
||||
function usePanelToggle() {
|
||||
const [activePanel, setActivePanel] = useState<string | null>(null)
|
||||
|
||||
const togglePanel = useCallback((panelId: string) => {
|
||||
setActivePanel((prev) => (prev === panelId ? null : panelId))
|
||||
}, [])
|
||||
|
||||
const closePanel = useCallback(() => {
|
||||
setActivePanel(null)
|
||||
}, [])
|
||||
|
||||
return { activePanel, togglePanel, closePanel }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interactive Demos
|
||||
|
||||
### Layout Presets Demo
|
||||
|
||||
:::demo TerminalLayoutPresets
|
||||
# terminal-layout-presets.tsx
|
||||
/**
|
||||
* Terminal Layout Presets Demo
|
||||
* Interactive layout preset buttons
|
||||
*/
|
||||
export function TerminalLayoutPresets() {
|
||||
const [layout, setLayout] = React.useState('grid-2x2')
|
||||
|
||||
const layouts = {
|
||||
single: 'grid-cols-1 grid-rows-1',
|
||||
'split-h': 'grid-cols-2 grid-rows-1',
|
||||
'split-v': 'grid-cols-1 grid-rows-2',
|
||||
'grid-2x2': 'grid-cols-2 grid-rows-2',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-background space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">Terminal Layout Presets</h3>
|
||||
<div className="flex gap-2">
|
||||
{Object.keys(layouts).map((preset) => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => setLayout(preset)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
layout === preset
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{preset.replace('-', ' ').toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`grid gap-2 h-64 ${layouts[layout]}`}>
|
||||
{Array.from({ length: layout === 'single' ? 1 : layout.includes('2x') ? 4 : 2 }).map((_, i) => (
|
||||
<div key={i} className="bg-muted/20 border rounded p-4 font-mono text-xs">
|
||||
<div className="text-green-500">$ Terminal {i + 1}</div>
|
||||
<div className="text-muted-foreground mt-1">Ready for input...</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Floating Panels Demo
|
||||
|
||||
:::demo FloatingPanelsDemo
|
||||
# floating-panels-demo.tsx
|
||||
/**
|
||||
* Floating Panels Demo
|
||||
* Mutually exclusive overlay panels
|
||||
*/
|
||||
export function FloatingPanelsDemo() {
|
||||
const [activePanel, setActivePanel] = React.useState(null)
|
||||
|
||||
const panels = [
|
||||
{ id: 'issues', title: 'Issues + Queue', side: 'left', width: 700 },
|
||||
{ id: 'queue', title: 'Queue', side: 'right', width: 400 },
|
||||
{ id: 'inspector', title: 'Inspector', side: 'right', width: 360 },
|
||||
{ id: 'execution', title: 'Execution Monitor', side: 'right', width: 380 },
|
||||
{ id: 'scheduler', title: 'Scheduler', side: 'right', width: 340 },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="relative h-[500px] p-6 bg-background border rounded-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold">Floating Panels</h3>
|
||||
<div className="flex gap-2">
|
||||
{panels.map((panel) => (
|
||||
<button
|
||||
key={panel.id}
|
||||
onClick={() => setActivePanel(activePanel === panel.id ? null : panel.id)}
|
||||
className={`px-3 py-1.5 text-xs rounded transition-colors ${
|
||||
activePanel === panel.id
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'border hover:bg-accent'
|
||||
}`}
|
||||
>
|
||||
{panel.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[380px] bg-muted/20 border rounded flex items-center justify-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{activePanel ? `"${panels.find((p) => p.id === activePanel)?.title}" panel is open` : 'Click a button to open a floating panel'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Floating Panel Overlay */}
|
||||
{activePanel && (
|
||||
<div
|
||||
className={`absolute top-16 border rounded-lg shadow-lg bg-background ${
|
||||
panels.find((p) => p.id === activePanel)?.side === 'left' ? 'left-6' : 'right-6'
|
||||
}`}
|
||||
style={{ width: panels.find((p) => p.id === activePanel)?.width }}
|
||||
>
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b">
|
||||
<span className="text-sm font-medium">{panels.find((p) => p.id === activePanel)?.title}</span>
|
||||
<button
|
||||
onClick={() => setActivePanel(null)}
|
||||
className="text-xs hover:bg-accent px-2 py-1 rounded"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4 text-sm text-muted-foreground">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500"/>
|
||||
<span>Item 1</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500"/>
|
||||
<span>Item 2</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-amber-500"/>
|
||||
<span>Item 3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
### Resizable Panes Demo
|
||||
|
||||
:::demo ResizablePanesDemo
|
||||
# resizable-panes-demo.tsx
|
||||
/**
|
||||
* Resizable Panes Demo
|
||||
* Simulates the Allotment resizable split behavior
|
||||
*/
|
||||
export function ResizablePanesDemo() {
|
||||
const [leftWidth, setLeftWidth] = React.useState(240)
|
||||
const [rightWidth, setRightWidth] = React.useState(280)
|
||||
const [isDragging, setIsDragging] = React.useState(null)
|
||||
|
||||
const handleDragStart = (side) => (e) => {
|
||||
setIsDragging(side)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleMouseMove = (e) => {
|
||||
if (isDragging === 'left') {
|
||||
setLeftWidth(Math.max(180, Math.min(320, e.clientX)))
|
||||
} else if (isDragging === 'right') {
|
||||
setRightWidth(Math.max(200, Math.min(400, window.innerWidth - e.clientX)))
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseUp = () => setIsDragging(null)
|
||||
|
||||
if (isDragging) {
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', handleMouseUp)
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
return (
|
||||
<div className="h-[400px] flex bg-background border rounded-lg overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
<div style={{ width: leftWidth }} className="border-r flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Session Groups
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['Active Sessions', 'Completed'].map((g) => (
|
||||
<div key={g} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{g}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Left Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('left')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'left' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 bg-muted/20 flex items-center justify-center">
|
||||
<span className="text-sm text-muted-foreground">Terminal Grid Area</span>
|
||||
</div>
|
||||
|
||||
{/* Right Drag Handle */}
|
||||
<div
|
||||
onMouseDown={handleDragStart('right')}
|
||||
className={`w-1 bg-border hover:bg-primary cursor-col-resize transition-colors ${
|
||||
isDragging === 'right' ? 'bg-primary' : ''
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div style={{ width: rightWidth }} className="border-l flex flex-col">
|
||||
<div className="px-3 py-2 text-xs font-semibold border-b bg-muted/30">
|
||||
Project Files
|
||||
</div>
|
||||
<div className="flex-1 p-2 text-sm space-y-1">
|
||||
{['src/', 'docs/', 'tests/'].map((f) => (
|
||||
<div key={f} className="px-2 py-1 hover:bg-accent rounded cursor-pointer">{f}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Feature Flags
|
||||
|
||||
| Flag | Controls |
|
||||
|------|----------|
|
||||
| `dashboardQueuePanelEnabled` | Queue panel visibility |
|
||||
| `dashboardInspectorEnabled` | Inspector panel visibility |
|
||||
| `dashboardExecutionMonitorEnabled` | Execution Monitor panel visibility |
|
||||
|
||||
### Layout Presets
|
||||
|
||||
@@ -106,59 +590,36 @@
|
||||
| **Split-V** | Two panes stacked vertically |
|
||||
| **Grid-2x2** | Four panes in 2x2 grid |
|
||||
|
||||
---
|
||||
### Panel Types
|
||||
|
||||
## Components Reference
|
||||
|
||||
### Main Components
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `TerminalDashboardPage` | `@/pages/TerminalDashboardPage.tsx` | Main terminal dashboard page |
|
||||
| `DashboardToolbar` | `@/components/terminal-dashboard/DashboardToolbar.tsx` | Top toolbar with panel toggles |
|
||||
| `SessionGroupTree` | `@/components/terminal-dashboard/SessionGroupTree.tsx` | Session tree navigation |
|
||||
| `TerminalGrid` | `@/components/terminal-dashboard/TerminalGrid.tsx` | Tmux-style terminal panes |
|
||||
| `FileSidebarPanel` | `@/components/terminal-dashboard/FileSidebarPanel.tsx` | File explorer sidebar |
|
||||
| `FloatingPanel` | `@/components/terminal-dashboard/FloatingPanel.tsx` | Overlay panel wrapper |
|
||||
| `IssuePanel` | `@/components/terminal-dashboard/IssuePanel.tsx` | Issues list panel |
|
||||
| `QueuePanel` | `@/components/terminal-dashboard/QueuePanel.tsx` | Queue management panel |
|
||||
| `QueueListColumn` | `@/components/terminal-dashboard/QueueListColumn.tsx` | Queue list (compact) |
|
||||
| `SchedulerPanel` | `@/components/terminal-dashboard/SchedulerPanel.tsx` | Queue scheduler controls |
|
||||
| `InspectorContent` | `@/components/terminal-dashboard/BottomInspector.tsx` | Association inspector |
|
||||
| `ExecutionMonitorPanel` | `@/components/terminal-dashboard/ExecutionMonitorPanel.tsx` | Execution tracking |
|
||||
|
||||
### State Management
|
||||
|
||||
- **Local state** (TerminalDashboardPage):
|
||||
- `activePanel`: PanelId | null
|
||||
- `isFileSidebarOpen`: boolean
|
||||
- `isSessionSidebarOpen`: boolean
|
||||
|
||||
- **Zustand stores**:
|
||||
- `useWorkflowStore` - Project path
|
||||
- `useAppStore` - Immersive mode state
|
||||
- `useConfigStore` - Feature flags
|
||||
- `useTerminalGridStore` - Terminal grid layout and focused pane
|
||||
- `useExecutionMonitorStore` - Active execution count
|
||||
- `useQueueSchedulerStore` - Scheduler status
|
||||
| Panel | Content | Position | Feature Flag |
|
||||
|-------|---------|----------|--------------|
|
||||
| **Issues+Queue** | Combined Issues panel + Queue list column | Left (overlay) | - |
|
||||
| **Queue** | Full queue management panel | Right (overlay) | `dashboardQueuePanelEnabled` |
|
||||
| **Inspector** | Association chain inspector | Right (overlay) | `dashboardInspectorEnabled` |
|
||||
| **Execution Monitor** | Real-time execution tracking | Right (overlay) | `dashboardExecutionMonitorEnabled` |
|
||||
| **Scheduler** | Queue scheduler controls | Right (overlay) | - |
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
## Accessibility
|
||||
|
||||
### Panel IDs
|
||||
- **Keyboard Navigation**:
|
||||
- <kbd>Tab</kbd> - Navigate through toolbar buttons
|
||||
- <kbd>Enter</kbd>/<kbd>Space</kbd> - Activate toolbar buttons
|
||||
- <kbd>Escape</kbd> - Close floating panels
|
||||
- <kbd>F11</kbd> - Toggle fullscreen mode
|
||||
|
||||
```typescript
|
||||
type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
```
|
||||
- **ARIA Attributes**:
|
||||
- `aria-label` on toolbar buttons
|
||||
- `aria-expanded` on sidebar toggles
|
||||
- `aria-hidden` on inactive floating panels
|
||||
- `role="dialog"` on floating panels
|
||||
|
||||
### Feature Flags
|
||||
|
||||
| Flag | Controls |
|
||||
|------|----------|
|
||||
| `dashboardQueuePanelEnabled` | Queue panel visibility |
|
||||
| `dashboardInspectorEnabled` | Inspector panel visibility |
|
||||
| `dashboardExecutionMonitorEnabled` | Execution Monitor panel visibility |
|
||||
- **Screen Reader Support**:
|
||||
- Panel state announced when toggled
|
||||
- Layout changes announced
|
||||
- Focus management when panels open/close
|
||||
|
||||
---
|
||||
|
||||
@@ -168,3 +629,4 @@ type PanelId = 'issues' | 'queue' | 'inspector' | 'execution' | 'scheduler';
|
||||
- [Sessions](/features/sessions) - Session management
|
||||
- [Issue Hub](/features/issue-hub) - Issues, queue, discovery
|
||||
- [Explorer](/features/explorer) - File explorer
|
||||
- [Queue](/features/queue) - Queue management standalone page
|
||||
|
||||
Reference in New Issue
Block a user