Add enhanced styles for the review tab, including layout, buttons, and responsive design

This commit is contained in:
catlog22
2025-12-08 22:11:14 +08:00
parent 1c3c070db4
commit 818d9f3f5d
19 changed files with 8259 additions and 9261 deletions

View File

@@ -8,10 +8,22 @@ const __dirname = dirname(__filename);
// Bundled template paths
const UNIFIED_TEMPLATE = join(__dirname, '../templates/dashboard.html');
const JS_FILE = join(__dirname, '../templates/dashboard.js');
const CSS_FILE = join(__dirname, '../templates/dashboard.css');
const MODULE_CSS_DIR = join(__dirname, '../templates/dashboard-css');
const WORKFLOW_TEMPLATE = join(__dirname, '../templates/workflow-dashboard.html');
const REVIEW_TEMPLATE = join(__dirname, '../templates/review-cycle-dashboard.html');
// Modular CSS files in load order
const MODULE_CSS_FILES = [
'01-base.css',
'02-session.css',
'03-tasks.css',
'04-lite-tasks.css',
'05-context.css',
'06-cards.css',
'07-managers.css',
'08-review.css'
];
const MODULE_FILES = [
'utils.js',
'state.js',
@@ -63,8 +75,11 @@ export async function generateDashboard(data) {
function generateFromUnifiedTemplate(data) {
let html = readFileSync(UNIFIED_TEMPLATE, 'utf8');
// Read CSS file
let cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
// Read and concatenate modular CSS files in load order
let cssContent = MODULE_CSS_FILES.map(file => {
const filePath = join(MODULE_CSS_DIR, file);
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
}).join('\n\n');
// Read JS content
let jsContent = '';

View File

@@ -31,10 +31,22 @@ function getEnterpriseMcpPath() {
const wsClients = new Set();
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
const MODULE_CSS_DIR = join(import.meta.dirname, '../templates/dashboard-css');
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
const MODULE_JS_DIR = join(import.meta.dirname, '../templates/dashboard-js');
// Modular CSS files in load order
const MODULE_CSS_FILES = [
'01-base.css',
'02-session.css',
'03-tasks.css',
'04-lite-tasks.css',
'05-context.css',
'06-cards.css',
'07-managers.css',
'08-review.css'
];
/**
* Handle POST request with JSON body
*/
@@ -965,8 +977,11 @@ async function updateTaskStatus(sessionPath, taskId, newStatus) {
function generateServerDashboard(initialPath) {
let html = readFileSync(TEMPLATE_PATH, 'utf8');
// Read CSS file
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
// Read and concatenate modular CSS files in load order
const cssContent = MODULE_CSS_FILES.map(file => {
const filePath = join(MODULE_CSS_DIR, file);
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
}).join('\n\n');
// Read and concatenate modular JS files in dependency order
let jsContent = MODULE_FILES.map(file => {

View File

@@ -1,385 +0,0 @@
import http from 'http';
import { URL } from 'url';
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { scanSessions } from './session-scanner.js';
import { aggregateData } from './data-aggregator.js';
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
/**
* Create and start the dashboard server
* @param {Object} options - Server options
* @param {number} options.port - Port to listen on (default: 3456)
* @param {string} options.initialPath - Initial project path
* @returns {Promise<http.Server>}
*/
export async function startServer(options = {}) {
const port = options.port || 3456;
const initialPath = options.initialPath || process.cwd();
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${port}`);
const pathname = url.pathname;
// CORS headers for API requests
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
try {
// API: Get workflow data for a path
if (pathname === '/api/data') {
const projectPath = url.searchParams.get('path') || initialPath;
const data = await getWorkflowData(projectPath);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
return;
}
// API: Get recent paths
if (pathname === '/api/recent-paths') {
const paths = getRecentPaths();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ paths }));
return;
}
// API: Get session detail data (context, summaries, impl-plan, review)
if (pathname === '/api/session-detail') {
const sessionPath = url.searchParams.get('path');
const dataType = url.searchParams.get('type') || 'all';
if (!sessionPath) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Session path is required' }));
return;
}
const detail = await getSessionDetailData(sessionPath, dataType);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(detail));
return;
}
// Serve dashboard HTML
if (pathname === '/' || pathname === '/index.html') {
const html = generateServerDashboard(initialPath);
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
return;
}
// 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
} catch (error) {
console.error('Server error:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
return new Promise((resolve, reject) => {
server.listen(port, () => {
console.log(`Dashboard server running at http://localhost:${port}`);
resolve(server);
});
server.on('error', reject);
});
}
/**
* Get workflow data for a project path
* @param {string} projectPath
* @returns {Promise<Object>}
*/
async function getWorkflowData(projectPath) {
const resolvedPath = resolvePath(projectPath);
const workflowDir = join(resolvedPath, '.workflow');
// Track this path
trackRecentPath(resolvedPath);
// Check if .workflow exists
if (!existsSync(workflowDir)) {
return {
generatedAt: new Date().toISOString(),
activeSessions: [],
archivedSessions: [],
liteTasks: { litePlan: [], liteFix: [] },
reviewData: { dimensions: {} },
projectOverview: null,
statistics: {
totalSessions: 0,
activeSessions: 0,
totalTasks: 0,
completedTasks: 0,
reviewFindings: 0,
litePlanCount: 0,
liteFixCount: 0
},
projectPath: normalizePathForDisplay(resolvedPath),
recentPaths: getRecentPaths()
};
}
// Scan and aggregate data
const sessions = await scanSessions(workflowDir);
const data = await aggregateData(sessions, workflowDir);
data.projectPath = normalizePathForDisplay(resolvedPath);
data.recentPaths = getRecentPaths();
return data;
}
/**
* Get session detail data (context, summaries, impl-plan, review)
* @param {string} sessionPath - Path to session directory
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
* @returns {Promise<Object>}
*/
async function getSessionDetailData(sessionPath, dataType) {
const result = {};
// Normalize path
const normalizedPath = sessionPath.replace(/\\/g, '/');
try {
// Load context-package.json (in .process/ subfolder)
if (dataType === 'context' || dataType === 'all') {
// Try .process/context-package.json first (common location)
let contextFile = join(normalizedPath, '.process', 'context-package.json');
if (!existsSync(contextFile)) {
// Fallback to session root
contextFile = join(normalizedPath, 'context-package.json');
}
if (existsSync(contextFile)) {
try {
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
} catch (e) {
result.context = null;
}
}
}
// Load task JSONs from .task/ folder
if (dataType === 'tasks' || dataType === 'all') {
const taskDir = join(normalizedPath, '.task');
result.tasks = [];
if (existsSync(taskDir)) {
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
for (const file of files) {
try {
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
result.tasks.push({
filename: file,
task_id: file.replace('.json', ''),
...content
});
} catch (e) {
// Skip unreadable files
}
}
// Sort by task ID
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
}
}
// Load summaries from .summaries/
if (dataType === 'summary' || dataType === 'all') {
const summariesDir = join(normalizedPath, '.summaries');
result.summaries = [];
if (existsSync(summariesDir)) {
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
for (const file of files) {
try {
const content = readFileSync(join(summariesDir, file), 'utf8');
result.summaries.push({ name: file.replace('.md', ''), content });
} catch (e) {
// Skip unreadable files
}
}
}
}
// Load plan.json (for lite tasks)
if (dataType === 'plan' || dataType === 'all') {
const planFile = join(normalizedPath, 'plan.json');
if (existsSync(planFile)) {
try {
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
} catch (e) {
result.plan = null;
}
}
}
// Load IMPL_PLAN.md
if (dataType === 'impl-plan' || dataType === 'all') {
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
if (existsSync(implPlanFile)) {
try {
result.implPlan = readFileSync(implPlanFile, 'utf8');
} catch (e) {
result.implPlan = null;
}
}
}
// Load review data from .review/
if (dataType === 'review' || dataType === 'all') {
const reviewDir = join(normalizedPath, '.review');
result.review = {
state: null,
dimensions: [],
severityDistribution: null,
totalFindings: 0
};
if (existsSync(reviewDir)) {
// Load review-state.json
const stateFile = join(reviewDir, 'review-state.json');
if (existsSync(stateFile)) {
try {
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
result.review.state = state;
result.review.severityDistribution = state.severity_distribution || {};
result.review.totalFindings = state.total_findings || 0;
result.review.phase = state.phase || 'unknown';
result.review.dimensionSummaries = state.dimension_summaries || {};
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
result.review.criticalFiles = state.critical_files || [];
} catch (e) {
// Skip unreadable state
}
}
// Load dimension findings
const dimensionsDir = join(reviewDir, 'dimensions');
if (existsSync(dimensionsDir)) {
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
for (const file of files) {
try {
const dimName = file.replace('.json', '');
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
// Handle array structure: [ { findings: [...] } ]
let findings = [];
let summary = null;
if (Array.isArray(data) && data.length > 0) {
const dimData = data[0];
findings = dimData.findings || [];
summary = dimData.summary || null;
} else if (data.findings) {
findings = data.findings;
summary = data.summary || null;
}
result.review.dimensions.push({
name: dimName,
findings: findings,
summary: summary,
count: findings.length
});
} catch (e) {
// Skip unreadable files
}
}
}
}
}
} catch (error) {
console.error('Error loading session detail:', error);
result.error = error.message;
}
return result;
}
/**
* Generate dashboard HTML for server mode
* @param {string} initialPath
* @returns {string}
*/
function generateServerDashboard(initialPath) {
let html = readFileSync(TEMPLATE_PATH, 'utf8');
// Read CSS and JS files
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
// Inject CSS content
html = html.replace('{{CSS_CONTENT}}', cssContent);
// Prepare JS content with empty initial data (will be loaded dynamically)
const emptyData = {
generatedAt: new Date().toISOString(),
activeSessions: [],
archivedSessions: [],
liteTasks: { litePlan: [], liteFix: [] },
reviewData: { dimensions: {} },
projectOverview: null,
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
};
// Replace JS placeholders
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
// Add server mode flag and dynamic loading functions at the start of JS
const serverModeScript = `
// Server mode - load data dynamically
window.SERVER_MODE = true;
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
async function loadDashboardData(path) {
try {
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
if (!res.ok) throw new Error('Failed to load data');
return await res.json();
} catch (err) {
console.error('Error loading data:', err);
return null;
}
}
async function loadRecentPaths() {
try {
const res = await fetch('/api/recent-paths');
if (!res.ok) return [];
const data = await res.json();
return data.paths || [];
} catch (err) {
return [];
}
}
`;
// Prepend server mode script to JS content
jsContent = serverModeScript + jsContent;
// Inject JS content
html = html.replace('{{JS_CONTENT}}', jsContent);
// Replace any remaining placeholders in HTML
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
return html;
}

View File

@@ -1,385 +0,0 @@
import http from 'http';
import { URL } from 'url';
import { readFileSync, existsSync, readdirSync } from 'fs';
import { join } from 'path';
import { scanSessions } from './session-scanner.js';
import { aggregateData } from './data-aggregator.js';
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
/**
* Create and start the dashboard server
* @param {Object} options - Server options
* @param {number} options.port - Port to listen on (default: 3456)
* @param {string} options.initialPath - Initial project path
* @returns {Promise<http.Server>}
*/
export async function startServer(options = {}) {
const port = options.port || 3456;
const initialPath = options.initialPath || process.cwd();
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${port}`);
const pathname = url.pathname;
// CORS headers for API requests
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
try {
// API: Get workflow data for a path
if (pathname === '/api/data') {
const projectPath = url.searchParams.get('path') || initialPath;
const data = await getWorkflowData(projectPath);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
return;
}
// API: Get recent paths
if (pathname === '/api/recent-paths') {
const paths = getRecentPaths();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ paths }));
return;
}
// API: Get session detail data (context, summaries, impl-plan, review)
if (pathname === '/api/session-detail') {
const sessionPath = url.searchParams.get('path');
const dataType = url.searchParams.get('type') || 'all';
if (!sessionPath) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Session path is required' }));
return;
}
const detail = await getSessionDetailData(sessionPath, dataType);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(detail));
return;
}
// Serve dashboard HTML
if (pathname === '/' || pathname === '/index.html') {
const html = generateServerDashboard(initialPath);
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
return;
}
// 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
} catch (error) {
console.error('Server error:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
return new Promise((resolve, reject) => {
server.listen(port, () => {
console.log(`Dashboard server running at http://localhost:${port}`);
resolve(server);
});
server.on('error', reject);
});
}
/**
* Get workflow data for a project path
* @param {string} projectPath
* @returns {Promise<Object>}
*/
async function getWorkflowData(projectPath) {
const resolvedPath = resolvePath(projectPath);
const workflowDir = join(resolvedPath, '.workflow');
// Track this path
trackRecentPath(resolvedPath);
// Check if .workflow exists
if (!existsSync(workflowDir)) {
return {
generatedAt: new Date().toISOString(),
activeSessions: [],
archivedSessions: [],
liteTasks: { litePlan: [], liteFix: [] },
reviewData: { dimensions: {} },
projectOverview: null,
statistics: {
totalSessions: 0,
activeSessions: 0,
totalTasks: 0,
completedTasks: 0,
reviewFindings: 0,
litePlanCount: 0,
liteFixCount: 0
},
projectPath: normalizePathForDisplay(resolvedPath),
recentPaths: getRecentPaths()
};
}
// Scan and aggregate data
const sessions = await scanSessions(workflowDir);
const data = await aggregateData(sessions, workflowDir);
data.projectPath = normalizePathForDisplay(resolvedPath);
data.recentPaths = getRecentPaths();
return data;
}
/**
* Get session detail data (context, summaries, impl-plan, review)
* @param {string} sessionPath - Path to session directory
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
* @returns {Promise<Object>}
*/
async function getSessionDetailData(sessionPath, dataType) {
const result = {};
// Normalize path
const normalizedPath = sessionPath.replace(/\\/g, '/');
try {
// Load context-package.json (in .process/ subfolder)
if (dataType === 'context' || dataType === 'all') {
// Try .process/context-package.json first (common location)
let contextFile = join(normalizedPath, '.process', 'context-package.json');
if (!existsSync(contextFile)) {
// Fallback to session root
contextFile = join(normalizedPath, 'context-package.json');
}
if (existsSync(contextFile)) {
try {
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
} catch (e) {
result.context = null;
}
}
}
// Load task JSONs from .task/ folder
if (dataType === 'tasks' || dataType === 'all') {
const taskDir = join(normalizedPath, '.task');
result.tasks = [];
if (existsSync(taskDir)) {
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
for (const file of files) {
try {
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
result.tasks.push({
filename: file,
task_id: file.replace('.json', ''),
...content
});
} catch (e) {
// Skip unreadable files
}
}
// Sort by task ID
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
}
}
// Load summaries from .summaries/
if (dataType === 'summary' || dataType === 'all') {
const summariesDir = join(normalizedPath, '.summaries');
result.summaries = [];
if (existsSync(summariesDir)) {
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
for (const file of files) {
try {
const content = readFileSync(join(summariesDir, file), 'utf8');
result.summaries.push({ name: file.replace('.md', ''), content });
} catch (e) {
// Skip unreadable files
}
}
}
}
// Load plan.json (for lite tasks)
if (dataType === 'plan' || dataType === 'all') {
const planFile = join(normalizedPath, 'plan.json');
if (existsSync(planFile)) {
try {
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
} catch (e) {
result.plan = null;
}
}
}
// Load IMPL_PLAN.md
if (dataType === 'impl-plan' || dataType === 'all') {
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
if (existsSync(implPlanFile)) {
try {
result.implPlan = readFileSync(implPlanFile, 'utf8');
} catch (e) {
result.implPlan = null;
}
}
}
// Load review data from .review/
if (dataType === 'review' || dataType === 'all') {
const reviewDir = join(normalizedPath, '.review');
result.review = {
state: null,
dimensions: [],
severityDistribution: null,
totalFindings: 0
};
if (existsSync(reviewDir)) {
// Load review-state.json
const stateFile = join(reviewDir, 'review-state.json');
if (existsSync(stateFile)) {
try {
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
result.review.state = state;
result.review.severityDistribution = state.severity_distribution || {};
result.review.totalFindings = state.total_findings || 0;
result.review.phase = state.phase || 'unknown';
result.review.dimensionSummaries = state.dimension_summaries || {};
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
result.review.criticalFiles = state.critical_files || [];
} catch (e) {
// Skip unreadable state
}
}
// Load dimension findings
const dimensionsDir = join(reviewDir, 'dimensions');
if (existsSync(dimensionsDir)) {
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
for (const file of files) {
try {
const dimName = file.replace('.json', '');
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
// Handle array structure: [ { findings: [...] } ]
let findings = [];
let summary = null;
if (Array.isArray(data) && data.length > 0) {
const dimData = data[0];
findings = dimData.findings || [];
summary = dimData.summary || null;
} else if (data.findings) {
findings = data.findings;
summary = data.summary || null;
}
result.review.dimensions.push({
name: dimName,
findings: findings,
summary: summary,
count: findings.length
});
} catch (e) {
// Skip unreadable files
}
}
}
}
}
} catch (error) {
console.error('Error loading session detail:', error);
result.error = error.message;
}
return result;
}
/**
* Generate dashboard HTML for server mode
* @param {string} initialPath
* @returns {string}
*/
function generateServerDashboard(initialPath) {
let html = readFileSync(TEMPLATE_PATH, 'utf8');
// Read CSS and JS files
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
// Inject CSS content
html = html.replace('{{CSS_CONTENT}}', cssContent);
// Prepare JS content with empty initial data (will be loaded dynamically)
const emptyData = {
generatedAt: new Date().toISOString(),
activeSessions: [],
archivedSessions: [],
liteTasks: { litePlan: [], liteFix: [] },
reviewData: { dimensions: {} },
projectOverview: null,
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
};
// Replace JS placeholders
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
// Add server mode flag and dynamic loading functions at the start of JS
const serverModeScript = `
// Server mode - load data dynamically
window.SERVER_MODE = true;
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
async function loadDashboardData(path) {
try {
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
if (!res.ok) throw new Error('Failed to load data');
return await res.json();
} catch (err) {
console.error('Error loading data:', err);
return null;
}
}
async function loadRecentPaths() {
try {
const res = await fetch('/api/recent-paths');
if (!res.ok) return [];
const data = await res.json();
return data.paths || [];
} catch (err) {
return [];
}
}
`;
// Prepend server mode script to JS content
jsContent = serverModeScript + jsContent;
// Inject JS content
html = html.replace('{{JS_CONTENT}}', jsContent);
// Replace any remaining placeholders in HTML
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
return html;
}

