feat(dashboard): simplify lite task UI and add exploration context support

- Remove status indicators from lite task cards (progress bars, percentages)
- Remove status icons and badges from task detail items
- Remove stats bar showing completed/in-progress/pending counts
- Add Plan tab in drawer for plan.json data display
- Add exploration-*.json parsing for context tab
- Add collapsible sections for architecture, dependencies, patterns
- Fix currentPath selector bug causing TypeError

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
catlog22
2025-12-05 15:47:49 +08:00
parent 72fe6195af
commit 60bb11c315
7 changed files with 2314 additions and 186 deletions

View File

@@ -1,144 +1,14 @@
import { scanSessions } from '../core/session-scanner.js';
import { aggregateData } from '../core/data-aggregator.js';
import { generateDashboard } from '../core/dashboard-generator.js';
import { launchBrowser, isHeadlessEnvironment } from '../utils/browser-launcher.js';
import { resolvePath, ensureDir, getWorkflowDir, validatePath, validateOutputPath, trackRecentPath, getRecentPaths, normalizePathForDisplay } from '../utils/path-resolver.js';
import chalk from 'chalk';
import { writeFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { serveCommand } from './serve.js';
/**
* View command handler - generates and opens workflow dashboard
* View command handler - starts dashboard server (unified with serve mode)
* @param {Object} options - Command options
*/
export async function viewCommand(options) {
// Validate project path
const pathValidation = validatePath(options.path, { mustExist: true });
if (!pathValidation.valid) {
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
process.exit(1);
}
const workingDir = pathValidation.path;
const workflowDir = join(workingDir, '.workflow');
// Track this path in recent paths
trackRecentPath(workingDir);
console.log(chalk.blue.bold('\n CCW Dashboard Generator\n'));
console.log(chalk.gray(` Project: ${workingDir}`));
console.log(chalk.gray(` Workflow: ${workflowDir}\n`));
// Check if .workflow directory exists
if (!existsSync(workflowDir)) {
console.log(chalk.yellow(' No .workflow directory found.'));
console.log(chalk.gray(' This project may not have any workflow sessions yet.\n'));
// Still generate an empty dashboard
const emptyData = {
generatedAt: new Date().toISOString(),
activeSessions: [],
archivedSessions: [],
liteTasks: { litePlan: [], liteFix: [] },
reviewData: null,
statistics: {
totalSessions: 0,
activeSessions: 0,
totalTasks: 0,
completedTasks: 0,
reviewFindings: 0,
litePlanCount: 0,
liteFixCount: 0
},
projectPath: normalizePathForDisplay(workingDir),
recentPaths: getRecentPaths()
};
await generateAndOpen(emptyData, workflowDir, options);
return;
}
try {
// Step 1: Scan for sessions
console.log(chalk.cyan(' Scanning sessions...'));
const sessions = await scanSessions(workflowDir);
console.log(chalk.green(` Found ${sessions.active.length} active, ${sessions.archived.length} archived sessions`));
if (sessions.hasReviewData) {
console.log(chalk.magenta(' Review data detected - will include Reviews tab'));
}
// Step 2: Aggregate all data
console.log(chalk.cyan(' Aggregating data...'));
const dashboardData = await aggregateData(sessions, workflowDir);
// Add project path and recent paths
dashboardData.projectPath = normalizePathForDisplay(workingDir);
dashboardData.recentPaths = getRecentPaths();
// Log statistics
const stats = dashboardData.statistics;
console.log(chalk.gray(` Tasks: ${stats.completedTasks}/${stats.totalTasks} completed`));
if (stats.reviewFindings > 0) {
console.log(chalk.gray(` Review findings: ${stats.reviewFindings}`));
}
// Step 3 & 4: Generate and open
await generateAndOpen(dashboardData, workflowDir, options);
} catch (error) {
console.error(chalk.red(`\n Error: ${error.message}\n`));
if (process.env.DEBUG) {
console.error(error.stack);
}
process.exit(1);
}
}
/**
* Generate dashboard and optionally open in browser
* @param {Object} data - Dashboard data
* @param {string} workflowDir - Path to .workflow
* @param {Object} options - Command options
*/
async function generateAndOpen(data, workflowDir, options) {
// Step 3: Generate dashboard HTML
console.log(chalk.cyan(' Generating dashboard...'));
const html = await generateDashboard(data);
// Step 4: Validate and write dashboard file
let outputPath;
if (options.output) {
const outputValidation = validateOutputPath(options.output, workflowDir);
if (!outputValidation.valid) {
console.error(chalk.red(`\n Error: ${outputValidation.error}\n`));
process.exit(1);
}
outputPath = outputValidation.path;
} else {
outputPath = join(workflowDir, 'dashboard.html');
}
ensureDir(dirname(outputPath));
writeFileSync(outputPath, html, 'utf8');
console.log(chalk.green(` Dashboard saved: ${outputPath}`));
// Step 5: Open in browser (unless --no-browser or headless environment)
if (options.browser !== false) {
if (isHeadlessEnvironment()) {
console.log(chalk.yellow('\n Running in CI/headless environment - skipping browser launch'));
console.log(chalk.gray(` Open manually: file://${outputPath.replace(/\\/g, '/')}\n`));
} else {
console.log(chalk.cyan(' Opening in browser...'));
try {
await launchBrowser(outputPath);
console.log(chalk.green.bold('\n Dashboard opened in browser!\n'));
} catch (error) {
console.log(chalk.yellow(`\n Could not open browser: ${error.message}`));
console.log(chalk.gray(` Open manually: file://${outputPath.replace(/\\/g, '/')}\n`));
}
}
} else {
console.log(chalk.gray(`\n Open in browser: file://${outputPath.replace(/\\/g, '/')}\n`));
}
// Forward to serve command with same options
await serveCommand({
path: options.path,
port: options.port || 3456,
browser: options.browser
});
}