Files
Claude-Code-Workflow/ccw/src/templates/dashboard.html
catlog22 894b93e08d feat(graph-explorer): implement interactive code relationship visualization with Cytoscape.js
- Added main render function to initialize the graph explorer view.
- Implemented data loading functions for graph nodes, edges, and search process data.
- Created UI layout with tabs for graph view and search process view.
- Developed filtering options for nodes and edges with corresponding UI elements.
- Integrated Cytoscape.js for graph visualization, including node and edge styling.
- Added functionality for node selection and details display.
- Implemented impact analysis feature with modal display for results.
- Included utility functions for refreshing data and managing UI states.
2025-12-15 19:35:18 +08:00

827 lines
50 KiB
HTML

<!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>
<!-- Tailwind CSS (本地) -->
<script src="./assets/js/tailwind.js"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
safelist: [
// Background colors
'bg-card', 'bg-background', 'bg-hover', 'bg-accent', 'bg-muted', 'bg-primary', 'bg-success', 'bg-warning',
'bg-success-light', 'bg-warning-light', 'bg-primary-light', 'bg-sidebar-background', 'bg-destructive',
'bg-destructive/5', 'bg-destructive/10', 'bg-warning/5',
'bg-info', 'bg-info-light', 'bg-indigo', 'bg-indigo-light', 'bg-orange', 'bg-orange-light',
// Text colors
'text-foreground', 'text-muted-foreground', 'text-primary', 'text-card-foreground', 'text-success', 'text-warning',
'text-primary-foreground', 'text-accent-foreground', 'text-sidebar-foreground', 'text-destructive',
'text-info', 'text-indigo', 'text-orange',
// Border colors
'border', 'border-border', 'border-primary', 'border-success', 'border-warning', 'border-muted',
'border-l-success', 'border-l-warning', 'border-l-muted-foreground', 'border-l-primary',
// Layout
'rounded', 'rounded-lg', 'rounded-md', 'rounded-full', 'shadow', 'shadow-sm', 'shadow-md', 'shadow-lg',
'p-2', 'p-3', 'p-4', 'p-5', 'px-3', 'px-4', 'px-5', 'py-2', 'py-3', 'py-4',
'm-2', 'mb-2', 'mb-3', 'mb-4', 'mt-2', 'mt-3', 'mt-4', 'mx-2', 'my-2',
'gap-2', 'gap-3', 'gap-4', 'space-y-2', 'space-y-3',
// Flex & Grid
'flex', 'flex-1', 'flex-col', 'flex-wrap', 'items-center', 'items-start', 'justify-between', 'justify-center',
'grid', 'grid-cols-1', 'grid-cols-2', 'grid-cols-3',
// Sizing
'w-full', 'w-5', 'w-8', 'h-2', 'h-5', 'h-8', 'min-w-0', 'max-w-full',
// Text
'text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl',
'font-medium', 'font-semibold', 'font-bold', 'font-mono', 'truncate', 'uppercase',
// States & Transitions
'hover:shadow-md', 'hover:bg-hover', 'hover:-translate-y-1', 'hover:text-foreground',
'transition-all', 'duration-200', 'duration-300', 'cursor-pointer',
// Opacity & visibility
'opacity-50', 'opacity-80', 'hidden', 'block', 'inline', 'inline-flex',
// Position
'relative', 'absolute', 'fixed', 'sticky', 'top-0', 'right-0', 'left-0', 'bottom-0',
'z-10', 'z-40', 'z-50', 'overflow-hidden', 'overflow-y-auto',
],
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: 'hsl(var(--card))',
'card-foreground': 'hsl(var(--card-foreground))',
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
primary: 'hsl(var(--primary))',
'primary-foreground': 'hsl(var(--primary-foreground))',
'primary-light': 'hsl(var(--primary-light))',
secondary: 'hsl(var(--secondary))',
'secondary-foreground': 'hsl(var(--secondary-foreground))',
accent: 'hsl(var(--accent))',
'accent-foreground': 'hsl(var(--accent-foreground))',
destructive: 'hsl(var(--destructive))',
'destructive-foreground': 'hsl(var(--destructive-foreground))',
muted: 'hsl(var(--muted))',
'muted-foreground': 'hsl(var(--muted-foreground))',
'sidebar-background': 'hsl(var(--sidebar-background))',
'sidebar-foreground': 'hsl(var(--sidebar-foreground))',
hover: 'hsl(var(--hover))',
success: 'hsl(var(--success))',
'success-light': 'hsl(var(--success-light))',
warning: 'hsl(var(--warning))',
'warning-light': 'hsl(var(--warning-light))',
info: 'hsl(var(--info))',
'info-light': 'hsl(var(--info-light))',
indigo: 'hsl(var(--indigo))',
'indigo-light': 'hsl(var(--indigo-light))',
orange: 'hsl(var(--orange))',
'orange-light': 'hsl(var(--orange-light))',
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['Consolas', 'Monaco', 'Courier New', 'monospace'],
},
boxShadow: {
'sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
'DEFAULT': '0 2px 8px rgb(0 0 0 / 0.08)',
'md': '0 4px 12px rgb(0 0 0 / 0.1)',
'lg': '0 8px 24px rgb(0 0 0 / 0.12)',
},
},
},
}
</script>
<style>
/* CSS Custom Properties - Light Mode */
:root {
--background: 0 0% 98%;
--foreground: 0 0% 13%;
--card: 0 0% 100%;
--card-foreground: 0 0% 13%;
--border: 0 0% 90%;
--input: 0 0% 90%;
--ring: 220 65% 50%;
--primary: 220 65% 50%;
--primary-foreground: 0 0% 100%;
--primary-light: 220 65% 95%;
--secondary: 220 60% 65%;
--secondary-foreground: 0 0% 100%;
--accent: 220 40% 95%;
--accent-foreground: 0 0% 13%;
--destructive: 8 75% 55%;
--destructive-foreground: 0 0% 100%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 45%;
--sidebar-background: 0 0% 97%;
--sidebar-foreground: 0 0% 13%;
--hover: 0 0% 93%;
--success: 142 71% 45%;
--success-light: 142 76% 90%;
--warning: 38 92% 50%;
--warning-light: 48 96% 89%;
--info: 210 80% 55%;
--info-light: 210 80% 92%;
--indigo: 239 65% 60%;
--indigo-light: 239 65% 92%;
--orange: 25 90% 55%;
--orange-light: 25 90% 92%;
}
/* Dark Mode */
[data-theme="dark"] {
--background: 220 13% 10%;
--foreground: 0 0% 90%;
--card: 220 13% 14%;
--card-foreground: 0 0% 90%;
--border: 220 13% 20%;
--input: 220 13% 20%;
--ring: 220 65% 55%;
--primary: 220 65% 55%;
--primary-foreground: 0 0% 100%;
--primary-light: 220 50% 25%;
--secondary: 220 60% 60%;
--secondary-foreground: 0 0% 100%;
--accent: 220 30% 20%;
--accent-foreground: 0 0% 90%;
--destructive: 8 70% 50%;
--destructive-foreground: 0 0% 100%;
--muted: 220 13% 18%;
--muted-foreground: 0 0% 55%;
--sidebar-background: 220 13% 12%;
--sidebar-foreground: 0 0% 90%;
--hover: 220 13% 22%;
--success: 142 71% 40%;
--success-light: 142 50% 20%;
--warning: 38 85% 45%;
--warning-light: 40 50% 20%;
--info: 210 75% 50%;
--info-light: 210 50% 20%;
--indigo: 239 60% 55%;
--indigo-light: 239 40% 20%;
--orange: 25 85% 50%;
--orange-light: 25 50% 20%;
}
/* Scrollbar styling */
::-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 */
.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; }
.sidebar.collapsed .toggle-icon { transform: rotate(180deg); }
/* Path menu open state */
.path-menu.open { display: block; }
/* Mobile sidebar */
@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.open { display: block; }
.menu-toggle-btn { display: block !important; }
}
/* Task drawer */
.task-detail-drawer {
transform: translateX(100%);
transition: transform 0.3s ease;
}
.task-detail-drawer.open { transform: translateX(0); }
.drawer-overlay.active { display: block; }
/* Unified Icon System */
.nav-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
stroke-width: 2;
}
.nav-section-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
stroke-width: 2;
opacity: 0.8;
}
.sidebar.collapsed .nav-icon {
width: 20px;
height: 20px;
}
/* Icon Animations */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* Injected from dashboard-css/*.css modules */
{{CSS_CONTENT}}
</style>
</head>
<body class="font-sans bg-background text-foreground leading-normal">
<div class="flex flex-col min-h-screen">
<!-- Top Bar -->
<header class="flex items-center justify-between px-5 h-14 bg-card border-b border-border sticky top-0 z-50 shadow-sm">
<div class="flex items-center gap-4">
<button class="hidden md:hidden p-2 text-foreground hover:bg-hover rounded menu-toggle-btn" id="menuToggle">
<i data-lucide="menu" class="w-5 h-5"></i>
</button>
<div class="flex items-center gap-2 text-lg font-semibold text-primary">
<i data-lucide="workflow" class="w-6 h-6"></i>
<span class="hidden sm:inline">Claude Code Workflow</span>
</div>
</div>
<!-- Right Side Actions -->
<div class="flex items-center gap-3">
<!-- Path Selector -->
<div class="flex items-center gap-2 relative">
<label class="hidden sm:inline text-sm text-muted-foreground" data-i18n="header.project">Project:</label>
<div class="relative">
<button class="flex items-center gap-2 px-3 py-1.5 bg-background border border-border rounded text-sm text-foreground hover:bg-hover max-w-[300px]" id="pathButton">
<span class="truncate max-w-[240px]" id="currentPath">{{PROJECT_PATH}}</span>
<span class="text-xs text-muted-foreground"></span>
</button>
<div class="path-menu hidden absolute top-full right-0 mt-1 bg-card border border-border rounded-lg shadow-lg min-w-[280px] z-50" id="pathMenu">
<div class="px-3 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide" data-i18n="header.recentProjects">Recent Projects</div>
<div id="recentPaths" class="border-t border-border">
<!-- Dynamic recent paths -->
</div>
<div class="p-2 border-t border-border">
<button class="w-full flex items-center justify-center gap-2 px-3 py-2 bg-background border border-border rounded text-sm text-muted-foreground hover:bg-hover" id="browsePath">
<i data-lucide="folder-open" class="w-4 h-4"></i>
<span data-i18n="header.browse">Browse...</span>
</button>
</div>
</div>
</div>
<!-- Refresh Button -->
<button class="refresh-btn p-1.5 text-muted-foreground hover:text-foreground hover:bg-hover rounded" id="refreshWorkspace" data-i18n-title="header.refreshWorkspace" title="Refresh workspace">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5"/>
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
<path d="M16 21h5v-5"/>
</svg>
</button>
</div>
<!-- Language Toggle -->
<button class="p-2 hover:bg-hover rounded flex items-center justify-center text-sm font-medium min-w-[40px]" id="langToggle" data-i18n-title="header.language" title="Language" onclick="switchLang(currentLang === 'zh' ? 'en' : 'zh')">
EN
</button>
<!-- Theme Toggle -->
<button class="p-2 hover:bg-hover rounded flex items-center justify-center" id="themeToggle" data-i18n-title="header.toggleTheme" title="Toggle theme">
<i data-lucide="moon" class="w-5 h-5 theme-icon-dark"></i>
<i data-lucide="sun" class="w-5 h-5 theme-icon-light hidden"></i>
</button>
</div>
</header>
<!-- Sidebar Overlay (mobile) -->
<div class="sidebar-overlay hidden fixed inset-0 bg-black/50 z-40" id="sidebarOverlay"></div>
<!-- Main Layout -->
<div class="flex flex-1">
<!-- Sidebar -->
<aside class="sidebar w-64 bg-sidebar-background border-r border-border flex flex-col sticky top-14 h-[calc(100vh-56px)] overflow-y-auto transition-all duration-300" id="sidebar">
<nav class="flex-1 py-3">
<!-- Project Overview Section -->
<div class="mb-2" id="projectOverviewNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="layout-dashboard" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.project">Project</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="project-overview" data-tooltip="Project Overview">
<i data-lucide="bar-chart-3" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.overview">Overview</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="explorer" data-tooltip="File Explorer">
<i data-lucide="folder-tree" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.explorer">Explorer</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="cli-manager" data-tooltip="CLI Tools Status">
<i data-lucide="terminal" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.status">Status</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeCliTools">0/3</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="cli-history" data-tooltip="CLI Execution History">
<i data-lucide="history" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.history">History</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="graph-explorer" data-tooltip="Code Graph Explorer">
<i data-lucide="git-branch" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.graphExplorer">Graph</span>
</li>
</ul>
</div>
<!-- Sessions Section -->
<div class="mb-2">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="history" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.sessions">Sessions</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors active" data-filter="all" data-tooltip="All Sessions">
<i data-lucide="list" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.all">All</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeAll">0</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-filter="active" data-tooltip="Active Sessions">
<i data-lucide="play-circle" class="nav-icon text-success"></i>
<span class="nav-text flex-1" data-i18n="nav.active">Active</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-success-light text-success" id="badgeActive">0</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-filter="archived" data-tooltip="Archived Sessions">
<i data-lucide="archive" class="nav-icon text-info"></i>
<span class="nav-text flex-1" data-i18n="nav.archived">Archived</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-info-light text-info" id="badgeArchived">0</span>
</li>
</ul>
</div>
<!-- Lite Tasks Section -->
<div class="mb-2" id="liteTasksNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="zap" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.liteTasks">Lite Tasks</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-lite="lite-plan" data-tooltip="Lite Plan Sessions">
<i data-lucide="file-edit" class="nav-icon text-indigo"></i>
<span class="nav-text flex-1" data-i18n="nav.litePlan">Lite Plan</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-indigo-light text-indigo" id="badgeLitePlan">0</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-lite="lite-fix" data-tooltip="Lite Fix Sessions">
<i data-lucide="wrench" class="nav-icon text-orange"></i>
<span class="nav-text flex-1" data-i18n="nav.liteFix">Lite Fix</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-orange-light text-orange" id="badgeLiteFix">0</span>
</li>
</ul>
</div>
<!-- MCP Servers Section -->
<div class="mb-2" id="mcpServersNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="plug" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.mcpServers">MCP Servers</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="mcp-manager" data-tooltip="MCP Server Management">
<i data-lucide="settings" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.manage">Manage</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeMcpServers">0</span>
</li>
</ul>
</div>
<!-- Hooks Section -->
<div class="mb-2" id="hooksNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="webhook" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.hooks">Hooks</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="hook-manager" data-tooltip="Hook Management">
<i data-lucide="cable" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.manage">Manage</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeHooks">0</span>
</li>
</ul>
</div>
<!-- Memory Section -->
<div class="mb-2" id="memoryNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<i data-lucide="brain" class="nav-section-icon mr-2"></i>
<span class="nav-section-title" data-i18n="nav.memory">Memory</span>
</div>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="memory" data-tooltip="Context Memory">
<i data-lucide="database" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.contextMemory">Context</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="prompt-history" data-tooltip="Prompt History">
<i data-lucide="message-square" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.promptHistory">Prompts</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="skills-manager" data-tooltip="Skills Management">
<i data-lucide="sparkles" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.skills">Skills</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeSkills">0</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="rules-manager" data-tooltip="Rules Management">
<i data-lucide="book-open" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.rules">Rules</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeRules">0</span>
</li>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="claude-manager" data-tooltip="CLAUDE.md Manager">
<i data-lucide="file-code" class="nav-icon"></i>
<span class="nav-text flex-1" data-i18n="nav.claudeManager">CLAUDE.md</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeClaude">0</span>
</li>
</ul>
</div>
</nav>
<!-- Sidebar Footer -->
<div class="p-3 border-t border-border">
<button class="flex items-center justify-center gap-2 w-full px-3 py-2 border border-border rounded text-sm text-muted-foreground hover:bg-hover transition-colors" id="sidebarToggle">
<i data-lucide="panel-left-close" class="toggle-icon w-4 h-4 transition-transform duration-300"></i>
<span class="toggle-text" data-i18n="nav.collapse">Collapse</span>
</button>
</div>
</aside>
<!-- Content Area -->
<main class="flex-1 p-6 overflow-y-auto min-w-0">
<!-- Stats Section: Left Metrics + Right Carousel -->
<section id="statsGrid" class="stats-section flex gap-4 mb-6">
<!-- Left: 4 Metrics Grid -->
<div class="stats-metrics grid grid-cols-2 gap-3 shrink-0">
<div class="bg-card border border-border rounded-lg p-4 text-center hover:shadow-md transition-all duration-200 min-w-[140px]">
<div class="flex justify-center mb-1 text-primary"><i data-lucide="layers" class="w-5 h-5"></i></div>
<div class="text-2xl font-bold text-foreground" id="statTotalSessions">0</div>
<div class="text-xs text-muted-foreground mt-1" data-i18n="stats.totalSessions">Total Sessions</div>
</div>
<div class="bg-card border border-border rounded-lg p-4 text-center hover:shadow-md transition-all duration-200 min-w-[140px]">
<div class="flex justify-center mb-1 text-success"><i data-lucide="activity" class="w-5 h-5"></i></div>
<div class="text-2xl font-bold text-foreground" id="statActiveSessions">0</div>
<div class="text-xs text-muted-foreground mt-1" data-i18n="stats.activeSessions">Active Sessions</div>
</div>
<div class="bg-card border border-border rounded-lg p-4 text-center hover:shadow-md transition-all duration-200 min-w-[140px]">
<div class="flex justify-center mb-1 text-primary"><i data-lucide="clipboard-list" class="w-5 h-5"></i></div>
<div class="text-2xl font-bold text-foreground" id="statTotalTasks">0</div>
<div class="text-xs text-muted-foreground mt-1" data-i18n="stats.totalTasks">Total Tasks</div>
</div>
<div class="bg-card border border-border rounded-lg p-4 text-center hover:shadow-md transition-all duration-200 min-w-[140px]">
<div class="flex justify-center mb-1 text-success"><i data-lucide="check-circle-2" class="w-5 h-5"></i></div>
<div class="text-2xl font-bold text-foreground" id="statCompletedTasks">0</div>
<div class="text-xs text-muted-foreground mt-1" data-i18n="stats.completedTasks">Completed Tasks</div>
</div>
</div>
<!-- Right: Active Session Carousel (Image-style with dots) -->
<div class="stats-carousel flex-1 bg-card border border-border rounded-lg overflow-hidden min-h-[180px] flex flex-col relative">
<!-- Carousel Content (Full height) -->
<div class="carousel-content flex-1 relative overflow-hidden" id="carouselContent">
<!-- Dynamic carousel slides -->
<div class="carousel-empty flex items-center justify-center h-full text-muted-foreground">
<div class="text-center">
<div class="flex justify-center mb-2"><i data-lucide="target" class="w-8 h-8"></i></div>
<p class="text-sm" data-i18n="carousel.noActiveSessions">No active sessions</p>
</div>
</div>
</div>
<!-- Bottom: Dots Indicator & Controls -->
<div class="carousel-footer flex items-center justify-center gap-3 py-2 border-t border-border bg-muted/20">
<!-- Previous Button -->
<button class="carousel-btn p-1 rounded hover:bg-hover text-muted-foreground hover:text-foreground" id="carouselPrev" data-i18n-title="carousel.previous" title="Previous">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
</button>
<!-- Dots Indicator -->
<div class="carousel-dots flex items-center gap-1.5" id="carouselDots">
<!-- Dots will be rendered dynamically -->
</div>
<!-- Next Button -->
<button class="carousel-btn p-1 rounded hover:bg-hover text-muted-foreground hover:text-foreground" id="carouselNext" data-i18n-title="carousel.next" title="Next">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
</button>
<!-- Pause Button -->
<button class="carousel-btn p-1 rounded hover:bg-hover text-muted-foreground hover:text-foreground ml-1" id="carouselPause" data-i18n-title="carousel.pause" title="Pause auto-play">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" id="carouselPauseIcon"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
</button>
</div>
</div>
</section>
<!-- Content Header -->
<div class="flex items-center justify-between flex-wrap gap-3 mb-5">
<h2 class="text-xl font-semibold text-foreground" id="contentTitle" data-i18n="title.allSessions">All Sessions</h2>
<div class="relative">
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground"></i>
<input type="text" data-i18n-placeholder="search.placeholder" placeholder="Search..." id="searchInput"
class="pl-9 pr-4 py-2 w-60 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all">
</div>
</div>
<!-- Storage Manager Card (only visible in CLI Manager view) -->
<section id="storageCard" class="mb-6" style="display: none;">
<!-- Rendered by storage-manager.js -->
</section>
<!-- Main Content Container -->
<section class="main-content" id="mainContent">
<!-- Dynamic content: sessions grid or session detail page -->
</section>
</main>
</div>
<!-- Footer -->
<footer class="flex items-center justify-between px-5 h-10 bg-card border-t border-border text-xs text-muted-foreground">
<div><span data-i18n="footer.generated">Generated:</span> <span id="generatedAt">-</span></div>
<div data-i18n="footer.version">CCW Dashboard v1.0</div>
</footer>
<!-- Task Detail Drawer -->
<div class="task-detail-drawer fixed top-0 right-0 w-1/2 max-w-full h-full bg-card border-l border-border shadow-lg z-50 flex flex-col" id="taskDetailDrawer">
<div class="flex items-center justify-between px-5 py-4 border-b border-border">
<h3 class="text-lg font-semibold text-foreground" id="drawerTaskTitle" data-i18n="tab.tasks">Task Details</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeTaskDrawer()">&times;</button>
</div>
<div class="flex-1 overflow-y-auto p-5" id="drawerContent">
<!-- Dynamic content -->
</div>
</div>
<div class="drawer-overlay hidden fixed inset-0 bg-black/50 z-40" id="drawerOverlay" onclick="closeTaskDrawer()"></div>
</div>
<!-- Markdown Preview Modal -->
<div id="markdownModal" class="markdown-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="markdown-modal-backdrop absolute inset-0 bg-black/60" onclick="closeMarkdownModal()"></div>
<div class="markdown-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-4xl h-[85vh] flex flex-col">
<div class="markdown-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<h3 class="text-lg font-semibold text-foreground" id="markdownModalTitle" data-i18n="modal.contentPreview">Content Preview</h3>
<div class="flex items-center gap-2">
<div class="flex bg-muted rounded-lg p-0.5">
<button id="mdTabRaw" class="md-tab-btn px-3 py-1 text-sm rounded-md transition-colors" onclick="switchMarkdownTab('raw')" data-i18n="modal.raw">Raw</button>
<button id="mdTabPreview" class="md-tab-btn px-3 py-1 text-sm rounded-md transition-colors active" onclick="switchMarkdownTab('preview')" data-i18n="modal.preview">Preview</button>
</div>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeMarkdownModal()">&times;</button>
</div>
</div>
<div class="markdown-modal-body flex-1 overflow-auto p-4">
<pre id="markdownRaw" class="hidden whitespace-pre-wrap text-sm font-mono text-foreground bg-muted p-4 rounded-lg overflow-auto h-full"></pre>
<div id="markdownPreview" class="markdown-preview prose prose-sm max-w-none"></div>
</div>
</div>
</div>
<!-- MCP Server Create Modal -->
<div id="mcpCreateModal" class="mcp-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="mcp-modal-backdrop absolute inset-0 bg-black/60" onclick="closeMcpCreateModal()"></div>
<div class="mcp-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-lg flex flex-col">
<div class="mcp-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<div class="flex items-center gap-3">
<h3 class="text-lg font-semibold text-foreground" data-i18n="mcp.createTitle">Create MCP Server</h3>
<div class="flex bg-muted rounded-lg p-0.5">
<button id="mcpTabForm" class="mcp-tab-btn px-3 py-1 text-sm rounded-md transition-colors active" onclick="switchMcpCreateTab('form')" data-i18n="mcp.form">Form</button>
<button id="mcpTabJson" class="mcp-tab-btn px-3 py-1 text-sm rounded-md transition-colors" onclick="switchMcpCreateTab('json')" data-i18n="mcp.json">JSON</button>
</div>
</div>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeMcpCreateModal()">&times;</button>
</div>
<!-- Form Mode -->
<div id="mcpFormMode" class="mcp-modal-body p-4 space-y-4">
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1"><span data-i18n="mcp.serverName">Server Name</span> <span class="text-destructive">*</span></label>
<input type="text" id="mcpServerName" data-i18n-placeholder="mcp.serverNamePlaceholder" placeholder="e.g., my-mcp-server"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1"><span data-i18n="mcp.scope">Scope</span> <span class="text-destructive">*</span></label>
<select id="mcpServerScope" class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
<option value="project" data-i18n="mcp.scopeProject">Project - Only this project</option>
<option value="global" data-i18n="mcp.scopeGlobal">Global - All projects (~/. claude.json)</option>
</select>
<p class="text-xs text-muted-foreground mt-1">Choose where to save this MCP server configuration</p>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1"><span data-i18n="mcp.command">Command</span> <span class="text-destructive">*</span></label>
<input type="text" id="mcpServerCommand" data-i18n-placeholder="mcp.commandPlaceholder" placeholder="e.g., npx, uvx, node, python"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1">Arguments (one per line)</label>
<textarea id="mcpServerArgs" placeholder="e.g.,&#10;-y&#10;@smithery/cli@latest&#10;run&#10;exa" rows="4"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm font-mono focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 resize-none"></textarea>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1" data-i18n="mcp.envVars">Environment Variables (KEY=VALUE per line)</label>
<textarea id="mcpServerEnv" placeholder="e.g.,&#10;API_KEY=your-api-key&#10;DEBUG=true" rows="3"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm font-mono focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 resize-none"></textarea>
</div>
</div>
<!-- JSON Mode -->
<div id="mcpJsonMode" class="mcp-modal-body p-4 space-y-4 hidden">
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1" data-i18n="mcp.pasteJson">Paste MCP Server JSON Configuration</label>
<textarea id="mcpServerJson" placeholder='{
"servers": {
"my-server": {
"command": "npx",
"args": ["-y", "@package/server"],
"env": {
"API_KEY": "your-key"
}
}
}
}' rows="12"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm font-mono focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 resize-none"></textarea>
<p class="text-xs text-muted-foreground mt-2">Supports <code class="bg-muted px-1 rounded">{"servers": {...}}</code>, <code class="bg-muted px-1 rounded">{"mcpServers": {...}}</code>, and direct server config formats.</p>
</div>
<div id="mcpJsonPreview" class="hidden">
<label class="block text-sm font-medium text-foreground mb-2">Preview (servers to be added):</label>
<div id="mcpJsonPreviewContent" class="bg-muted rounded-lg p-3 text-sm space-y-2 max-h-32 overflow-y-auto"></div>
</div>
</div>
<div class="mcp-modal-footer flex justify-end gap-2 px-4 py-3 border-t border-border">
<button class="px-4 py-2 text-sm bg-muted text-foreground rounded-lg hover:bg-hover transition-colors" onclick="closeMcpCreateModal()" data-i18n="common.cancel">Cancel</button>
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity" onclick="submitMcpCreate()" data-i18n="common.create">Create</button>
</div>
</div>
</div>
<!-- Hook Create Modal -->
<div id="hookCreateModal" class="hook-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="hook-modal-backdrop absolute inset-0 bg-black/60" onclick="closeHookCreateModal()"></div>
<div class="hook-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-lg flex flex-col max-h-[90vh]">
<div class="hook-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<h3 class="text-lg font-semibold text-foreground" id="hookModalTitle" data-i18n="hook.createTitle">Create Hook</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeHookCreateModal()">&times;</button>
</div>
<div class="hook-modal-body p-4 space-y-4 overflow-y-auto">
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1"><span data-i18n="hook.event">Hook Event</span> <span class="text-destructive">*</span></label>
<select id="hookEvent" class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
<option value="" data-i18n="hook.selectEvent">Select an event...</option>
<option value="PreToolUse" data-i18n="hook.preToolUse">PreToolUse - Before a tool is executed</option>
<option value="PostToolUse" data-i18n="hook.postToolUse">PostToolUse - After a tool completes</option>
<option value="Notification" data-i18n="hook.notification">Notification - On notifications</option>
<option value="Stop" data-i18n="hook.stop">Stop - When agent stops</option>
</select>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1">Matcher (optional)</label>
<input type="text" id="hookMatcher" placeholder="e.g., Write, Edit, Bash (leave empty for all)"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
<p class="text-xs text-muted-foreground mt-1">Tool name to match. Leave empty to match all tools.</p>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1">Command <span class="text-destructive">*</span></label>
<input type="text" id="hookCommand" placeholder="e.g., curl, bash, node"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20">
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1">Arguments (one per line)</label>
<textarea id="hookArgs" placeholder="e.g.,&#10;-X&#10;POST&#10;http://localhost:3456/api/hook" rows="4"
class="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm font-mono focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 resize-none"></textarea>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-1">Scope</label>
<div class="flex gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="hookScope" value="project" checked class="text-primary focus:ring-primary">
<span class="text-sm text-foreground">Project (.claude/settings.json)</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="hookScope" value="global" class="text-primary focus:ring-primary">
<span class="text-sm text-foreground">Global (~/.claude/settings.json)</span>
</label>
</div>
</div>
<div class="form-group">
<label class="block text-sm font-medium text-foreground mb-2">Quick Templates</label>
<div class="grid grid-cols-2 gap-2">
<button class="hook-template-btn px-3 py-2 text-xs bg-muted text-foreground rounded border border-border hover:bg-hover transition-colors text-left" onclick="applyHookTemplate('ccw-notify')">
<span class="font-medium">CCW Notify</span>
<span class="block text-muted-foreground">Notify dashboard on Write</span>
</button>
<button class="hook-template-btn px-3 py-2 text-xs bg-muted text-foreground rounded border border-border hover:bg-hover transition-colors text-left" onclick="applyHookTemplate('log-tool')">
<span class="font-medium">Log Tool Usage</span>
<span class="block text-muted-foreground">Log all tool executions</span>
</button>
<button class="hook-template-btn px-3 py-2 text-xs bg-muted text-foreground rounded border border-border hover:bg-hover transition-colors text-left" onclick="applyHookTemplate('lint-check')">
<span class="font-medium">Lint Check</span>
<span class="block text-muted-foreground">Run eslint on file changes</span>
</button>
<button class="hook-template-btn px-3 py-2 text-xs bg-muted text-foreground rounded border border-border hover:bg-hover transition-colors text-left" onclick="applyHookTemplate('git-add')">
<span class="font-medium">Git Add</span>
<span class="block text-muted-foreground">Auto stage written files</span>
</button>
</div>
</div>
</div>
<div class="hook-modal-footer flex justify-end gap-2 px-4 py-3 border-t border-border">
<button class="px-4 py-2 text-sm bg-muted text-foreground rounded-lg hover:bg-hover transition-colors" onclick="closeHookCreateModal()">Cancel</button>
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity" onclick="submitHookCreate()">Create</button>
</div>
</div>
</div>
<!-- Hook Wizard Modal -->
<div id="hookWizardModal" class="hook-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="hook-modal-backdrop absolute inset-0 bg-black/60" onclick="closeHookWizardModal()"></div>
<div class="hook-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-xl flex flex-col max-h-[90vh]">
<div class="hook-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<h3 class="text-lg font-semibold text-foreground">Hook Wizard</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeHookWizardModal()">&times;</button>
</div>
<div class="hook-modal-body p-4 overflow-y-auto" id="wizardModalContent">
<!-- Dynamic wizard content -->
</div>
<div class="hook-modal-footer flex justify-end gap-2 px-4 py-3 border-t border-border">
<button class="px-4 py-2 text-sm bg-muted text-foreground rounded-lg hover:bg-hover transition-colors" onclick="closeHookWizardModal()">Cancel</button>
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity" onclick="submitHookWizard()">Install Hook</button>
</div>
</div>
</div>
<!-- Template View Modal -->
<div id="templateViewModal" class="hook-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="hook-modal-backdrop absolute inset-0 bg-black/60" onclick="closeTemplateViewModal()"></div>
<div class="hook-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-md flex flex-col max-h-[80vh]">
<div class="hook-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<h3 class="text-lg font-semibold text-foreground">Template Details</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeTemplateViewModal()">&times;</button>
</div>
<div class="hook-modal-body p-4 overflow-y-auto" id="templateViewContent">
<!-- Dynamic template content -->
</div>
</div>
</div>
<!-- Update CLAUDE.md Modal -->
<div id="updateClaudeMdModal" class="claude-md-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
<div class="claude-md-modal-backdrop absolute inset-0 bg-black/60" onclick="closeUpdateClaudeMdModal()"></div>
<div class="claude-md-modal-content relative bg-card border border-border rounded-lg shadow-2xl w-[90vw] max-w-md flex flex-col">
<div class="claude-md-modal-header flex items-center justify-between px-4 py-3 border-b border-border">
<h3 class="text-lg font-semibold text-foreground">Update CLAUDE.md</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeUpdateClaudeMdModal()">&times;</button>
</div>
<div class="claude-md-modal-body p-4 space-y-4">
<div class="claude-md-form-group">
<label>Target Directory</label>
<div class="claude-md-target-path" id="claudeMdTargetPath">-</div>
</div>
<div class="claude-md-form-group">
<label for="claudeMdTool">CLI Tool</label>
<select id="claudeMdTool">
<option value="gemini">Gemini (gemini-2.5-flash)</option>
<option value="qwen">Qwen (coder-model)</option>
<option value="codex">Codex (gpt5-codex)</option>
</select>
</div>
<div class="claude-md-form-group">
<label for="claudeMdStrategy">Strategy</label>
<select id="claudeMdStrategy">
<option value="single-layer">Single Layer - Current dir + child CLAUDE.md refs</option>
<option value="multi-layer">Multi Layer - Generate CLAUDE.md in all subdirs</option>
</select>
</div>
<div class="claude-md-status" id="claudeMdStatus"></div>
</div>
<div class="claude-md-modal-footer flex justify-end gap-2 px-4 py-3 border-t border-border">
<button class="px-4 py-2 text-sm bg-muted text-foreground rounded-lg hover:bg-hover transition-colors" onclick="closeUpdateClaudeMdModal()">Cancel</button>
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity" id="claudeMdExecuteBtn" onclick="executeUpdateClaudeMd()">Execute</button>
</div>
</div>
</div>
<!-- Lucide Icons (本地) -->
<script src="./assets/js/lucide.min.js"></script>
<!-- D3.js for Flowchart (本地) -->
<script src="./assets/js/d3.min.js"></script>
<!-- Cytoscape.js for Graph Visualization (本地) -->
<script src="./assets/js/cytoscape.min.js"></script>
<!-- Marked.js for Markdown rendering (本地) -->
<script src="./assets/js/marked.min.js"></script>
<!-- Highlight.js for Syntax Highlighting (本地) -->
<link rel="stylesheet" href="./assets/css/github-dark.min.css" id="hljs-theme-dark">
<link rel="stylesheet" href="./assets/css/github.min.css" id="hljs-theme-light" disabled>
<script src="./assets/js/highlight.min.js"></script>
<script>
{{JS_CONTENT}}
</script>
</body>
</html>