feat: Add Phase 6 for Post-Implementation Review and enhance workflow execution

- Introduced Phase 6: Post-Implementation Review with detailed steps for specialized reviews (quality, security, architecture, action items).
- Updated SKILL.md to reflect new phase and its execution lifecycle.
- Enhanced Flowchart component to conditionally display step statuses based on task tracking.
- Modified TaskDrawer to pass status tracking prop to Flowchart.
- Improved AgentList and other terminal dashboard components for better UI consistency and responsiveness.
- Removed GlobalKpiBar component as part of UI cleanup.
- Added issue detail preview in TerminalWorkbench for better user experience when no terminal is active.
- Updated localization files for new strings related to the terminal dashboard and workbench.
- Enhanced TaskListTab to conditionally render task stats and status dropdown based on task status tracking.
This commit is contained in:
catlog22
2026-02-14 21:49:31 +08:00
parent d535ab4749
commit 37d19ada75
15 changed files with 448 additions and 510 deletions

View File

@@ -1,322 +0,0 @@
---
name: review
description: Post-implementation review with specialized types (security/architecture/action-items/quality) using analysis agents and Gemini
argument-hint: "[--type=security|architecture|action-items|quality] [--archived] [optional: session-id]"
---
## Command Overview: /workflow:review
**Optional specialized review** for completed implementations. In the standard workflow, **passing tests = approved code**. Use this command only when specialized review is required (security, architecture, compliance, docs).
## Philosophy: "Tests Are the Review"
- **Default**: All tests pass -> Code approved
- **Optional**: Specialized reviews for:
- Security audits (vulnerabilities, auth/authz)
- Architecture compliance (patterns, technical debt)
- Action items verification (requirements met, acceptance criteria)
## Review Types
| Type | Focus | Use Case |
|------|-------|----------|
| `quality` | Code quality, best practices, maintainability | Default general review |
| `security` | Security vulnerabilities, data handling, access control | Security audits |
| `architecture` | Architectural patterns, technical debt, design decisions | Architecture compliance |
| `action-items` | Requirements met, acceptance criteria verified, action items completed | Pre-deployment verification |
**Notes**:
- For documentation generation, use `/workflow:tools:docs`
- For CLAUDE.md updates, use `/update-memory-related`
## Execution Process
```
Input Parsing:
├─ Parse --type flag (default: quality)
├─ Parse --archived flag (search in archives)
└─ Parse session-id argument (optional)
Step 1: Session Resolution
└─ Decision:
├─ session-id provided + --archived → Search .workflow/archives/
├─ session-id provided → Search .workflow/active/ first, then archives
└─ Not provided → Auto-detect from .workflow/active/
Step 2: Validation
├─ Check session directory exists (active or archived)
└─ Check for completed implementation (.summaries/IMPL-*.md exists)
Step 3: Type Check
└─ Decision:
├─ type=docs → Redirect to /workflow:tools:docs
└─ Other types → Continue to analysis
Step 4: Model Analysis Phase
├─ Load context (summaries, test results, changed files)
└─ Perform specialized review by type:
├─ security → Security patterns + Gemini analysis
├─ architecture → Qwen architecture analysis
├─ quality → Gemini code quality analysis
└─ action-items → Requirements verification
Step 5: Generate Report
└─ Output: REVIEW-{type}.md
```
## Execution Template
```bash
#!/bin/bash
# Optional specialized review for completed implementation
# Step 1: Session ID resolution and location detection
if [ -n "$SESSION_ARG" ]; then
sessionId="$SESSION_ARG"
else
sessionId=$(find .workflow/active/ -name "WFS-*" -type d | head -1 | xargs basename)
fi
# Step 2: Resolve session path (active or archived)
# Priority: --archived flag → active → archives
if [ -n "$ARCHIVED_FLAG" ]; then
sessionPath=".workflow/archives/${sessionId}"
elif [ -d ".workflow/active/${sessionId}" ]; then
sessionPath=".workflow/active/${sessionId}"
elif [ -d ".workflow/archives/${sessionId}" ]; then
sessionPath=".workflow/archives/${sessionId}"
echo "Note: Session found in archives, running review on archived session"
else
echo "Session ${sessionId} not found in active or archives"
exit 1
fi
# Check for completed tasks
if [ ! -d "${sessionPath}/.summaries" ] || [ -z "$(find ${sessionPath}/.summaries/ -name "IMPL-*.md" -type f 2>/dev/null)" ]; then
echo "No completed implementation found. Complete implementation first"
exit 1
fi
# Step 3: Determine review type (default: quality)
review_type="${TYPE_ARG:-quality}"
# Redirect docs review to specialized command
if [ "$review_type" = "docs" ]; then
echo "For documentation generation, please use:"
echo " /workflow:tools:docs"
echo ""
echo "The docs command provides:"
echo " - Hierarchical architecture documentation"
echo " - API documentation generation"
echo " - Documentation structure analysis"
exit 0
fi
# Step 4: Analysis handover → Model takes control
# BASH_EXECUTION_STOPS → MODEL_ANALYSIS_BEGINS
```
### Model Analysis Phase
After bash validation, the model takes control to:
1. **Load Context**: Read completed task summaries and changed files
```bash
# Load implementation summaries (iterate through .summaries/ directory)
for summary in ${sessionPath}/.summaries/*.md; do
cat "$summary"
done
# Load test results (if available)
for test_summary in ${sessionPath}/.summaries/TEST-FIX-*.md 2>/dev/null; do
cat "$test_summary"
done
# Get changed files
git log --since="$(cat ${sessionPath}/workflow-session.json | jq -r .created_at)" --name-only --pretty=format: | sort -u
```
2. **Perform Specialized Review**: Based on `review_type`
**Security Review** (`--type=security`):
- Use ripgrep for security patterns:
```bash
rg "password|token|secret|auth" -g "*.{ts,js,py}"
rg "eval|exec|innerHTML|dangerouslySetInnerHTML" -g "*.{ts,js,tsx}"
```
- Use Gemini for security analysis:
```bash
ccw cli -p "
PURPOSE: Security audit of completed implementation
TASK: Review code for security vulnerabilities, insecure patterns, auth/authz issues
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Security findings report with severity levels
RULES: Focus on OWASP Top 10, authentication, authorization, data validation, injection risks
" --tool gemini --mode write --cd ${sessionPath}
```
**Architecture Review** (`--type=architecture`):
- Use Qwen for architecture analysis:
```bash
ccw cli -p "
PURPOSE: Architecture compliance review
TASK: Evaluate adherence to architectural patterns, identify technical debt, review design decisions
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Architecture assessment with recommendations
RULES: Check for patterns, separation of concerns, modularity, scalability
" --tool qwen --mode write --cd ${sessionPath}
```
**Quality Review** (`--type=quality`):
- Use Gemini for code quality:
```bash
ccw cli -p "
PURPOSE: Code quality and best practices review
TASK: Assess code readability, maintainability, adherence to best practices
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Quality assessment with improvement suggestions
RULES: Check for code smells, duplication, complexity, naming conventions
" --tool gemini --mode write --cd ${sessionPath}
```
**Action Items Review** (`--type=action-items`):
- Verify all requirements and acceptance criteria met:
```bash
# Load task requirements and acceptance criteria
for task_file in ${sessionPath}/.task/*.json; do
cat "$task_file" | jq -r '
"Task: " + .id + "\n" +
"Requirements: " + .description + "\n" +
"Acceptance: " + (.convergence.criteria | join(", "))
'
done
# Check implementation summaries against requirements
ccw cli -p "
PURPOSE: Verify all requirements and acceptance criteria are met
TASK: Cross-check implementation summaries against original requirements
CONTEXT: @.task/IMPL-*.json,.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED:
- Requirements coverage matrix
- Acceptance criteria verification
- Missing/incomplete action items
- Pre-deployment readiness assessment
RULES:
- Check each requirement has corresponding implementation
- Verify all acceptance criteria are met
- Flag any incomplete or missing action items
- Assess deployment readiness
" --tool gemini --mode write --cd ${sessionPath}
```
3. **Generate Review Report**: Create structured report
```markdown
# Review Report: ${review_type}
**Session**: ${sessionId}
**Date**: $(date)
**Type**: ${review_type}
## Summary
- Tasks Reviewed: [count IMPL tasks]
- Files Changed: [count files]
- Severity: [High/Medium/Low]
## Findings
### Critical Issues
- [Issue 1 with file:line reference]
- [Issue 2 with file:line reference]
### Recommendations
- [Recommendation 1]
- [Recommendation 2]
### Positive Observations
- [Good pattern observed]
## Action Items
- [ ] [Action 1]
- [ ] [Action 2]
```
4. **Output Files**:
```bash
# Save review report
Write(${sessionPath}/REVIEW-${review_type}.md)
# Update session metadata
# (optional) Update workflow-session.json with review status
```
5. **Optional: Update Memory** (if docs review or significant findings):
```bash
# If architecture or quality issues found, suggest memory update
if [ "$review_type" = "architecture" ] || [ "$review_type" = "quality" ]; then
echo "Consider updating project documentation:"
echo " /update-memory-related"
fi
```
## Usage Examples
```bash
# General quality review after implementation
/workflow:review
# Security audit before deployment
/workflow:review --type=security
# Architecture review for specific session
/workflow:review --type=architecture WFS-payment-integration
# Review an archived session (auto-detects if not in active)
/workflow:review --type=security WFS-old-feature
# Explicitly review archived session
/workflow:review --archived --type=quality WFS-completed-feature
# Documentation review
/workflow:review --type=docs
```
## Features
- **Simple Validation**: Check session exists and has completed tasks
- **No Complex Orchestration**: Direct analysis, no multi-phase pipeline
- **Specialized Reviews**: Different prompts and tools for different review types
- **Archived Session Support**: Review archived sessions with `--archived` flag or auto-detection
- **MCP Integration**: Fast code search for security and architecture patterns
- **CLI Tool Integration**: Gemini for analysis, Qwen for architecture
- **Structured Output**: Markdown reports with severity levels and action items
- **Optional Memory Update**: Suggests documentation updates for significant findings
## Integration with Workflow
```
Standard Workflow:
plan -> execute -> test-gen -> execute (complete)
Optional Review (when needed):
plan -> execute -> test-gen -> execute -> review (security/architecture/docs)
```
**When to Use**:
- Before production deployment (security review + action-items review)
- After major feature (architecture review)
- Before code freeze (quality review)
- Pre-deployment verification (action-items review)
**When NOT to Use**:
- Regular development (tests are sufficient)
- Simple bug fixes (test-fix-agent handles it)
- Minor changes (update-memory-related is enough)
## Post-Review Action
After review completion, prompt user:
```
Review complete. Would you like to complete and archive this session?
→ Run /workflow:session:complete to archive with lessons learned
```