View File

@@ -0,0 +1,128 @@
/* ===================================
Dashboard - Complementary Styles
================================== */
/* This file contains only essential CSS that cannot be achieved
with Tailwind utilities. All layout, colors, and basic styling
are handled by Tailwind classes in dashboard.html.
CSS variables are defined inline in dashboard.html <style> block. */
/* Font Family Definitions */
:root {
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'SF Mono', 'Consolas', 'Monaco', 'Liberation Mono', 'Courier New', monospace;
}
body {
font-family: var(--font-sans);
}
/* Scrollbar styling (cannot be done in Tailwind) */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground));
}
/* Sidebar collapse state (JavaScript-toggled class) */
.sidebar.collapsed {
width: 60px;
}
.sidebar.collapsed .nav-text,
.sidebar.collapsed .nav-section-title,
.sidebar.collapsed .badge,
.sidebar.collapsed .toggle-text {
display: none;
}
.sidebar.collapsed .nav-section-header {
justify-content: center;
padding: 12px 0;
}
.sidebar.collapsed .nav-item {
justify-content: center;
padding: 10px 0;
}
/* Nav item active state */
.nav-item.active {
background-color: hsl(var(--accent));
color: hsl(var(--primary));
font-weight: 500;
}
.nav-item.active .nav-icon {
color: hsl(var(--primary));
}
.nav-item.active .nav-count {
background-color: hsl(var(--primary));
color: white;
}
.sidebar.collapsed .toggle-icon {
transform: rotate(180deg);
}
/* Path menu open state (JavaScript-toggled class) */
.path-menu.open {
display: block;
}
/* Mobile sidebar (responsive behavior beyond Tailwind) */
@media (max-width: 768px) {
.sidebar {
position: fixed;
left: -260px;
top: 56px;
height: calc(100vh - 56px);
z-index: 200;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
}
.sidebar.open {
left: 0;
}
.sidebar-overlay {
display: none;
}
.sidebar-overlay.open {
display: block;
}
.menu-toggle-btn {
display: block !important;
}
}
/* Task detail drawer (complex transform animation) */
.task-detail-drawer {
transform: translateX(100%);
transition: transform 0.3s ease;
}
.task-detail-drawer.open {
transform: translateX(0);
}
.drawer-overlay.active {
display: block;
}