View File

@@ -116,14 +116,21 @@ Phase 5: Completion
├─ Update task statuses in JSON files
├─ Generate summaries
└─ AskUserQuestion: Choose next step
├─ "Enter Review" → /workflow:review
├─ "Enter Review" → Phase 6
└─ "Complete Session" → /workflow:session:complete
Phase 6: Post-Implementation Review (Optional)
└─ Ref: phases/06-review.md
├─ Select review type (quality/security/architecture/action-items)
├─ CLI-assisted analysis (Gemini/Qwen)
├─ Generate REVIEW-{type}.md report
└─ Post-review: another review or complete session
Resume Mode (--resume-session):
├─ Skip Phase 1 & Phase 2
└─ Entry Point: Phase 3 (TodoWrite Generation)
├─ Update session status to "active" (if not already)
└─ Continue: Phase 4 → Phase 5
└─ Continue: Phase 4 → Phase 5 → [Phase 6]
```
## Execution Lifecycle
@@ -346,7 +353,7 @@ if (autoYes) {
```
**Based on user selection**:
- **"Enter Review"**: Execute `/workflow:review`
- **"Enter Review"**: Execute Phase 6 → `Ref: phases/06-review.md`
- **"Complete Session"**: Execute `/workflow:session:complete`
### Post-Completion Expansion
@@ -532,6 +539,12 @@ meta.agent missing → Infer from meta.type:
- "docs" → @doc-generator
```
## Phase Reference Documents
| Phase | Document | Purpose |
|-------|----------|---------|
| 6 | [phases/06-review.md](phases/06-review.md) | Post-implementation specialized review (security/architecture/quality/action-items) |
## Workflow File Structure Reference
```
.workflow/active/WFS-[topic-slug]/

View File

@@ -0,0 +1,212 @@
# Phase 6: Post-Implementation Review
Optional specialized review for completed implementations. In the standard workflow, **passing tests = approved code**. This phase executes only when user selects "Enter Review" in Phase 5 completion.
## Objective
- Perform specialized review (security/architecture/quality/action-items) on completed implementation
- Generate structured review report with severity levels and action items
- Provide CLI-assisted analysis using Gemini/Qwen for deep review
## Philosophy: "Tests Are the Review"
- **Default**: All tests pass → Code approved
- **Optional**: This phase for specialized reviews:
- Security audits (vulnerabilities, auth/authz)
- Architecture compliance (patterns, technical debt)
- Action items verification (requirements met, acceptance criteria)
- Code quality assessment (best practices, maintainability)
## Review Types
| Type | Focus | Use Case |
|------|-------|----------|
| `quality` | Code quality, best practices, maintainability | Default general review |
| `security` | Security vulnerabilities, data handling, access control | Security audits |
| `architecture` | Architectural patterns, technical debt, design decisions | Architecture compliance |
| `action-items` | Requirements met, acceptance criteria verified | Pre-deployment verification |
**Notes**:
- For documentation generation, use `/workflow:tools:docs`
- For CLAUDE.md updates, use `/update-memory-related`
## Execution
### Step 6.1: Review Type Selection
Prompt user to select review type:
```javascript
AskUserQuestion({
questions: [{
question: "Select review type:",
header: "Review Type",
multiSelect: false,
options: [
{ label: "Quality (Recommended)", description: "Code quality, best practices, maintainability" },
{ label: "Security", description: "Security vulnerabilities, data handling, access control" },
{ label: "Architecture", description: "Architectural patterns, technical debt, design decisions" },
{ label: "Action Items", description: "Requirements met, acceptance criteria verified" }
]
}]
})
```
**Auto Mode** (`--yes`): Skip selection, default to `quality`.
### Step 6.2: Validation
```bash
# Verify completed implementation exists
sessionPath=".workflow/active/${sessionId}"
if [ ! -d "${sessionPath}/.summaries" ] || [ -z "$(find ${sessionPath}/.summaries/ -name "IMPL-*.md" -type f 2>/dev/null)" ]; then
echo "No completed implementation found. Complete implementation first."
exit 1
fi
```
### Step 6.3: Context Loading
```bash
# Load implementation summaries
for summary in ${sessionPath}/.summaries/*.md; do
cat "$summary"
done
# Load test results (if available)
for test_summary in ${sessionPath}/.summaries/TEST-FIX-*.md 2>/dev/null; do
cat "$test_summary"
done
# Get changed files
git log --since="$(cat ${sessionPath}/workflow-session.json | jq -r .created_at)" --name-only --pretty=format: | sort -u
```
### Step 6.4: Specialized Review Analysis
Based on `review_type`, execute the corresponding analysis:
**Security Review** (`security`):
```bash
# Pattern scan
rg "password|token|secret|auth" -g "*.{ts,js,py}"
rg "eval|exec|innerHTML|dangerouslySetInnerHTML" -g "*.{ts,js,tsx}"
# Gemini security analysis
ccw cli -p "
PURPOSE: Security audit of completed implementation
TASK: Review code for security vulnerabilities, insecure patterns, auth/authz issues
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Security findings report with severity levels
RULES: Focus on OWASP Top 10, authentication, authorization, data validation, injection risks
" --tool gemini --mode write --cd ${sessionPath}
```
**Architecture Review** (`architecture`):
```bash
ccw cli -p "
PURPOSE: Architecture compliance review
TASK: Evaluate adherence to architectural patterns, identify technical debt, review design decisions
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Architecture assessment with recommendations
RULES: Check for patterns, separation of concerns, modularity, scalability
" --tool qwen --mode write --cd ${sessionPath}
```
**Quality Review** (`quality`):
```bash
ccw cli -p "
PURPOSE: Code quality and best practices review
TASK: Assess code readability, maintainability, adherence to best practices
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED: Quality assessment with improvement suggestions
RULES: Check for code smells, duplication, complexity, naming conventions
" --tool gemini --mode write --cd ${sessionPath}
```
**Action Items Review** (`action-items`):
```bash
# Load task requirements and acceptance criteria
for task_file in ${sessionPath}/.task/*.json; do
cat "$task_file" | jq -r '
"Task: " + .id + "\n" +
"Requirements: " + .description + "\n" +
"Acceptance: " + (.convergence.criteria | join(", "))
'
done
# Cross-check implementation against requirements
ccw cli -p "
PURPOSE: Verify all requirements and acceptance criteria are met
TASK: Cross-check implementation summaries against original requirements
CONTEXT: @.task/IMPL-*.json,.summaries/IMPL-*.md,../.. @../../project-tech.json @../../project-guidelines.json
EXPECTED:
- Requirements coverage matrix
- Acceptance criteria verification
- Missing/incomplete action items
- Pre-deployment readiness assessment
RULES:
- Check each requirement has corresponding implementation
- Verify all acceptance criteria are met
- Flag any incomplete or missing action items
- Assess deployment readiness
" --tool gemini --mode write --cd ${sessionPath}
```
### Step 6.5: Generate Review Report
Write structured report to session directory:
```markdown
# Review Report: ${review_type}
**Session**: ${sessionId}
**Date**: $(date)
**Type**: ${review_type}
## Summary
- Tasks Reviewed: [count IMPL tasks]
- Files Changed: [count files]
- Severity: [High/Medium/Low]
## Findings
### Critical Issues
- [Issue 1 with file:line reference]
### Recommendations
- [Recommendation 1]
### Positive Observations
- [Good pattern observed]
## Action Items
- [ ] [Action 1]
- [ ] [Action 2]
```
**Output**: `${sessionPath}/REVIEW-${review_type}.md`
### Step 6.6: Post-Review Prompt
```
Review complete. Would you like to:
→ Run another review type
→ Complete session: /workflow:session:complete
```
If architecture or quality issues found, suggest:
```
Consider updating project documentation:
→ /update-memory-related
```
## Output
- **File**: `${sessionPath}/REVIEW-${review_type}.md`
- **TodoWrite**: Mark Phase 6 completed
## Next Phase
Return to orchestrator for session completion or additional review cycles.