View File

@@ -0,0 +1,726 @@
/* ===================================
Session Cards (used by dashboard.js)
=================================== */
.sessions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem;
}
.session-card {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.session-card:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
transform: translateY(-2px);
}
.session-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.75rem;
}
.session-title {
font-weight: 600;
font-size: 0.9rem;
color: hsl(var(--foreground));
word-break: break-word;
}
.session-badges {
display: flex;
gap: 0.5rem;
flex-shrink: 0;
}
.session-status {
font-size: 0.65rem;
font-weight: 600;
padding: 0.2rem 0.5rem;
border-radius: 9999px;
text-transform: uppercase;
}
.session-status.active {
background: hsl(var(--success-light));
color: hsl(var(--success));
}
.session-status.archived {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
.session-type-badge {
font-size: 0.65rem;
font-weight: 500;
padding: 0.2rem 0.5rem;
border-radius: 9999px;
background: hsl(var(--accent));
color: hsl(var(--accent-foreground));
text-transform: uppercase;
}
.session-type-badge.review {
background: hsl(var(--warning-light));
color: hsl(var(--warning));
}
.session-type-badge.test {
background: hsl(220 80% 90%);
color: hsl(220 80% 40%);
}
.session-body {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.session-meta {
display: flex;
gap: 1rem;
font-size: 0.8rem;
color: hsl(var(--muted-foreground));
}
.session-meta-item {
display: flex;
align-items: center;
gap: 0.25rem;
}
.progress-container {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.progress-label {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.progress-bar-wrapper {
display: flex;
align-items: center;
gap: 0.75rem;
}
.progress-bar {
flex: 1;
height: 6px;
background: hsl(var(--muted));
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--success)));
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
white-space: nowrap;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 3rem;
color: hsl(var(--muted-foreground));
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Session detail page */
.session-detail-header {
margin-bottom: 1.5rem;
}
.session-detail-title {
font-size: 1.5rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.5rem;
}
.session-detail-meta {
display: flex;
gap: 1rem;
flex-wrap: wrap;
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
}
.task-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.task-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.15s ease;
}
.task-item:hover {
background: hsl(var(--hover));
}
.task-item.completed {
border-left: 3px solid hsl(var(--success));
opacity: 0.8;
}
.task-item.in_progress {
border-left: 3px solid hsl(var(--warning));
}
.task-item.pending {
border-left: 3px solid hsl(var(--muted-foreground));
}
.task-checkbox {
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
border: 2px solid hsl(var(--border));
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: bold;
flex-shrink: 0;
}
.task-item.completed .task-checkbox {
background: hsl(var(--success));
border-color: hsl(var(--success));
color: white;
}
.task-item.completed .task-checkbox::after {
content: '✓';
}
.task-item.in_progress .task-checkbox {
border-color: hsl(var(--warning));
color: hsl(var(--warning));
}
.task-item.in_progress .task-checkbox::after {
content: '⟳';
}
.task-title {
flex: 1;
font-size: 0.875rem;
color: hsl(var(--foreground));
}
.task-id {
font-size: 0.75rem;
font-family: var(--font-mono);
color: hsl(var(--muted-foreground));
}
/* Back button */
.back-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: hsl(var(--muted));
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
color: hsl(var(--foreground));
cursor: pointer;
transition: background 0.15s;
margin-bottom: 1rem;
}
.back-button:hover {
background: hsl(var(--hover));
}
/* ===================================
Path Dropdown Menu
=================================== */
.path-menu {
min-width: 320px;
max-height: 400px;
overflow-y: auto;
}
.path-menu .path-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
cursor: pointer;
transition: background 0.15s;
border-bottom: 1px solid hsl(var(--border));
}
.path-menu .path-item:hover {
background: hsl(var(--hover));
}
.path-menu .path-item:last-child {
border-bottom: none;
}
.path-menu .path-icon {
font-size: 1.25rem;
flex-shrink: 0;
}
.path-menu .path-text {
flex: 1;
font-size: 0.875rem;
color: hsl(var(--foreground));
word-break: break-all;
}
.path-menu .path-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.path-menu .path-text {
cursor: pointer;
}
.path-delete-btn {
width: 20px;
height: 20px;
border: none;
background: transparent;
color: hsl(var(--muted-foreground));
font-size: 16px;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.15s, background 0.15s, color 0.15s;
flex-shrink: 0;
margin-left: 8px;
}
.path-menu .path-item:hover .path-delete-btn {
opacity: 1;
}
.path-delete-btn:hover {
background: hsl(var(--destructive) / 0.1);
color: hsl(var(--destructive));
}
/* ===================================
Session Detail Page
=================================== */
.session-detail-page {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.detail-header {
display: flex;
flex-direction: column;
gap: 1rem;
}
.btn-back {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: hsl(var(--muted));
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
color: hsl(var(--foreground));
cursor: pointer;
transition: background 0.15s;
width: fit-content;
}
.btn-back:hover {
background: hsl(var(--hover));
}
.back-icon {
font-size: 1rem;
}
.detail-title-row {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.detail-session-id {
font-size: 1.5rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0;
word-break: break-word;
}
.detail-badges {
display: flex;
gap: 0.5rem;
}
.detail-info-bar {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
padding: 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
}
.info-item {
display: flex;
gap: 0.5rem;
font-size: 0.875rem;
}
.info-label {
color: hsl(var(--muted-foreground));
}
.info-value {
color: hsl(var(--foreground));
font-weight: 500;
}
/* Detail Tabs */
.detail-tabs {
display: flex;
gap: 0.25rem;
border-bottom: 1px solid hsl(var(--border));
overflow-x: auto;
}
.detail-tab {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.detail-tab:hover {
color: hsl(var(--foreground));
background: hsl(var(--hover));
}
.detail-tab.active {
color: hsl(var(--primary));
border-bottom-color: hsl(var(--primary));
}
.tab-icon {
font-size: 1rem;
}
.tab-count {
padding: 0.125rem 0.5rem;
background: hsl(var(--muted));
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
.detail-tab.active .tab-count {
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
.detail-tab-content {
min-height: 300px;
}
/* Task Stats */
.task-stats {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.task-stat {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
font-size: 0.875rem;
}
.task-stat.completed { border-left: 3px solid hsl(var(--success)); }
.task-stat.in-progress { border-left: 3px solid hsl(var(--warning)); }
.task-stat.pending { border-left: 3px solid hsl(var(--muted-foreground)); }
.stat-count {
font-weight: 600;
color: hsl(var(--foreground));
}
.stat-label {
color: hsl(var(--muted-foreground));
}
/* Tab Loading */
.tab-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 3rem;
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
}
/* Context/Summary Content */
.context-content,
.summary-content,
.impl-plan-content,
.review-content {
padding: 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
}
.context-section,
.summary-section,
.plan-section {
margin-bottom: 1.5rem;
}
.context-section:last-child,
.summary-section:last-child,
.plan-section:last-child {
margin-bottom: 0;
}
/* Plan Tab Styles */
.plan-tab-content {
padding: 1rem 0;
}
.plan-section-title {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.plan-summary-text,
.plan-approach-text {
color: hsl(var(--foreground));
line-height: 1.6;
padding: 0.75rem 1rem;
background: hsl(var(--muted));
border-radius: 0.5rem;
border-left: 3px solid hsl(var(--primary));
}
.plan-meta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.75rem;
}
.plan-meta-grid .meta-item {
padding: 0.5rem 0.75rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-size: 0.875rem;
}
.plan-meta-grid .meta-label {
font-weight: 600;
color: hsl(var(--muted-foreground));
}
/* Lite Task Detail Page */
.lite-task-detail-page .detail-header {
margin-bottom: 1.5rem;
}
.lite-task-detail-page .task-toolbar {
margin-bottom: 1rem;
}
.lite-task-detail-page .task-stat {
font-size: 0.8rem;
}
/* Context Tab Content */
.context-tab-content {
padding: 1rem 0;
}
.context-tab-content .context-section h4 {
font-size: 0.9rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.5rem;
}
.context-tab-content .context-section p {
color: hsl(var(--foreground));
line-height: 1.6;
}
.section-title {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.file-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.file-item {
padding: 0.5rem 0.75rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.8rem;
color: hsl(var(--foreground));
}
/* Code blocks */
pre, code {
font-family: var(--font-mono);
background: hsl(var(--muted));
border-radius: 0.25rem;
}
pre {
padding: 1rem;
overflow-x: auto;
font-size: 0.8rem;
line-height: 1.5;
}
code {
padding: 0.125rem 0.25rem;
font-size: 0.85em;
}
/* Review Findings */
.findings-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.finding-item {
padding: 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
border-left: 3px solid hsl(var(--border));
}
.finding-item.critical { border-left-color: hsl(0 70% 50%); }
.finding-item.high { border-left-color: hsl(25 90% 55%); }
.finding-item.medium { border-left-color: hsl(45 90% 50%); }
.finding-item.low { border-left-color: hsl(var(--success)); }
.finding-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 0.5rem;
}
.finding-title {
font-weight: 600;
color: hsl(var(--foreground));
}
.severity-badge {
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.severity-badge.critical { background: hsl(0 70% 90%); color: hsl(0 70% 40%); }
.severity-badge.high { background: hsl(25 90% 90%); color: hsl(25 90% 35%); }
.severity-badge.medium { background: hsl(45 90% 90%); color: hsl(45 90% 30%); }
.severity-badge.low { background: hsl(var(--success-light)); color: hsl(var(--success)); }
.finding-description {
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
line-height: 1.5;
}
.finding-location {
margin-top: 0.5rem;
font-size: 0.75rem;
font-family: var(--font-mono);
color: hsl(var(--muted-foreground));
}

View File

@@ -0,0 +1,512 @@
/* ===================================
Tasks Tab Content
=================================== */
.tasks-tab-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Task Toolbar - Combined Stats & Actions */
.task-toolbar {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1rem;
background: hsl(var(--muted) / 0.2);
border: 1px solid hsl(var(--border) / 0.5);
border-radius: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.toolbar-divider {
width: 1px;
height: 1.5rem;
background: hsl(var(--border));
flex-shrink: 0;
}
.task-stats-bar {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.task-stat {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 9999px;
font-size: 0.8rem;
font-weight: 500;
}
.task-stat.completed {
border-color: hsl(142 76% 36% / 0.4);
background: hsl(142 76% 36% / 0.1);
color: hsl(142 76% 30%);
}
.task-stat.in-progress {
border-color: hsl(38 92% 50% / 0.4);
background: hsl(38 92% 50% / 0.1);
color: hsl(38 92% 40%);
}
.task-stat.pending {
border-color: hsl(var(--border));
background: hsl(var(--muted) / 0.3);
color: hsl(var(--muted-foreground));
}
/* Responsive: Stack toolbar on smaller screens */
@media (max-width: 768px) {
.task-toolbar {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.toolbar-divider {
width: 100%;
height: 1px;
}
.task-stats-bar {
justify-content: center;
}
.task-bulk-actions {
justify-content: center;
}
}
.tasks-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
/* Task Item */
.detail-task-item {
display: flex;
flex-direction: column;
padding: 0.875rem 1rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
border-left: 3px solid hsl(var(--muted-foreground));
transition: all 0.15s ease;
}
.detail-task-item:hover {
background: hsl(var(--hover));
box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
}
.detail-task-item.completed {
border-left-color: hsl(var(--success));
}
.detail-task-item.in_progress {
border-left-color: hsl(var(--warning));
}
.detail-task-item.pending {
border-left-color: hsl(var(--muted-foreground));
}
/* Status-based background colors for task cards */
.detail-task-item.status-completed {
background: hsl(var(--success) / 0.08);
}
.detail-task-item.status-completed:hover {
background: hsl(var(--success) / 0.12);
}
.detail-task-item.status-in_progress {
background: hsl(var(--warning) / 0.08);
}
.detail-task-item.status-in_progress:hover {
background: hsl(var(--warning) / 0.12);
}
.detail-task-item.status-pending {
background: hsl(var(--muted-foreground) / 0.05);
}
.detail-task-item.status-pending:hover {
background: hsl(var(--muted-foreground) / 0.08);
}
.task-item-header {
display: flex;
align-items: center;
gap: 0.75rem;
}
.task-status-icon {
font-size: 1rem;
width: 1.5rem;
text-align: center;
flex-shrink: 0;
}
.detail-task-item.completed .task-status-icon {
color: hsl(var(--success));
}
.detail-task-item.in_progress .task-status-icon {
color: hsl(var(--warning));
}
.task-id-badge {
padding: 0.125rem 0.5rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-size: 0.75rem;
font-family: var(--font-mono);
font-weight: 500;
color: hsl(var(--foreground));
flex-shrink: 0;
}
.task-item-header .task-title {
flex: 1;
font-size: 0.875rem;
color: hsl(var(--foreground));
}
.task-status-badge {
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
flex-shrink: 0;
}
.task-status-badge.completed {
background: hsl(var(--success-light));
color: hsl(var(--success));
}
.task-status-badge.in_progress {
background: hsl(var(--warning-light));
color: hsl(var(--warning));
}
.task-status-badge.pending {
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
/* Full Task Item (expanded view) */
.detail-task-item-full {
display: flex;
flex-direction: column;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
border-left: 3px solid hsl(var(--muted-foreground));
overflow: hidden;
}
.detail-task-item-full.completed {
border-left-color: hsl(var(--success));
}
.detail-task-item-full.in_progress {
border-left-color: hsl(var(--warning));
}
.task-item-header-full {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
background: hsl(var(--card));
transition: background 0.15s;
}
.task-item-header-full:hover {
background: hsl(var(--hover));
}
/* Empty State */
.tab-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
text-align: center;
}
.empty-icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-title {
font-size: 1.25rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.5rem;
}
.empty-text {
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
}
/* Tab Error */
.tab-error {
padding: 2rem;
text-align: center;
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 0.1);
border-radius: 0.5rem;
}
/* Collapsible Sections */
.collapsible-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: hsl(var(--muted));
cursor: pointer;
transition: background 0.15s;
}
.collapsible-header:hover {
background: hsl(var(--hover));
}
.collapsible-icon {
transition: transform 0.2s;
}
.collapsible-header.open .collapsible-icon {
transform: rotate(90deg);
}
.collapsible-content {
padding: 1rem;
display: none;
}
.collapsible-content.open {
display: block;
}
/* ===================================
Task Drawer (Sidebar Panel)
=================================== */
.drawer-overlay {
display: none;
}
.drawer-overlay.active {
display: block;
}
.drawer-task-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.drawer-tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border));
overflow-x: auto;
}
.drawer-tab {
padding: 0.625rem 1rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.drawer-tab:hover {
color: hsl(var(--foreground));
background: hsl(var(--hover));
}
.drawer-tab.active {
color: hsl(var(--primary));
border-bottom-color: hsl(var(--primary));
}
.drawer-tab-content {
flex: 1;
overflow-y: auto;
}
.drawer-panel {
display: none;
}
.drawer-panel.active {
display: block;
}
.drawer-section {
margin-bottom: 1.5rem;
}
.drawer-section-title {
font-size: 0.875rem;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0 0 0.75rem 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.empty-section {
padding: 1rem;
text-align: center;
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
background: hsl(var(--muted));
border-radius: 0.375rem;
margin-bottom: 1rem;
}
/* Steps List */
.steps-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.step-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.375rem;
}
.step-number {
width: 1.5rem;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-radius: 50%;
font-size: 0.75rem;
font-weight: 600;
flex-shrink: 0;
}
.step-content {
flex: 1;
font-size: 0.875rem;
color: hsl(var(--foreground));
line-height: 1.5;
}
.step-description {
font-size: 0.8rem;
color: hsl(var(--muted-foreground));
margin-top: 0.25rem;
}
/* Files List */
.files-list {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.file-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.8rem;
color: hsl(var(--foreground));
}
.file-icon {
font-size: 0.875rem;
flex-shrink: 0;
}
/* Test Commands */
.test-commands {
margin-top: 1rem;
}
.command-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: hsl(220 13% 18%);
color: hsl(142 71% 60%);
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.8rem;
margin-bottom: 0.375rem;
}
/* Flowchart Container */
.flowchart-container {
min-height: 300px;
background: hsl(var(--muted));
border-radius: 0.5rem;
padding: 1rem;
overflow: auto;
}
.flowchart-container svg {
max-width: 100%;
}
/* JSON View */
.json-view {
background: hsl(var(--muted));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
font-size: 0.75rem;
line-height: 1.6;
color: hsl(var(--foreground));
max-height: 500px;
overflow-y: auto;
}

View File

@@ -0,0 +1,843 @@
/* ===================================
Lite Task Detail Page Additions
=================================== */
/* Path Tags */
.path-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.path-tag {
padding: 0.25rem 0.5rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.8rem;
color: hsl(var(--foreground));
}
/* JSON Content */
.json-content {
background: hsl(var(--muted));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
font-size: 0.75rem;
line-height: 1.6;
color: hsl(var(--foreground));
max-height: 500px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
}
/* Button View JSON */
.btn-view-json {
padding: 0.25rem 0.5rem;
background: hsl(var(--muted));
border: 1px solid hsl(var(--border));
border-radius: 0.25rem;
font-size: 0.7rem;
font-family: var(--font-mono);
color: hsl(var(--muted-foreground));
cursor: pointer;
transition: all 0.15s;
}
.btn-view-json:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
}
/* Context Fields */
.context-fields {
display: flex;
flex-direction: column;
gap: 1rem;
}
.context-section {
padding: 1rem;
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
}
.context-section-title {
font-size: 1rem;
font-weight: 600;
color: hsl(var(--foreground));
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid hsl(var(--border));
}
.context-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.context-label {
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
text-transform: uppercase;
letter-spacing: 0.025em;
}
.context-value {
font-size: 0.875rem;
color: hsl(var(--foreground));
line-height: 1.6;
}
.context-field label {
display: block;
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
margin-bottom: 0.5rem;
text-transform: uppercase;
}
.context-field p {
margin: 0;
font-size: 0.875rem;
color: hsl(var(--foreground));
line-height: 1.5;
}
.context-field ul {
margin: 0;
padding-left: 1.25rem;
font-size: 0.875rem;
color: hsl(var(--foreground));
}
.context-field ul li {
margin-bottom: 0.25rem;
}
/* Modification Points */
.mod-points {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.mod-point {
padding: 0.5rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.25rem;
}
.mod-target {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
margin-left: 0.5rem;
}
.mod-change {
margin: 0.5rem 0 0 0;
font-size: 0.8rem;
color: hsl(var(--foreground));
}
/* Implementation Steps */
.impl-steps {
margin: 0;
padding-left: 1.25rem;
font-size: 0.875rem;
color: hsl(var(--foreground));
}
.impl-steps li {
margin-bottom: 0.5rem;
line-height: 1.5;
}
/* Implementation Steps List (Drawer) */
.impl-steps-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.impl-step-item {
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1rem;
transition: border-color 0.2s;
}
.impl-step-item:hover {
border-color: hsl(var(--primary) / 0.5);
}
.impl-step-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.impl-step-number {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.75rem;
height: 1.75rem;
padding: 0 0.5rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.impl-step-title {
font-weight: 600;
font-size: 0.9rem;
color: hsl(var(--foreground));
flex: 1;
}
.impl-step-desc {
font-size: 0.85rem;
color: hsl(var(--muted-foreground));
line-height: 1.5;
margin-bottom: 0.75rem;
padding-left: 2.5rem;
}
.impl-step-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid hsl(var(--border));
}
.impl-step-mods,
.impl-step-flow {
font-size: 0.8rem;
}
.impl-step-mods strong,
.impl-step-flow strong {
display: block;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: hsl(var(--muted-foreground));
margin-bottom: 0.5rem;
}
.impl-step-mods ul,
.impl-step-flow ol {
margin: 0;
padding-left: 1.25rem;
color: hsl(var(--foreground));
}
.impl-step-mods li,
.impl-step-flow li {
margin-bottom: 0.375rem;
line-height: 1.4;
}
.impl-step-mods code {
font-size: 0.75rem;
padding: 0.125rem 0.375rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
color: hsl(var(--primary));
}
.impl-step-deps {
margin-top: 0.75rem;
padding-top: 0.5rem;
border-top: 1px dashed hsl(var(--border));
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
}
.dep-badge {
display: inline-block;
padding: 0.125rem 0.5rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-size: 0.7rem;
margin-left: 0.25rem;
}
/* Field Groups */
.field-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.field-row {
display: flex;
gap: 0.5rem;
font-size: 0.875rem;
}
.field-label {
font-weight: 500;
color: hsl(var(--muted-foreground));
min-width: 100px;
}
.field-value {
color: hsl(var(--foreground));
flex: 1;
}
.json-value-null {
color: hsl(var(--muted-foreground));
font-style: italic;
}
.json-value-boolean {
color: hsl(220 80% 60%);
}
.json-value-number {
color: hsl(142 71% 45%);
}
.json-value-string {
color: hsl(var(--foreground));
}
/* Array Items */
.array-value {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
}
.array-item {
padding: 0.125rem 0.375rem;
background: hsl(var(--muted));
border-radius: 0.25rem;
font-size: 0.8rem;
}
.array-item.path-item {
font-family: var(--font-mono);
background: hsl(var(--accent));
}
/* Nested Array */
.nested-array {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 0.25rem;
}
.array-object {
padding: 0.5rem;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 0.25rem;
}
.array-object-header {
font-size: 0.7rem;
font-weight: 600;
color: hsl(var(--muted-foreground));
margin-bottom: 0.25rem;
}
/* Collapsible Sections */
.collapsible-section {
border-top: 1px solid hsl(var(--border));
}
.collapsible-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
cursor: pointer;
transition: background 0.15s;
}
.collapsible-header:hover {
background: hsl(var(--hover));
}
.collapse-icon {
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
transition: transform 0.2s;
}
.collapsible-header.expanded .collapse-icon {
transform: rotate(90deg);
}
.section-label {
font-size: 0.75rem;
font-weight: 600;
color: hsl(var(--foreground));
text-transform: uppercase;
}
.section-preview {
flex: 1;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.collapsible-content {
padding: 1rem;
background: hsl(var(--muted));
}
.collapsible-content.collapsed {
display: none;
}
/* Summary Tab */
.summary-tab-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
.summary-item-collapsible {
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
overflow: hidden;
}
.summary-collapsible-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: hsl(var(--card));
cursor: pointer;
transition: background 0.15s;
}
.summary-collapsible-header:hover {
background: hsl(var(--hover));
}
.summary-name {
font-weight: 600;
color: hsl(var(--foreground));
}
.summary-preview {
flex: 1;
font-size: 0.75rem;
color: hsl(var(--muted-foreground));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.summary-collapsible-content {
padding: 1rem;
background: hsl(var(--muted));
}
.summary-collapsible-content.collapsed {
display: none;
}
.summary-content-pre {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.8rem;
line-height: 1.6;
}
/* Summary Item Direct (No collapse) */
.summary-item-direct {
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1rem;
background: hsl(var(--card));
}
.summary-item-direct .summary-content-pre {
margin-top: 0.5rem;
padding: 0.75rem;
background: hsl(var(--muted));
border-radius: 0.375rem;
}
.markdown-content {
background: hsl(var(--muted));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
font-size: 0.8rem;
line-height: 1.6;
color: hsl(var(--foreground));
max-height: 600px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-word;
margin: 0;
}
/* JSON Modal */
.json-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
opacity: 0;
transition: opacity 0.2s;
}
.json-modal-overlay.active {
opacity: 1;
}
.json-modal {
background: hsl(var(--card));
border-radius: 0.5rem;
width: 90%;
max-width: 700px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.2);
}
.json-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid hsl(var(--border));
}
.json-modal-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
color: hsl(var(--foreground));
}
.json-modal-close {
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
font-size: 1.5rem;
color: hsl(var(--muted-foreground));
cursor: pointer;
border-radius: 0.25rem;
}
.json-modal-close:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
}
.json-modal-body {
flex: 1;
overflow: auto;
padding: 1rem;
}
.json-modal-content {
margin: 0;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.75rem;
line-height: 1.6;
color: hsl(var(--foreground));
}
.json-modal-footer {
padding: 1rem;
border-top: 1px solid hsl(var(--border));
display: flex;
justify-content: flex-end;
}
.btn-copy-json {
padding: 0.5rem 1rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-copy-json:hover {
opacity: 0.9;
}
/* Flowchart Fallback */
.flowchart-fallback {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
}
/* ===================================
Markdown Modal
=================================== */
.markdown-modal.hidden {
display: none;
}
.md-tab-btn {
color: hsl(var(--muted-foreground));
}
.md-tab-btn.active {
background: hsl(var(--background));
color: hsl(var(--foreground));
font-weight: 500;
}
.md-tab-btn:hover:not(.active) {
color: hsl(var(--foreground));
}
/* Markdown Preview Prose Styles */
.markdown-preview {
color: hsl(var(--foreground));
line-height: 1.7;
}
.markdown-preview h1,
.markdown-preview h2,
.markdown-preview h3,
.markdown-preview h4,
.markdown-preview h5,
.markdown-preview h6 {
color: hsl(var(--foreground));
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
line-height: 1.3;
}
.markdown-preview h1 { font-size: 1.75rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0.3em; }
.markdown-preview h2 { font-size: 1.5rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0.3em; }
.markdown-preview h3 { font-size: 1.25rem; }
.markdown-preview h4 { font-size: 1.1rem; }
.markdown-preview p {
margin-bottom: 1em;
}
.markdown-preview ul,
.markdown-preview ol {
margin-bottom: 1em;
padding-left: 1.5em;
}
.markdown-preview li {
margin-bottom: 0.25em;
}
.markdown-preview code {
background: hsl(var(--muted));
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.875em;
color: hsl(var(--primary));
}
.markdown-preview pre {
background: hsl(var(--muted));
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1em;
}
.markdown-preview pre code {
background: none;
padding: 0;
color: hsl(var(--foreground));
}
.markdown-preview blockquote {
border-left: 3px solid hsl(var(--primary));
padding-left: 1rem;
margin-left: 0;
margin-bottom: 1em;
color: hsl(var(--muted-foreground));
font-style: italic;
}
.markdown-preview table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1em;
}
.markdown-preview th,
.markdown-preview td {
border: 1px solid hsl(var(--border));
padding: 0.5rem 0.75rem;
text-align: left;
}
.markdown-preview th {
background: hsl(var(--muted));
font-weight: 600;
}
.markdown-preview a {
color: hsl(var(--primary));
text-decoration: underline;
}
.markdown-preview hr {
border: none;
border-top: 1px solid hsl(var(--border));
margin: 1.5em 0;
}
/* View Details Button */
.btn-view-details {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border: none;
border-radius: 0.375rem;
font-size: 0.8rem;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-view-details:hover {
opacity: 0.9;
}
.summary-item-card {
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
}
.summary-item-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.summary-item-name {
font-weight: 500;
color: hsl(var(--foreground));
display: flex;
align-items: center;
gap: 0.5rem;
}
.summary-item-title {
font-weight: 600;
font-size: 0.95rem;
color: hsl(var(--foreground));
margin: 0;
}
.summary-item-preview {
font-size: 0.8rem;
color: hsl(var(--muted-foreground));
margin-top: 0.5rem;
line-height: 1.5;
}
.summary-preview-text {
margin: 0;
padding: 0.75rem;
background: hsl(var(--muted) / 0.5);
border-radius: 0.25rem;
font-size: 0.75rem;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
max-height: 80px;
overflow: hidden;
}
.impl-plan-card {
background: hsl(var(--muted) / 0.3);
border: 1px solid hsl(var(--border));
border-radius: 0.5rem;
padding: 1rem;
}
.impl-plan-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.impl-plan-title {
font-weight: 600;
color: hsl(var(--foreground));
display: flex;
align-items: center;
gap: 0.5rem;
}
.impl-plan-preview {
font-size: 0.8rem;
color: hsl(var(--muted-foreground));
line-height: 1.5;
}
.impl-plan-preview-text {
margin: 0;
padding: 0.75rem;
background: hsl(var(--muted) / 0.5);
border-radius: 0.25rem;
font-size: 0.75rem;
line-height: 1.4;
white-space: pre-wrap;
word-break: break-word;
max-height: 100px;
overflow: hidden;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,936 @@
/* ==========================================
REFRESH BUTTON
========================================== */
.refresh-btn {
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.refresh-btn:hover {
background: hsl(var(--muted));
}
.refresh-btn.refreshing svg {
animation: spin 1s linear infinite;
}
.refresh-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ==========================================
MCP MANAGER STYLES
========================================== */
.mcp-manager {
width: 100%;
}
.mcp-section {
margin-bottom: 2rem;
width: 100%;
}
.mcp-server-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
width: 100%;
}
.mcp-server-card {
position: relative;
}
.mcp-server-card.opacity-60 {
opacity: 0.6;
}
.mcp-server-available {
border-style: dashed;
}
.mcp-server-available:hover {
border-style: solid;
}
/* MCP Toggle Switch */
.mcp-toggle {
position: relative;
display: inline-flex;
align-items: center;
}
.mcp-toggle input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.mcp-toggle > div {
width: 36px;
height: 20px;
background: hsl(var(--muted));
border-radius: 10px;
position: relative;
transition: background 0.2s;
}
.mcp-toggle > div::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.mcp-toggle input:checked + div {
background: hsl(var(--success));
}
.mcp-toggle input:checked + div::after {
transform: translateX(16px);
}
.mcp-toggle input:focus + div {
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
/* MCP Projects List */
.mcp-projects-list {
max-height: 400px;
overflow-y: auto;
}
.mcp-project-item {
transition: background 0.15s;
}
.mcp-project-item:hover {
background: hsl(var(--hover));
}
.mcp-project-item.bg-primary-light {
background: hsl(var(--primary-light));
}
/* MCP Empty State */
.mcp-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 120px;
}
/* MCP Server Details */
.mcp-server-details {
font-size: 0.875rem;
}
.mcp-server-details .font-mono {
font-family: var(--font-mono);
}
/* MCP Projects Table */
.mcp-projects-table {
overflow-x: auto;
}
.mcp-projects-table table {
border-collapse: collapse;
min-width: 100%;
}
.mcp-projects-table th {
white-space: nowrap;
}
.mcp-projects-table td {
vertical-align: middle;
}
.mcp-projects-table tr:hover {
background-color: hsl(var(--hover));
}
.mcp-projects-table .bg-primary-light\/30 {
background-color: hsl(var(--primary) / 0.08);
}
/* MCP Create Modal */
.mcp-modal {
animation: fadeIn 0.15s ease-out;
}
.mcp-modal-backdrop {
animation: fadeIn 0.15s ease-out;
}
.mcp-modal-content {
animation: slideUp 0.2s ease-out;
}
.mcp-modal.hidden {
display: none;
}
.mcp-modal .form-group label {
display: block;
margin-bottom: 0.25rem;
}
.mcp-modal input,
.mcp-modal textarea {
transition: border-color 0.15s, box-shadow 0.15s;
}
.mcp-modal input:focus,
.mcp-modal textarea:focus {
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
/* MCP Tab Buttons */
.mcp-tab-btn {
cursor: pointer;
transition: all 0.2s;
color: hsl(var(--muted-foreground));
}
.mcp-tab-btn.active {
background: hsl(var(--background));
color: hsl(var(--foreground));
font-weight: 500;
}
.mcp-tab-btn:hover:not(.active) {
color: hsl(var(--foreground));
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ==========================================
HOOK MANAGER STYLES
========================================== */
.hook-manager {
width: 100%;
}
.hook-section {
margin-bottom: 2rem;
width: 100%;
}
.hook-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem;
width: 100%;
}
.hook-card {
position: relative;
}
.hook-details {
font-size: 0.875rem;
}
.hook-details .font-mono {
font-family: var(--font-mono);
}
.hook-templates-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.hook-template-card {
transition: all 0.2s ease;
}
.hook-template-card:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
}
/* Hook Empty State */
.hook-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 120px;
}
/* Hook Modal */
.hook-modal {
animation: fadeIn 0.15s ease-out;
}
.hook-modal-backdrop {
animation: fadeIn 0.15s ease-out;
}
.hook-modal-content {
animation: slideUp 0.2s ease-out;
}
.hook-modal.hidden {
display: none;
}
.hook-modal .form-group label {
display: block;
margin-bottom: 0.25rem;
}
.hook-modal input,
.hook-modal textarea,
.hook-modal select {
transition: border-color 0.15s, box-shadow 0.15s;
}
.hook-modal input:focus,
.hook-modal textarea:focus,
.hook-modal select:focus {
border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
}
.hook-template-btn {
transition: all 0.15s ease;
}
.hook-template-btn:hover {
background: hsl(var(--hover));
border-color: hsl(var(--primary));
}
/* ==========================================
STATS SECTION & CAROUSEL
========================================== */
.stats-section {
min-height: 180px;
}
.stats-metrics {
width: 300px;
}
.stats-carousel {
position: relative;
}
.carousel-header {
height: 40px;
}
.carousel-btn {
transition: all 0.15s;
}
.carousel-btn:hover {
background: hsl(var(--hover));
}
/* Carousel dots indicator */
.carousel-dots {
display: flex;
align-items: center;
gap: 6px;
}
.carousel-dot {
cursor: pointer;
border: none;
padding: 0;
transition: all 0.2s ease;
}
.carousel-dot:focus {
outline: none;
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.3);
}
.carousel-footer {
flex-shrink: 0;
}
.carousel-content {
position: relative;
overflow: hidden;
}
.carousel-slide {
position: absolute;
inset: 0;
}
.carousel-empty {
position: absolute;
inset: 0;
}
/* Carousel slide animations */
.carousel-fade-in {
animation: carouselFadeIn 0.3s ease-out forwards;
}
.carousel-slide-left {
animation: carouselSlideLeft 0.35s ease-out forwards;
}
.carousel-slide-right {
animation: carouselSlideRight 0.35s ease-out forwards;
}
@keyframes carouselFadeIn {
from {
opacity: 0;
transform: scale(0.98);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes carouselSlideLeft {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes carouselSlideRight {
from {
opacity: 0;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Task card in carousel */
.carousel-slide .task-card {
height: 100%;
display: flex;
flex-direction: column;
}
.carousel-slide .task-timestamps {
flex-grow: 1;
}
.carousel-slide .task-session-info {
margin-top: auto;
}
/* Task status badge pulse for in_progress */
.task-status-badge {
transition: all 0.2s;
}
.bg-warning-light .task-status-badge {
animation: statusPulse 2s ease-in-out infinite;
}
@keyframes statusPulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* Task highlight animation when navigating from carousel */
.task-highlight {
animation: taskHighlight 0.5s ease-out 3;
}
@keyframes taskHighlight {
0%, 100% {
background: transparent;
box-shadow: none;
}
50% {
background: hsl(var(--primary-light));
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.3);
}
}
/* Line clamp utility */
.line-clamp-1 {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Highlight pulse effect */
.highlight-pulse {
animation: highlightPulse 0.5s ease-out 2;
}
@keyframes highlightPulse {
0%, 100% {
box-shadow: none;
}
50% {
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.3);
}
}
/* ==========================================
NOTIFICATION BUBBLES
========================================== */
.notification-bubble {
position: fixed;
top: 70px;
right: 20px;
max-width: 360px;
padding: 12px 16px;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 8px;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
z-index: 1000;
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease-out;
}
.notification-bubble.show {
opacity: 1;
transform: translateX(0);
}
.notification-bubble.fade-out {
opacity: 0;
transform: translateX(100%);
}
.notification-bubble:nth-child(2) {
top: 130px;
}
.notification-bubble:nth-child(3) {
top: 190px;
}
.notification-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.notification-icon {
font-size: 1.25rem;
}
.notification-message {
font-size: 0.875rem;
color: hsl(var(--foreground));
flex: 1;
}
.notification-action {
padding: 4px 12px;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border: none;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
}
.notification-action:hover {
opacity: 0.9;
}
.notification-close {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: hsl(var(--muted-foreground));
font-size: 1.25rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.15s;
}
.notification-close:hover {
background: hsl(var(--hover));
color: hsl(var(--foreground));
}
/* Notification types */
.notification-success {
border-left: 3px solid hsl(var(--success));
}
.notification-warning {
border-left: 3px solid hsl(var(--warning));
}
.notification-error {
border-left: 3px solid hsl(var(--destructive));
}
.notification-info {
border-left: 3px solid hsl(var(--primary));
}
/* Responsive stats section */
@media (max-width: 768px) {
.stats-section {
flex-direction: column;
}
.stats-metrics {
width: 100%;
grid-template-columns: repeat(4, 1fr);
}
.stats-carousel {
min-height: 160px;
}
}
/* Exploration Object Rendering - Theme Compatible */
.exp-object {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background: hsl(var(--muted));
border-radius: 8px;
border: 1px solid hsl(var(--border));
}
.exp-obj-field {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: flex-start;
font-size: 13px;
line-height: 1.5;
}
.exp-obj-key {
font-weight: 600;
color: hsl(var(--muted-foreground));
min-width: 120px;
}
.exp-obj-val {
color: hsl(var(--foreground));
flex: 1;
}
.exp-obj-nested {
margin-left: 16px;
padding: 8px;
border-left: 2px solid hsl(var(--border));
}
.exp-obj-nested > .exp-obj-key {
display: block;
margin-bottom: 8px;
color: hsl(var(--primary));
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.exp-list {
margin: 4px 0 0 0;
padding-left: 20px;
list-style: disc;
}
.exp-list li {
font-size: 13px;
color: hsl(var(--foreground));
line-height: 1.6;
margin-bottom: 4px;
}
.exp-array-objects {
display: flex;
flex-direction: column;
gap: 10px;
}
.exp-object-item {
padding: 10px;
background: hsl(var(--card));
border-radius: 6px;
border: 1px solid hsl(var(--border));
}
.clarification-impact {
font-size: 12px;
color: hsl(var(--muted-foreground));
margin-top: 6px;
}
.priority-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
}
.priority-badge.priority-high {
background: hsl(var(--destructive) / 0.15);
color: hsl(var(--destructive));
}
.priority-badge.priority-medium {
background: hsl(var(--warning-light));
color: hsl(var(--warning));
}
.priority-badge.priority-low {
background: hsl(var(--success-light));
color: hsl(var(--success));
}
/* Conflict Tab Styles - Theme Compatible */
.conflict-tab-content {
padding: 16px;
}
.conflict-tab-header {
margin-bottom: 24px;
}
.conflict-tab-header h3 {
font-size: 18px;
font-weight: 600;
color: hsl(var(--foreground));
margin: 0 0 8px 0;
}
.conflict-meta-info {
display: flex;
gap: 16px;
font-size: 13px;
color: hsl(var(--muted-foreground));
}
.conflict-meta-info strong {
color: hsl(var(--foreground));
}
.conflict-section-title {
font-size: 15px;
font-weight: 600;
color: hsl(var(--foreground));
margin: 24px 0 12px 0;
padding-bottom: 8px;
border-bottom: 1px solid hsl(var(--border));
}
.conflict-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 16px;
}
.conflict-card {
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
border-radius: 12px;
padding: 16px;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.conflict-card:hover {
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.08);
border-color: hsl(var(--primary));
}
.decision-card {
border-left: 3px solid hsl(var(--primary));
}
.resolved-card {
border-left: 3px solid hsl(var(--success));
}
.conflict-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.conflict-card-label {
font-size: 14px;
font-weight: 600;
color: hsl(var(--foreground));
}
.conflict-card-id {
font-size: 13px;
font-weight: 600;
color: hsl(var(--primary));
font-family: monospace;
}
.conflict-category-tag {
padding: 3px 8px;
background: hsl(var(--muted));
color: hsl(var(--muted-foreground));
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.conflict-card-choice {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding: 10px 12px;
background: hsl(var(--primary-light));
border-radius: 8px;
}
.choice-label {
font-size: 12px;
color: hsl(var(--muted-foreground));
}
.choice-value {
font-size: 14px;
font-weight: 600;
color: hsl(var(--primary));
}
.conflict-card-desc {
font-size: 13px;
color: hsl(var(--muted-foreground));
line-height: 1.5;
margin-bottom: 10px;
}
.conflict-card-brief {
font-size: 13px;
color: hsl(var(--foreground));
line-height: 1.5;
margin-bottom: 12px;
}
.conflict-card-implications {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid hsl(var(--border));
}
.conflict-card-implications .impl-label {
display: block;
font-size: 11px;
font-weight: 600;
color: hsl(var(--muted-foreground));
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 6px;
}
.conflict-card-implications ul {
margin: 0;
padding-left: 18px;
}
.conflict-card-implications li {
font-size: 12px;
color: hsl(var(--muted-foreground));
line-height: 1.5;
margin-bottom: 4px;
}
.conflict-card-strategy {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid hsl(var(--border));
}
.conflict-card-strategy .strategy-label {
font-size: 12px;
color: hsl(var(--muted-foreground));
}
.strategy-tag {
padding: 4px 10px;
background: hsl(var(--success-light));
color: hsl(var(--success));
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -190,7 +190,7 @@
.task-detail-drawer.open { transform: translateX(0); }
.drawer-overlay.active { display: block; }
/* Injected from dashboard.css */
/* Injected from dashboard-css/*.css modules */
{{CSS_CONTENT}}
</style>
</head>

View File

@@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
background: 'hsl(var(--color-background))',
foreground: 'hsl(var(--color-foreground))',
card: 'hsl(var(--color-card))',
border: 'hsl(var(--color-border))',
},
},
},
}
</script>
<style>:root{--color-background:0 0% 100%;--color-foreground:0 0% 25%;}</style>
</head>
<body>
<div class="flex flex-col min-h-screen bg-background text-foreground">
<header class="flex items-center justify-between px-5 h-14 bg-card border-b border-border sticky top-0 z-50">
<div class="flex items-center gap-2">
<span class="text-xl"></span>
<span class="font-semibold">Claude Code Workflow</span>
</div>
</header>
<main class="flex-1 p-6">
<h1>Dashboard</h1>
</main>
</div>
<script>{{JS_CONTENT}}</script>
</body>
</html>

View File

@@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
background: 'hsl(var(--color-background))',
foreground: 'hsl(var(--color-foreground))',
card: 'hsl(var(--color-card))',
border: 'hsl(var(--color-border))',
input: 'hsl(var(--color-input))',
ring: 'hsl(var(--color-ring))',
primary: 'hsl(var(--color-interactive-primary-default))',
accent: 'hsl(var(--color-interactive-accent-default))',
muted: 'hsl(var(--color-muted))',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
}
</script>
<style>:root{--color-background:0 0% 100%;--color-foreground:0 0% 25%;--color-card:0 0% 100%;--color-border:0 0% 90%;--color-input:0 0% 90%;--color-ring:220 65% 50%;--color-interactive-primary-default:220 65% 50%;--color-interactive-accent-default:220 40% 95%;--color-muted:0 0% 97%;--color-muted-foreground:0 0% 50%;--color-sidebar-background:0 0% 97.5%;}[data-theme="dark"]{--color-background:0 0% 10%;--color-foreground:0 0% 90%;--color-card:0 0% 15%;--color-border:0 0% 25%;}</style>
</head>
<body>Test</body>
</html>