View File

@@ -54,8 +54,9 @@ const StatusIcon: React.FC<{ status?: string; className?: string }> = ({ status,
const CustomNode: React.FC<{ data: FlowchartNodeData }> = ({ data }) => {
const isPreAnalysis = data.type === 'pre-analysis';
const isSection = data.type === 'section';
const isCompleted = data.status === 'completed';
const isInProgress = data.status === 'in_progress';
const showStatus = data.showStepStatus !== false;
const isCompleted = showStatus && data.status === 'completed';
const isInProgress = showStatus && data.status === 'in_progress';
if (isSection) {
return (
@@ -101,14 +102,14 @@ const CustomNode: React.FC<{ data: FlowchartNodeData }> = ({ data }) => {
<span
className={`flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${stepBgClass}`}
>
{isCompleted ? <CheckCircle className="h-4 w-4" /> : data.step}
{isCompleted && showStatus ? <CheckCircle className="h-4 w-4" /> : data.step}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className={`text-sm font-semibold ${isCompleted ? 'text-green-700 dark:text-green-400' : 'text-foreground'}`}>
{data.label}
</span>
{data.status && data.status !== 'pending' && (
{showStatus && data.status && data.status !== 'pending' && (
<StatusIcon status={data.status} className="h-3.5 w-3.5" />
)}
</div>
@@ -141,12 +142,13 @@ const nodeTypes: NodeTypes = {
export interface FlowchartProps {
flowControl: FlowControl;
className?: string;
showStepStatus?: boolean;
}
/**
* Flowchart component for visualizing implementation approach
*/
export function Flowchart({ flowControl, className = '' }: FlowchartProps) {
export function Flowchart({ flowControl, className = '', showStepStatus = true }: FlowchartProps) {
const preAnalysis = flowControl.pre_analysis || [];
const implSteps = flowControl.implementation_approach || [];
@@ -185,6 +187,7 @@ export function Flowchart({ flowControl, className = '' }: FlowchartProps) {
step: `P${idx + 1}`,
output: step.output_to,
type: 'pre-analysis' as const,
showStepStatus,
},
});
@@ -308,6 +311,7 @@ export function Flowchart({ flowControl, className = '' }: FlowchartProps) {
type: 'implementation' as const,
dependsOn,
status: stepStatus,
showStepStatus,
},
});
@@ -411,10 +415,12 @@ export function Flowchart({ flowControl, className = '' }: FlowchartProps) {
nodeColor={(node) => {
const data = node.data as FlowchartNodeData;
if (data.type === 'section') return '#9ca3af';
// Status-based colors
// Status-based colors (only when status tracking is enabled)
if (data.showStepStatus !== false) {
if (data.status === 'completed') return '#22c55e'; // green-500
if (data.status === 'in_progress') return '#f59e0b'; // amber-500
if (data.status === 'blocked') return '#ef4444'; // red-500
}
if (data.type === 'pre-analysis') return '#f59e0b';
return '#3b82f6';
}}

View File

@@ -382,7 +382,7 @@ export function TaskDrawer({ task, isOpen, onClose }: TaskDrawerProps) {
{/* Flowchart Tab */}
{hasFlowchart && (
<TabsContent value="flowchart" className="mt-4 pb-6">
<Flowchart flowControl={flowControl!} className="min-h-[400px]" />
<Flowchart flowControl={flowControl!} className="min-h-[400px]" showStepStatus={hasStatusTracking} />
</TabsContent>
)}

View File

@@ -95,8 +95,8 @@ export function AgentList() {
return (
<div className="flex flex-col">
{/* Section header */}
<div className="flex items-center gap-2 px-3 py-2 border-t border-border shrink-0">
{/* Section header with visual separation */}
<div className="flex items-center gap-2 px-3 py-2 border-t border-border bg-muted/20 shrink-0">
<Bot className="w-4 h-4 text-muted-foreground" />
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
{formatMessage({ id: 'terminalDashboard.agentList.title' })}
@@ -110,8 +110,8 @@ export function AgentList() {
{/* Plan list or empty state */}
{planEntries.length === 0 ? (
<div className="flex items-center justify-center py-4 px-3">
<p className="text-xs text-muted-foreground">
<div className="flex items-center justify-center py-3 px-3">
<p className="text-[10px] text-muted-foreground">
{formatMessage({ id: 'terminalDashboard.agentList.noAgents' })}
</p>
</div>

View File

@@ -1,138 +0,0 @@
// ========================================
// GlobalKpiBar Component
// ========================================
// Top bar showing 3 KPI metrics spanning the full page width.
// Metrics:
// 1. Active Sessions - count from sessionManagerStore (wraps cliSessionStore)
// 2. Queue Size - pending/ready items count from useIssueQueue React Query hook
// 3. Alert Count - total alerts from all terminalMetas
//
// Per design spec (V-001): consumes sessionManagerStore, NOT cliSessionStore directly.
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import { Activity, ListChecks, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
useSessionManagerStore,
selectGroups,
selectTerminalMetas,
} from '@/stores/sessionManagerStore';
import { useIssueQueue } from '@/hooks/useIssues';
import type { TerminalStatus } from '@/types/terminal-dashboard';
// ========== KPI Item ==========
function KpiItem({
icon: Icon,
label,
value,
variant = 'default',
}: {
icon: React.ComponentType<{ className?: string }>;
label: string;
value: number;
variant?: 'default' | 'primary' | 'warning' | 'destructive';
}) {
const variantStyles = {
default: 'text-muted-foreground',
primary: 'text-primary',
warning: 'text-warning',
destructive: 'text-destructive',
};
return (
<div className="flex items-center gap-2">
<Icon className={cn('w-4 h-4', variantStyles[variant])} />
<span className="text-xs text-muted-foreground">{label}</span>
<span className={cn('text-sm font-semibold tabular-nums', variantStyles[variant])}>
{value}
</span>
</div>
);
}
// ========== Main Component ==========
export function GlobalKpiBar() {
const { formatMessage } = useIntl();
const groups = useSessionManagerStore(selectGroups);
const terminalMetas = useSessionManagerStore(selectTerminalMetas);
const queueQuery = useIssueQueue();
// Derive active session count from sessionManagerStore groups
const sessionCount = useMemo(() => {
const allSessionIds = groups.flatMap((g) => g.sessionIds);
// Count sessions that have 'active' status in terminalMetas
let activeCount = 0;
for (const sid of allSessionIds) {
const meta = terminalMetas[sid];
const status: TerminalStatus = meta?.status ?? 'idle';
if (status === 'active') {
activeCount++;
}
}
// If no sessions are managed in groups, return total unique session IDs
// This ensures the KPI shows meaningful data even before grouping
return activeCount > 0 ? activeCount : allSessionIds.length;
}, [groups, terminalMetas]);
// Derive queue pending count from useIssueQueue data
const queuePendingCount = useMemo(() => {
const queue = queueQuery.data;
if (!queue) return 0;
// Count all items across grouped_items
let count = 0;
if (queue.grouped_items) {
for (const items of Object.values(queue.grouped_items)) {
count += items.length;
}
}
// Also count ungrouped tasks and solutions
if (queue.tasks) count += queue.tasks.length;
if (queue.solutions) count += queue.solutions.length;
return count;
}, [queueQuery.data]);
// Derive total alert count from all terminalMetas
const totalAlerts = useMemo(() => {
let count = 0;
for (const meta of Object.values(terminalMetas)) {
count += meta.alertCount;
}
return count;
}, [terminalMetas]);
return (
<div className="flex items-center gap-6 px-4 py-2 border-b border-border bg-muted/30 shrink-0">
<KpiItem
icon={Activity}
label={formatMessage({ id: 'terminalDashboard.kpi.activeSessions' })}
value={sessionCount}
variant="primary"
/>
<div className="w-px h-4 bg-border" />
<KpiItem
icon={ListChecks}
label={formatMessage({ id: 'terminalDashboard.kpi.queueSize' })}
value={queuePendingCount}
variant={queuePendingCount > 0 ? 'warning' : 'default'}
/>
<div className="w-px h-4 bg-border" />
<KpiItem
icon={AlertTriangle}
label={formatMessage({ id: 'terminalDashboard.kpi.alertCount' })}
value={totalAlerts}
variant={totalAlerts > 0 ? 'destructive' : 'default'}
/>
<span className="text-xs text-muted-foreground ml-auto">
{formatMessage({ id: 'terminalDashboard.page.title' })}
</span>
</div>
);
}

View File

@@ -85,7 +85,7 @@ function IssueItem({
<button
type="button"
className={cn(
'w-full text-left px-3 py-2 rounded-md transition-colors',
'w-full text-left px-2.5 py-1.5 rounded-md transition-colors',
'hover:bg-muted/60 focus:outline-none focus:ring-1 focus:ring-primary/30',
isSelected && 'bg-primary/10 ring-1 ring-primary/30',
isHighlighted && !isSelected && 'bg-accent/50'
@@ -120,7 +120,7 @@ function IssueItem({
{issue.context}
</p>
)}
<div className="mt-1 flex items-center gap-2 text-[10px] text-muted-foreground pl-5">
<div className="mt-0.5 flex items-center gap-2 text-[10px] text-muted-foreground pl-5">
<span className="font-mono">{issue.id}</span>
{issue.labels && issue.labels.length > 0 && (
<>
@@ -140,7 +140,7 @@ function IssueEmptyState() {
return (
<div className="flex-1 flex items-center justify-center text-muted-foreground p-4">
<div className="text-center">
<AlertCircle className="h-10 w-10 mx-auto mb-3 opacity-40" />
<AlertCircle className="h-6 w-6 mx-auto mb-1.5 opacity-30" />
<p className="text-sm">{formatMessage({ id: 'terminalDashboard.issuePanel.noIssues' })}</p>
<p className="text-xs mt-1 opacity-70">
{formatMessage({ id: 'terminalDashboard.issuePanel.noIssuesDesc' })}
@@ -157,7 +157,7 @@ function IssueErrorState({ error }: { error: Error }) {
return (
<div className="flex-1 flex items-center justify-center text-destructive p-4">
<div className="text-center">
<AlertTriangle className="h-10 w-10 mx-auto mb-3 opacity-60" />
<AlertTriangle className="h-6 w-6 mx-auto mb-1.5 opacity-30" />
<p className="text-sm">{formatMessage({ id: 'terminalDashboard.issuePanel.error' })}</p>
<p className="text-xs mt-1 opacity-70">{error.message}</p>
</div>

View File

@@ -164,7 +164,7 @@ function QueueErrorState({ error }: { error: Error }) {
return (
<div className="flex-1 flex items-center justify-center text-destructive p-4">
<div className="text-center">
<AlertTriangle className="h-10 w-10 mx-auto mb-3 opacity-60" />
<AlertTriangle className="h-6 w-6 mx-auto mb-1.5 opacity-30" />
<p className="text-sm">{formatMessage({ id: 'terminalDashboard.queuePanel.error' })}</p>
<p className="text-xs mt-1 opacity-70">{error.message}</p>
</div>

View File

@@ -96,8 +96,8 @@ export function SessionGroupTree() {
{formatMessage({ id: 'terminalDashboard.sessionTree.createGroup' })}
</button>
</div>
<div className="flex-1 flex flex-col items-center justify-center gap-2 text-muted-foreground p-4">
<Folder className="w-8 h-8 opacity-50" />
<div className="flex-1 flex flex-col items-center justify-center gap-1.5 text-muted-foreground p-4">
<Folder className="w-6 h-6 opacity-30" />
<p className="text-xs text-center">
{formatMessage({ id: 'terminalDashboard.sessionTree.noGroups' })}
</p>

View File

@@ -4,8 +4,9 @@
// Horizontal tab strip for terminal sessions in the Terminal Dashboard.
// Renders tabs from sessionManagerStore groups with status indicators and alert badges.
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import { Terminal } from 'lucide-react';
import { Terminal, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
useSessionManagerStore,
@@ -35,6 +36,15 @@ export function TerminalTabBar() {
// Flatten all sessionIds from all groups
const allSessionIds = groups.flatMap((g) => g.sessionIds);
// Total alerts across all terminals
const totalAlerts = useMemo(() => {
let count = 0;
for (const meta of Object.values(terminalMetas)) {
count += meta.alertCount;
}
return count;
}, [terminalMetas]);
if (allSessionIds.length === 0) {
return (
<div className="flex items-center gap-2 px-3 py-2 border-b border-border bg-muted/30 min-h-[40px]">
@@ -88,6 +98,16 @@ export function TerminalTabBar() {
</button>
);
})}
{/* Total alerts indicator at right end */}
{totalAlerts > 0 && (
<div className="ml-auto flex items-center gap-1 px-3 py-2 shrink-0 text-destructive">
<AlertTriangle className="w-3.5 h-3.5" />
<span className="text-[10px] font-semibold tabular-nums">
{totalAlerts > 99 ? '99+' : totalAlerts}
</span>
</div>
)}
</div>
);
}

View File

@@ -2,23 +2,139 @@
// TerminalWorkbench Component
// ========================================
// Container for the right panel of the Terminal Dashboard.
// Combines TerminalTabBar (tab switching) and TerminalInstance (xterm.js)
// in a flex-col layout. MVP scope: single terminal view (1x1 grid).
// Combines TerminalTabBar (tab switching) and TerminalInstance (xterm.js).
// When no terminal is active, shows selected issue detail preview
// or a compact empty state with action hints.
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
import { Terminal } from 'lucide-react';
import {
Terminal,
CircleDot,
Tag,
Clock,
User,
} from 'lucide-react';
import { Badge } from '@/components/ui/Badge';
import { cn } from '@/lib/utils';
import {
useSessionManagerStore,
selectSessionManagerActiveTerminalId,
} from '@/stores/sessionManagerStore';
import {
useIssueQueueIntegrationStore,
selectSelectedIssueId,
} from '@/stores/issueQueueIntegrationStore';
import { useIssues } from '@/hooks/useIssues';
import type { Issue } from '@/lib/api';
import { TerminalTabBar } from './TerminalTabBar';
import { TerminalInstance } from './TerminalInstance';
// ========== Priority Styles ==========
const PRIORITY_VARIANT: Record<Issue['priority'], 'destructive' | 'warning' | 'info' | 'secondary'> = {
critical: 'destructive',
high: 'warning',
medium: 'info',
low: 'secondary',
};
const STATUS_COLORS: Record<Issue['status'], string> = {
open: 'text-info',
in_progress: 'text-warning',
resolved: 'text-success',
closed: 'text-muted-foreground',
completed: 'text-success',
};
// ========== Issue Detail Preview ==========
function IssueDetailPreview({ issue }: { issue: Issue }) {
const { formatMessage } = useIntl();
return (
<div className="flex-1 overflow-y-auto p-6">
<div className="max-w-lg mx-auto space-y-4">
{/* Header hint */}
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">
{formatMessage({ id: 'terminalDashboard.workbench.issuePreview' })}
</p>
{/* Title + Status */}
<div className="space-y-2">
<div className="flex items-start gap-2">
<CircleDot className={cn('w-4 h-4 shrink-0 mt-0.5', STATUS_COLORS[issue.status] ?? 'text-muted-foreground')} />
<h3 className="text-base font-semibold text-foreground leading-snug">
{issue.title}
</h3>
</div>
<div className="flex items-center gap-2 pl-6">
<Badge variant={PRIORITY_VARIANT[issue.priority]} className="text-[10px] px-1.5 py-0">
{issue.priority}
</Badge>
<span className="text-[10px] text-muted-foreground font-mono">{issue.id}</span>
</div>
</div>
{/* Context / Description */}
{issue.context && (
<div className="rounded-md border border-border bg-muted/20 p-3">
<p className="text-xs text-foreground/80 leading-relaxed whitespace-pre-wrap">
{issue.context}
</p>
</div>
)}
{/* Metadata rows */}
<div className="space-y-1.5 text-xs text-muted-foreground">
{issue.labels && issue.labels.length > 0 && (
<div className="flex items-center gap-2">
<Tag className="w-3.5 h-3.5 shrink-0" />
<div className="flex items-center gap-1 flex-wrap">
{issue.labels.map((label) => (
<span key={label} className="px-1.5 py-0.5 rounded bg-muted text-[10px]">
{label}
</span>
))}
</div>
</div>
)}
{issue.assignee && (
<div className="flex items-center gap-2">
<User className="w-3.5 h-3.5 shrink-0" />
<span>{issue.assignee}</span>
</div>
)}
{issue.createdAt && (
<div className="flex items-center gap-2">
<Clock className="w-3.5 h-3.5 shrink-0" />
<span>{new Date(issue.createdAt).toLocaleString()}</span>
</div>
)}
</div>
{/* Hint */}
<p className="text-[10px] text-muted-foreground/60 pt-2">
{formatMessage({ id: 'terminalDashboard.workbench.issuePreviewHint' })}
</p>
</div>
</div>
);
}
// ========== Component ==========
export function TerminalWorkbench() {
const { formatMessage } = useIntl();
const activeTerminalId = useSessionManagerStore(selectSessionManagerActiveTerminalId);
const selectedIssueId = useIssueQueueIntegrationStore(selectSelectedIssueId);
const { issues } = useIssues();
// Find selected issue for preview
const selectedIssue = useMemo(() => {
if (!selectedIssueId) return null;
return issues.find((i) => i.id === selectedIssueId) ?? null;
}, [selectedIssueId, issues]);
return (
<div className="flex flex-col h-full">
@@ -30,11 +146,14 @@ export function TerminalWorkbench() {
<div className="flex-1 min-h-0">
<TerminalInstance sessionId={activeTerminalId} />
</div>
) : selectedIssue ? (
/* Issue detail preview when no terminal but issue is selected */
<IssueDetailPreview issue={selectedIssue} />
) : (
/* Empty state when no terminal is selected */
/* Compact empty state */
<div className="flex-1 flex items-center justify-center text-muted-foreground">
<div className="text-center">
<Terminal className="h-10 w-10 mx-auto mb-3 opacity-50" />
<Terminal className="h-6 w-6 mx-auto mb-1.5 opacity-30" />
<p className="text-sm font-medium">
{formatMessage({ id: 'terminalDashboard.workbench.noTerminal' })}
</p>

View File

@@ -20,6 +20,12 @@
"noSelection": "Select an item to view details",
"associationChain": "Association Chain"
},
"bottomPanel": {
"queueTab": "Queue",
"inspectorTab": "Inspector",
"collapse": "Collapse panel",
"expand": "Expand panel"
},
"sessionTree": {
"createGroup": "New Group",
"groupNamePrompt": "Enter group name",
@@ -67,7 +73,9 @@
},
"workbench": {
"noTerminal": "No terminal selected",
"noTerminalHint": "Select a session from the tab bar or create a new one"
"noTerminalHint": "Select a session from the tab bar or create a new one",
"issuePreview": "Issue Preview",
"issuePreviewHint": "Select a terminal session or send this issue to the queue to begin work"
},
"placeholder": {
"sessionTree": "Session groups will appear here",

View File

@@ -20,6 +20,12 @@
"noSelection": "选择一个项目以查看详情",
"associationChain": "关联链路"
},
"bottomPanel": {
"queueTab": "队列",
"inspectorTab": "检查器",
"collapse": "折叠面板",
"expand": "展开面板"
},
"sessionTree": {
"createGroup": "新建分组",
"groupNamePrompt": "输入分组名称",
@@ -67,7 +73,9 @@
},
"workbench": {
"noTerminal": "未选择终端",
"noTerminalHint": "从标签栏选择会话或创建新会话"
"noTerminalHint": "从标签栏选择会话或创建新会话",
"issuePreview": "问题预览",
"issuePreviewHint": "选择终端会话或将此问题发送到队列以开始工作"
},
"placeholder": {
"sessionTree": "会话分组将在此显示",

View File

@@ -44,6 +44,14 @@ export function TaskListTab({ session, onTaskClick }: TaskListTabProps) {
const { formatMessage } = useIntl();
const tasks = session.tasks || [];
// Detect if session tasks support status tracking (new format has explicit status/status_history in raw data)
const hasStatusTracking = tasks.some((t) => {
const raw = (t as unknown as Record<string, unknown>)._raw as Record<string, unknown> | undefined;
const source = (raw?._raw as Record<string, unknown>) || raw;
return source ? (source.status !== undefined || source.status_history !== undefined) : false;
});
const completed = tasks.filter((t) => t.status === 'completed').length;
const inProgress = tasks.filter((t) => t.status === 'in_progress').length;
const pending = tasks.filter((t) => t.status === 'pending').length;
@@ -165,7 +173,8 @@ export function TaskListTab({ session, onTaskClick }: TaskListTabProps) {
return (
<div className="space-y-4">
{/* Stats Bar with Bulk Actions */}
{/* Stats Bar with Bulk Actions (only for tasks with status tracking) */}
{hasStatusTracking && (
<TaskStatsBar
completed={completed}
inProgress={inProgress}
@@ -177,6 +186,7 @@ export function TaskListTab({ session, onTaskClick }: TaskListTabProps) {
isLoadingInProgress={isLoadingInProgress}
isLoadingCompleted={isLoadingCompleted}
/>
)}
{/* Tasks List */}
{localTasks.length === 0 ? (
@@ -236,12 +246,14 @@ export function TaskListTab({ session, onTaskClick }: TaskListTabProps) {
{/* Right: Status and Meta info */}
<div className="flex flex-col items-end gap-2 flex-shrink-0">
{/* Row 1: Status dropdown */}
{/* Row 1: Status dropdown (only for tasks with status tracking) */}
{hasStatusTracking && (
<TaskStatusDropdown
currentStatus={task.status as TaskStatus}
onStatusChange={(newStatus) => handleTaskStatusChange(task.task_id, newStatus)}
size="sm"
/>
)}
{/* Row 2: Meta info */}
<div className="flex items-center gap-3 flex-wrap justify-end text-xs text-muted-foreground">