View File

@@ -1,212 +0,0 @@
/* Tailwind Base Styles with Design Tokens */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* CSS Custom Properties - Light Mode (Default) */
:root {
/* Base Colors */
--color-background: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-card: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-card-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-border: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
--color-input: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
--color-ring: 220 65% 50%; /* oklch(0.5 0.15 250) -> primary blue */
/* Interactive Colors - Primary */
--color-interactive-primary-default: 220 65% 50%; /* oklch(0.5 0.15 250) -> #4066bf */
--color-interactive-primary-hover: 220 65% 55%; /* oklch(0.55 0.15 250) -> lighter blue */
--color-interactive-primary-active: 220 65% 45%; /* oklch(0.45 0.15 250) -> darker blue */
--color-interactive-primary-disabled: 220 30% 70%; /* oklch(0.7 0.05 250) -> muted blue */
--color-interactive-primary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Interactive Colors - Secondary */
--color-interactive-secondary-default: 220 60% 65%; /* oklch(0.65 0.12 250) -> #6b8ccc */
--color-interactive-secondary-hover: 220 60% 70%; /* oklch(0.7 0.12 250) -> lighter */
--color-interactive-secondary-active: 220 60% 60%; /* oklch(0.6 0.12 250) -> darker */
--color-interactive-secondary-disabled: 220 30% 80%; /* oklch(0.8 0.05 250) -> muted */
--color-interactive-secondary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Interactive Colors - Accent */
--color-interactive-accent-default: 220 40% 95%; /* oklch(0.95 0.02 250) -> #eef3fa */
--color-interactive-accent-hover: 220 45% 92%; /* oklch(0.92 0.03 250) -> slightly darker */
--color-interactive-accent-active: 220 35% 97%; /* oklch(0.97 0.02 250) -> slightly lighter */
--color-interactive-accent-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
/* Interactive Colors - Destructive */
--color-interactive-destructive-default: 8 75% 55%; /* oklch(0.55 0.22 25) -> #d93025 */
--color-interactive-destructive-hover: 8 75% 60%; /* oklch(0.6 0.22 25) -> lighter red */
--color-interactive-destructive-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Semantic Colors */
--color-muted: 0 0% 97%; /* oklch(0.97 0 0) -> very light gray */
--color-muted-foreground: 0 0% 50%; /* oklch(0.5 0 0) -> medium gray */
/* Sidebar Colors */
--color-sidebar-background: 0 0% 97.5%; /* oklch(0.975 0 0) -> #f8f8f8 */
--color-sidebar-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-sidebar-primary: 220 65% 50%; /* oklch(0.5 0.15 250) -> primary blue */
--color-sidebar-primary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-sidebar-accent: 220 40% 95%; /* oklch(0.95 0.02 250) -> light blue */
--color-sidebar-accent-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-sidebar-border: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
/* Typography */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'Consolas', 'Monaco', 'Courier New', monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
--letter-spacing-tight: -0.025em;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.025em;
/* Spacing */
--spacing-0: 0;
--spacing-1: 0.25rem; /* 4px */
--spacing-2: 0.5rem; /* 8px */
--spacing-3: 0.75rem; /* 12px */
--spacing-4: 1rem; /* 16px */
--spacing-6: 1.5rem; /* 24px */
--spacing-8: 2rem; /* 32px */
--spacing-12: 3rem; /* 48px */
--spacing-16: 4rem; /* 64px */
/* Effects */
--opacity-disabled: 0.5;
--opacity-hover: 0.8;
--opacity-active: 1;
/* Shadows */
--shadow-2xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-xs: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-sm: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
/* Border Radius */
--border-radius-sm: calc(0.375rem - 4px); /* 2px */
--border-radius-md: calc(0.375rem - 2px); /* 4px */
--border-radius-lg: 0.375rem; /* 6px */
--border-radius-xl: calc(0.375rem + 4px); /* 10px */
--border-radius-default: 0.375rem; /* 6px */
/* Animations */
--duration-instant: 0ms;
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-medium: 300ms;
--duration-slow: 500ms;
--easing-linear: linear;
--easing-ease-in: cubic-bezier(0.4, 0, 1, 1);
--easing-ease-out: cubic-bezier(0, 0, 0.2, 1);
--easing-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--easing-spring: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Dark Mode Theme */
[data-theme="dark"] {
/* Base Colors - Dark Mode */
--color-background: 0 0% 10%; /* Dark background */
--color-foreground: 0 0% 90%; /* Light text */
--color-card: 0 0% 15%; /* Dark card background */
--color-card-foreground: 0 0% 90%; /* Light card text */
--color-border: 0 0% 25%; /* Dark border */
--color-input: 0 0% 25%; /* Dark input border */
--color-ring: 220 65% 60%; /* Brighter ring for dark mode */
/* Interactive Colors - Primary (Dark Mode) */
--color-interactive-primary-default: 220 70% 60%; /* Brighter blue for dark mode */
--color-interactive-primary-hover: 220 70% 65%; /* Even brighter on hover */
--color-interactive-primary-active: 220 70% 55%; /* Slightly darker on active */
--color-interactive-primary-disabled: 220 30% 40%; /* Muted blue for dark mode */
--color-interactive-primary-foreground: 0 0% 100%; /* White text */
/* Interactive Colors - Secondary (Dark Mode) */
--color-interactive-secondary-default: 220 60% 70%; /* Brighter secondary */
--color-interactive-secondary-hover: 220 60% 75%; /* Brighter on hover */
--color-interactive-secondary-active: 220 60% 65%; /* Slightly darker on active */
--color-interactive-secondary-disabled: 220 30% 50%; /* Muted */
--color-interactive-secondary-foreground: 0 0% 100%; /* White text */
/* Interactive Colors - Accent (Dark Mode) */
--color-interactive-accent-default: 220 30% 25%; /* Dark accent */
--color-interactive-accent-hover: 220 35% 30%; /* Slightly lighter on hover */
--color-interactive-accent-active: 220 25% 20%; /* Darker on active */
--color-interactive-accent-foreground: 0 0% 90%; /* Light text */
/* Interactive Colors - Destructive (Dark Mode) */
--color-interactive-destructive-default: 8 75% 60%; /* Brighter red for visibility */
--color-interactive-destructive-hover: 8 75% 65%; /* Even brighter on hover */
--color-interactive-destructive-foreground: 0 0% 100%; /* White text */
/* Semantic Colors (Dark Mode) */
--color-muted: 0 0% 20%; /* Dark muted background */
--color-muted-foreground: 0 0% 60%; /* Lighter muted text */
/* Sidebar Colors (Dark Mode) */
--color-sidebar-background: 0 0% 12%; /* Slightly lighter than background */
--color-sidebar-foreground: 0 0% 90%; /* Light text */
--color-sidebar-primary: 220 70% 60%; /* Brighter blue */
--color-sidebar-primary-foreground: 0 0% 100%; /* White text */
--color-sidebar-accent: 220 30% 25%; /* Dark accent */
--color-sidebar-accent-foreground: 0 0% 90%; /* Light text */
--color-sidebar-border: 0 0% 25%; /* Dark border */
}
/* Base typography */
* {
box-sizing: border-box;
}
body {
@apply bg-background text-foreground font-sans leading-normal;
margin: 0;
padding: 0;
}
/* Focus styles */
*:focus-visible {
outline: 2px solid hsl(var(--color-ring));
outline-offset: 2px;
}
}
@layer utilities {
/* Custom utility classes */
.text-balance {
text-wrap: balance;
}
.transition-default {
transition: all var(--duration-normal) var(--easing-ease-out);
}
.transition-fast {
transition: all var(--duration-fast) var(--easing-ease-out);
}
.transition-medium {
transition: all var(--duration-medium) var(--easing-ease-in-out);
}
.transition-slow {
transition: all var(--duration-slow) var(--easing-ease-in-out);
}
}

View File

@@ -46,7 +46,7 @@ function writeFile(filePath, content) {
* Auto-adapts line endings (CRLF/LF)
*/
function executeUpdateMode(content, params) {
const { oldText, newText } = params;
const { oldText, newText, replaceAll } = params;
if (!oldText) throw new Error('Parameter "oldText" is required for update mode');
if (newText === undefined) throw new Error('Parameter "newText" is required for update mode');
@@ -62,10 +62,19 @@ function executeUpdateMode(content, params) {
let newContent = normalizedContent;
let status = 'not found';
let replacements = 0;
if (newContent.includes(normalizedOld)) {
newContent = newContent.replace(normalizedOld, normalizedNew);
status = 'replaced';
if (replaceAll) {
const parts = newContent.split(normalizedOld);
replacements = parts.length - 1;
newContent = parts.join(normalizedNew);
status = 'replaced_all';
} else {
newContent = newContent.replace(normalizedOld, normalizedNew);
status = 'replaced';
replacements = 1;
}
}
// Restore original line ending
@@ -77,7 +86,13 @@ function executeUpdateMode(content, params) {
content: newContent,
modified: content !== newContent,
status,
message: status === 'replaced' ? 'Text replaced successfully' : 'oldText not found in file'
replacements,
message:
status === 'replaced_all'
? `Text replaced successfully (${replacements} occurrences)`
: status === 'replaced'
? 'Text replaced successfully'
: 'oldText not found in file'
};
}
@@ -91,7 +106,11 @@ function executeLineMode(content, params) {
if (!operation) throw new Error('Parameter "operation" is required for line mode');
if (line === undefined) throw new Error('Parameter "line" is required for line mode');
const lines = content.split('\n');
// Detect original line ending and normalize for processing
const hasCRLF = content.includes('\r\n');
const normalizedContent = hasCRLF ? content.replace(/\r\n/g, '\n') : content;
const lines = normalizedContent.split('\n');
const lineIndex = line - 1; // Convert to 0-based
if (lineIndex < 0 || lineIndex >= lines.length) {
@@ -139,7 +158,12 @@ function executeLineMode(content, params) {
throw new Error(`Unknown operation: ${operation}. Valid: insert_before, insert_after, replace, delete`);
}
const newContent = newLines.join('\n');
let newContent = newLines.join('\n');
// Restore original line endings
if (hasCRLF) {
newContent = newContent.replace(/\n/g, '\r\n');
}
return {
content: newContent,
@@ -213,6 +237,10 @@ export const editFileTool = {
type: 'string',
description: '[update mode] Replacement text'
},
replaceAll: {
type: 'boolean',
description: '[update mode] Replace all occurrences of oldText (default: false)'
},
// Line mode params
operation: {
type: 'string',

View File

@@ -1,4 +1,5 @@
{
"name": "ccw-test"
"name": "claude-code-workflow",
"version": "6.0.5",
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",