@yattalo/task-system 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +66 -0
  2. package/dist/commands/dashboard.d.ts +7 -0
  3. package/dist/commands/dashboard.d.ts.map +1 -0
  4. package/dist/commands/dashboard.js +30 -0
  5. package/dist/commands/dashboard.js.map +1 -0
  6. package/dist/commands/init.d.ts +8 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +316 -0
  9. package/dist/commands/init.js.map +1 -0
  10. package/dist/commands/kanban.d.ts +7 -0
  11. package/dist/commands/kanban.d.ts.map +1 -0
  12. package/dist/commands/kanban.js +68 -0
  13. package/dist/commands/kanban.js.map +1 -0
  14. package/dist/commands/report.d.ts +8 -0
  15. package/dist/commands/report.d.ts.map +1 -0
  16. package/dist/commands/report.js +60 -0
  17. package/dist/commands/report.js.map +1 -0
  18. package/dist/commands/seed.d.ts +6 -0
  19. package/dist/commands/seed.d.ts.map +1 -0
  20. package/dist/commands/seed.js +30 -0
  21. package/dist/commands/seed.js.map +1 -0
  22. package/dist/commands/status.d.ts +6 -0
  23. package/dist/commands/status.d.ts.map +1 -0
  24. package/dist/commands/status.js +62 -0
  25. package/dist/commands/status.js.map +1 -0
  26. package/dist/generators/agent-ops-scripts.d.ts +8 -0
  27. package/dist/generators/agent-ops-scripts.d.ts.map +1 -0
  28. package/dist/generators/agent-ops-scripts.js +843 -0
  29. package/dist/generators/agent-ops-scripts.js.map +1 -0
  30. package/dist/generators/agent-ops.d.ts +6 -0
  31. package/dist/generators/agent-ops.d.ts.map +1 -0
  32. package/dist/generators/agent-ops.js +290 -0
  33. package/dist/generators/agent-ops.js.map +1 -0
  34. package/dist/generators/claude-hooks.d.ts +6 -0
  35. package/dist/generators/claude-hooks.d.ts.map +1 -0
  36. package/dist/generators/claude-hooks.js +260 -0
  37. package/dist/generators/claude-hooks.js.map +1 -0
  38. package/dist/generators/cli-aliases.d.ts +6 -0
  39. package/dist/generators/cli-aliases.d.ts.map +1 -0
  40. package/dist/generators/cli-aliases.js +187 -0
  41. package/dist/generators/cli-aliases.js.map +1 -0
  42. package/dist/generators/dashboard.d.ts +6 -0
  43. package/dist/generators/dashboard.d.ts.map +1 -0
  44. package/dist/generators/dashboard.js +732 -0
  45. package/dist/generators/dashboard.js.map +1 -0
  46. package/dist/generators/git-hook.d.ts +6 -0
  47. package/dist/generators/git-hook.d.ts.map +1 -0
  48. package/dist/generators/git-hook.js +163 -0
  49. package/dist/generators/git-hook.js.map +1 -0
  50. package/dist/generators/http.d.ts +6 -0
  51. package/dist/generators/http.d.ts.map +1 -0
  52. package/dist/generators/http.js +175 -0
  53. package/dist/generators/http.js.map +1 -0
  54. package/dist/generators/orchestrator.d.ts +6 -0
  55. package/dist/generators/orchestrator.d.ts.map +1 -0
  56. package/dist/generators/orchestrator.js +391 -0
  57. package/dist/generators/orchestrator.js.map +1 -0
  58. package/dist/generators/schema.d.ts +8 -0
  59. package/dist/generators/schema.d.ts.map +1 -0
  60. package/dist/generators/schema.js +470 -0
  61. package/dist/generators/schema.js.map +1 -0
  62. package/dist/generators/skill.d.ts +6 -0
  63. package/dist/generators/skill.d.ts.map +1 -0
  64. package/dist/generators/skill.js +147 -0
  65. package/dist/generators/skill.js.map +1 -0
  66. package/dist/generators/slash-commands.d.ts +6 -0
  67. package/dist/generators/slash-commands.d.ts.map +1 -0
  68. package/dist/generators/slash-commands.js +268 -0
  69. package/dist/generators/slash-commands.js.map +1 -0
  70. package/dist/generators/tasks.d.ts +6 -0
  71. package/dist/generators/tasks.d.ts.map +1 -0
  72. package/dist/generators/tasks.js +451 -0
  73. package/dist/generators/tasks.js.map +1 -0
  74. package/dist/index.d.ts +3 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +54 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/presets/research.d.ts +3 -0
  79. package/dist/presets/research.d.ts.map +1 -0
  80. package/dist/presets/research.js +56 -0
  81. package/dist/presets/research.js.map +1 -0
  82. package/dist/presets/software.d.ts +3 -0
  83. package/dist/presets/software.d.ts.map +1 -0
  84. package/dist/presets/software.js +56 -0
  85. package/dist/presets/software.js.map +1 -0
  86. package/dist/utils/detect.d.ts +3 -0
  87. package/dist/utils/detect.d.ts.map +1 -0
  88. package/dist/utils/detect.js +76 -0
  89. package/dist/utils/detect.js.map +1 -0
  90. package/dist/utils/merge.d.ts +14 -0
  91. package/dist/utils/merge.d.ts.map +1 -0
  92. package/dist/utils/merge.js +43 -0
  93. package/dist/utils/merge.js.map +1 -0
  94. package/package.json +39 -0
@@ -0,0 +1,732 @@
1
+ // ============================================================
2
+ // Generator: Layer 3 — Standalone HTML Dashboard
3
+ // Generates dashboard.html (single file, zero framework)
4
+ // Uses Alpine.js + Tailwind CDN + Mermaid.js
5
+ // ============================================================
6
+ import { writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ export function generateDashboard(ctx) {
9
+ const { config, targetDir, dryRun } = ctx;
10
+ const agents = config.agents;
11
+ const pollingInterval = config.dashboard?.pollingInterval ?? 5000;
12
+ const theme = config.dashboard?.theme ?? "dark";
13
+ const isDark = theme === "dark";
14
+ const L = [];
15
+ // --- Preamble ---
16
+ L.push(`<!DOCTYPE html>`);
17
+ L.push(`<html lang="en" class="${isDark ? "dark" : ""}">`);
18
+ L.push(`<head>`);
19
+ L.push(` <meta charset="UTF-8">`);
20
+ L.push(` <meta name="viewport" content="width=device-width, initial-scale=1.0">`);
21
+ L.push(` <title>${config.projectName} — Task Dashboard</title>`);
22
+ L.push(` <script src="https://cdn.tailwindcss.com"><\/script>`);
23
+ L.push(` <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"><\/script>`);
24
+ L.push(` <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"><\/script>`);
25
+ L.push(` <script>`);
26
+ L.push(` tailwind.config = {`);
27
+ L.push(` darkMode: 'class',`);
28
+ L.push(` theme: { extend: {} }`);
29
+ L.push(` };`);
30
+ L.push(` <\/script>`);
31
+ // --- Custom CSS ---
32
+ L.push(` <style>`);
33
+ L.push(` [x-cloak] { display: none !important; }`);
34
+ L.push(` .kanban-col { min-height: 400px; }`);
35
+ L.push(` .kanban-card { transition: transform 0.15s, box-shadow 0.15s; cursor: pointer; }`);
36
+ L.push(` .kanban-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }`);
37
+ L.push(` .modal-backdrop { background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); }`);
38
+ L.push(` .progress-bar { transition: width 0.5s ease; }`);
39
+ L.push(` .tab-active { border-bottom: 2px solid #06b6d4; color: #06b6d4; }`);
40
+ L.push(` .status-badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 9999px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }`);
41
+ L.push(` .agent-badge { font-size: 0.65rem; padding: 1px 6px; border-radius: 4px; font-weight: 700; }`);
42
+ L.push(` .mermaid svg { max-width: 100%; }`);
43
+ L.push(` @keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }`);
44
+ L.push(` .pulse-dot { animation: pulse-dot 2s infinite; }`);
45
+ L.push(` </style>`);
46
+ L.push(`</head>`);
47
+ // --- Body with Alpine root ---
48
+ const bg = isDark ? "bg-gray-950 text-gray-100" : "bg-gray-50 text-gray-900";
49
+ L.push(`<body class="${bg} min-h-screen font-mono" x-data="dashboard()" x-init="init()">`);
50
+ // --- Header ---
51
+ L.push(``);
52
+ L.push(` <!-- Header -->`);
53
+ L.push(` <header class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border-b px-6 py-3 flex items-center justify-between">`);
54
+ L.push(` <div class="flex items-center gap-4">`);
55
+ L.push(` <h1 class="text-lg font-bold tracking-tight">${config.projectName}</h1>`);
56
+ L.push(` <span class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}">Task Dashboard</span>`);
57
+ L.push(` </div>`);
58
+ L.push(` <div class="flex items-center gap-4">`);
59
+ L.push(` <div class="flex items-center gap-2 text-xs ${isDark ? "text-gray-400" : "text-gray-500"}">`);
60
+ L.push(` <span class="w-2 h-2 rounded-full pulse-dot" :class="connected ? 'bg-green-500' : 'bg-red-500'"></span>`);
61
+ L.push(` <span x-text="connected ? 'Connected' : 'Disconnected'"></span>`);
62
+ L.push(` </div>`);
63
+ L.push(` <span class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="'Updated: ' + lastUpdated"></span>`);
64
+ L.push(` <button @click="fetchAll()" class="text-xs px-3 py-1 rounded ${isDark ? "bg-gray-800 hover:bg-gray-700" : "bg-gray-200 hover:bg-gray-300"} transition">Refresh</button>`);
65
+ L.push(` </div>`);
66
+ L.push(` </header>`);
67
+ // --- Tab Navigation ---
68
+ L.push(``);
69
+ L.push(` <!-- Tab Navigation -->`);
70
+ L.push(` <nav class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border-b px-6 flex gap-1">`);
71
+ const tabs = [
72
+ { id: "kanban", label: "Kanban" },
73
+ { id: "graph", label: "Graph" },
74
+ { id: "table", label: "Table" },
75
+ { id: "agents", label: "Agents" },
76
+ { id: "stats", label: "Stats" },
77
+ ];
78
+ for (const tab of tabs) {
79
+ L.push(` <button @click="activeTab = '${tab.id}'" :class="activeTab === '${tab.id}' ? 'tab-active' : ''" class="px-4 py-3 text-sm font-medium ${isDark ? "text-gray-400 hover:text-gray-200" : "text-gray-500 hover:text-gray-700"} transition">${tab.label}</button>`);
80
+ }
81
+ L.push(` </nav>`);
82
+ // --- Main Content ---
83
+ L.push(``);
84
+ L.push(` <!-- Main Content -->`);
85
+ L.push(` <main class="p-6">`);
86
+ // ======= KANBAN VIEW =======
87
+ L.push(``);
88
+ L.push(` <!-- Kanban View -->`);
89
+ L.push(` <div x-show="activeTab === 'kanban'" x-cloak>`);
90
+ L.push(` <div class="flex gap-4 overflow-x-auto pb-4">`);
91
+ const statusCols = [
92
+ { key: "todo", label: "To Do", color: "blue" },
93
+ { key: "in_progress", label: "In Progress", color: "cyan" },
94
+ { key: "blocked", label: "Blocked", color: "red" },
95
+ { key: "review", label: "In Review", color: "yellow" },
96
+ { key: "done", label: "Done", color: "green" },
97
+ ];
98
+ for (const col of statusCols) {
99
+ const colBg = isDark ? "bg-gray-900" : "bg-gray-100";
100
+ const headerBg = isDark ? "bg-gray-800" : "bg-gray-200";
101
+ L.push(` <!-- ${col.label} Column -->`);
102
+ L.push(` <div class="flex-shrink-0 w-72 ${colBg} rounded-lg kanban-col">`);
103
+ L.push(` <div class="${headerBg} rounded-t-lg px-3 py-2 flex items-center justify-between">`);
104
+ L.push(` <div class="flex items-center gap-2">`);
105
+ L.push(` <span class="w-2.5 h-2.5 rounded-full bg-${col.color}-500"></span>`);
106
+ L.push(` <span class="text-sm font-semibold">${col.label}</span>`);
107
+ L.push(` </div>`);
108
+ L.push(` <span class="text-xs ${isDark ? "text-gray-400" : "text-gray-500"}" x-text="(kanban['${col.key}'] || []).length"></span>`);
109
+ L.push(` </div>`);
110
+ L.push(` <div class="p-2 flex flex-col gap-2">`);
111
+ L.push(` <template x-for="task in (kanban['${col.key}'] || [])" :key="task.taskId">`);
112
+ L.push(` <div @click="openTask(task)" class="kanban-card ${isDark ? "bg-gray-800 border-gray-700" : "bg-white border-gray-200"} border rounded-lg p-3">`);
113
+ L.push(` <div class="flex items-center justify-between mb-1">`);
114
+ L.push(` <span class="text-xs font-bold text-${col.color}-400" x-text="task.taskId"></span>`);
115
+ L.push(` <span class="agent-badge" :style="'background:' + (AGENTS[task.agent]?.color || '#666') + '22; color:' + (AGENTS[task.agent]?.color || '#999')" x-text="AGENTS[task.agent]?.icon || task.agent"></span>`);
116
+ L.push(` </div>`);
117
+ L.push(` <p class="text-sm leading-tight" x-text="task.title"></p>`);
118
+ L.push(` <div class="mt-2 flex items-center gap-2">`);
119
+ L.push(` <span class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="task.priority"></span>`);
120
+ L.push(` <template x-if="task.category">`);
121
+ L.push(` <span class="text-xs ${isDark ? "text-gray-600" : "text-gray-400"}" x-text="'#' + task.category"></span>`);
122
+ L.push(` </template>`);
123
+ L.push(` </div>`);
124
+ L.push(` </div>`);
125
+ L.push(` </template>`);
126
+ L.push(` </div>`);
127
+ L.push(` </div>`);
128
+ }
129
+ L.push(` </div>`);
130
+ L.push(` </div>`);
131
+ // ======= GRAPH VIEW =======
132
+ L.push(``);
133
+ L.push(` <!-- Graph View -->`);
134
+ L.push(` <div x-show="activeTab === 'graph'" x-cloak>`);
135
+ L.push(` <div class="${isDark ? "bg-gray-900" : "bg-white"} rounded-lg p-6">`);
136
+ L.push(` <div class="flex items-center justify-between mb-4">`);
137
+ L.push(` <h2 class="text-lg font-semibold">Dependency Graph</h2>`);
138
+ L.push(` <button @click="renderGraph()" class="text-xs px-3 py-1 rounded ${isDark ? "bg-gray-800 hover:bg-gray-700" : "bg-gray-200 hover:bg-gray-300"} transition">Re-render</button>`);
139
+ L.push(` </div>`);
140
+ L.push(` <div id="mermaid-graph" class="overflow-auto"></div>`);
141
+ L.push(` </div>`);
142
+ L.push(` </div>`);
143
+ // ======= TABLE VIEW =======
144
+ L.push(``);
145
+ L.push(` <!-- Table View -->`);
146
+ L.push(` <div x-show="activeTab === 'table'" x-cloak>`);
147
+ L.push(` <div class="${isDark ? "bg-gray-900" : "bg-white"} rounded-lg overflow-hidden">`);
148
+ L.push(` <div class="px-4 py-3 flex items-center gap-4 ${isDark ? "border-gray-800" : "border-gray-200"} border-b">`);
149
+ L.push(` <input type="text" x-model="tableFilter" placeholder="Filter tasks..." class="flex-1 px-3 py-1.5 text-sm rounded ${isDark ? "bg-gray-800 border-gray-700 text-gray-200 placeholder-gray-500" : "bg-gray-100 border-gray-300 text-gray-800 placeholder-gray-400"} border focus:outline-none focus:ring-1 focus:ring-cyan-500">`);
150
+ L.push(` <select x-model="tableStatusFilter" class="px-2 py-1.5 text-sm rounded ${isDark ? "bg-gray-800 border-gray-700 text-gray-200" : "bg-gray-100 border-gray-300 text-gray-800"} border">`);
151
+ L.push(` <option value="">All Statuses</option>`);
152
+ L.push(` <option value="todo">To Do</option>`);
153
+ L.push(` <option value="in_progress">In Progress</option>`);
154
+ L.push(` <option value="blocked">Blocked</option>`);
155
+ L.push(` <option value="review">Review</option>`);
156
+ L.push(` <option value="done">Done</option>`);
157
+ L.push(` </select>`);
158
+ L.push(` <select x-model="tableAgentFilter" class="px-2 py-1.5 text-sm rounded ${isDark ? "bg-gray-800 border-gray-700 text-gray-200" : "bg-gray-100 border-gray-300 text-gray-800"} border">`);
159
+ L.push(` <option value="">All Agents</option>`);
160
+ for (const [key, agent] of Object.entries(agents)) {
161
+ L.push(` <option value="${key}">${agent.label}</option>`);
162
+ }
163
+ L.push(` </select>`);
164
+ L.push(` </div>`);
165
+ L.push(` <div class="overflow-x-auto">`);
166
+ L.push(` <table class="w-full text-sm">`);
167
+ L.push(` <thead>`);
168
+ L.push(` <tr class="${isDark ? "bg-gray-800 text-gray-400" : "bg-gray-100 text-gray-600"}">`);
169
+ L.push(` <th class="text-left px-4 py-2 cursor-pointer" @click="sortTable('taskId')">ID</th>`);
170
+ L.push(` <th class="text-left px-4 py-2 cursor-pointer" @click="sortTable('title')">Title</th>`);
171
+ L.push(` <th class="text-left px-4 py-2 cursor-pointer" @click="sortTable('status')">Status</th>`);
172
+ L.push(` <th class="text-left px-4 py-2 cursor-pointer" @click="sortTable('agent')">Agent</th>`);
173
+ L.push(` <th class="text-left px-4 py-2 cursor-pointer" @click="sortTable('priority')">Priority</th>`);
174
+ L.push(` <th class="text-left px-4 py-2">Category</th>`);
175
+ L.push(` <th class="text-left px-4 py-2">Dependencies</th>`);
176
+ L.push(` </tr>`);
177
+ L.push(` </thead>`);
178
+ L.push(` <tbody>`);
179
+ L.push(` <template x-for="task in filteredTasks()" :key="task.taskId">`);
180
+ L.push(` <tr @click="openTask(task)" class="cursor-pointer ${isDark ? "hover:bg-gray-800 border-gray-800" : "hover:bg-gray-50 border-gray-100"} border-t transition">`);
181
+ L.push(` <td class="px-4 py-2 font-bold" :class="statusColor(task.status)" x-text="task.taskId"></td>`);
182
+ L.push(` <td class="px-4 py-2" x-text="task.title"></td>`);
183
+ L.push(` <td class="px-4 py-2"><span class="status-badge" :class="statusBadgeClass(task.status)" x-text="task.status"></span></td>`);
184
+ L.push(` <td class="px-4 py-2"><span class="agent-badge" :style="'background:' + (AGENTS[task.agent]?.color || '#666') + '22; color:' + (AGENTS[task.agent]?.color || '#999')" x-text="AGENTS[task.agent]?.label || task.agent"></span></td>`);
185
+ L.push(` <td class="px-4 py-2" x-text="task.priority"></td>`);
186
+ L.push(` <td class="px-4 py-2 text-xs ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="task.category || '—'"></td>`);
187
+ L.push(` <td class="px-4 py-2 text-xs ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="(task.dependencies || []).join(', ') || '—'"></td>`);
188
+ L.push(` </tr>`);
189
+ L.push(` </template>`);
190
+ L.push(` </tbody>`);
191
+ L.push(` </table>`);
192
+ L.push(` </div>`);
193
+ L.push(` </div>`);
194
+ L.push(` </div>`);
195
+ // ======= AGENTS VIEW =======
196
+ L.push(``);
197
+ L.push(` <!-- Agents View -->`);
198
+ L.push(` <div x-show="activeTab === 'agents'" x-cloak>`);
199
+ L.push(` <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">`);
200
+ L.push(` <template x-for="[agentKey, agentInfo] in Object.entries(AGENTS)" :key="agentKey">`);
201
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border rounded-lg p-5">`);
202
+ L.push(` <div class="flex items-center gap-3 mb-4">`);
203
+ L.push(` <div class="w-10 h-10 rounded-lg flex items-center justify-center text-lg font-bold" :style="'background:' + agentInfo.color + '33; color:' + agentInfo.color" x-text="agentInfo.icon"></div>`);
204
+ L.push(` <div>`);
205
+ L.push(` <h3 class="font-semibold" x-text="agentInfo.label"></h3>`);
206
+ L.push(` <span class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="agentKey"></span>`);
207
+ L.push(` </div>`);
208
+ L.push(` </div>`);
209
+ L.push(` <div class="space-y-3">`);
210
+ L.push(` <div>`);
211
+ L.push(` <div class="flex justify-between text-xs mb-1">`);
212
+ L.push(` <span class="${isDark ? "text-gray-400" : "text-gray-500"}">Progress</span>`);
213
+ L.push(` <span x-text="agentProgress(agentKey) + '%'"></span>`);
214
+ L.push(` </div>`);
215
+ L.push(` <div class="w-full h-2 ${isDark ? "bg-gray-800" : "bg-gray-200"} rounded-full overflow-hidden">`);
216
+ L.push(` <div class="h-full rounded-full progress-bar" :style="'width:' + agentProgress(agentKey) + '%; background:' + agentInfo.color"></div>`);
217
+ L.push(` </div>`);
218
+ L.push(` </div>`);
219
+ L.push(` <div class="grid grid-cols-3 gap-2 text-center">`);
220
+ L.push(` <div class="${isDark ? "bg-gray-800" : "bg-gray-100"} rounded p-2">`);
221
+ L.push(` <div class="text-lg font-bold" x-text="agentStat(agentKey, 'total')"></div>`);
222
+ L.push(` <div class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}">Total</div>`);
223
+ L.push(` </div>`);
224
+ L.push(` <div class="${isDark ? "bg-gray-800" : "bg-gray-100"} rounded p-2">`);
225
+ L.push(` <div class="text-lg font-bold text-cyan-400" x-text="agentStat(agentKey, 'active')"></div>`);
226
+ L.push(` <div class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}">Active</div>`);
227
+ L.push(` </div>`);
228
+ L.push(` <div class="${isDark ? "bg-gray-800" : "bg-gray-100"} rounded p-2">`);
229
+ L.push(` <div class="text-lg font-bold text-green-400" x-text="agentStat(agentKey, 'done')"></div>`);
230
+ L.push(` <div class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"}">Done</div>`);
231
+ L.push(` </div>`);
232
+ L.push(` </div>`);
233
+ L.push(` <div>`);
234
+ L.push(` <h4 class="text-xs font-semibold ${isDark ? "text-gray-400" : "text-gray-500"} mb-2">Status Breakdown</h4>`);
235
+ L.push(` <template x-for="s in ['todo','in_progress','blocked','review','done']" :key="s">`);
236
+ L.push(` <div class="flex items-center gap-2 text-xs mb-1">`);
237
+ L.push(` <span class="w-16 ${isDark ? "text-gray-500" : "text-gray-400"}" x-text="s"></span>`);
238
+ L.push(` <div class="flex-1 h-1.5 ${isDark ? "bg-gray-800" : "bg-gray-200"} rounded-full overflow-hidden">`);
239
+ L.push(` <div class="h-full rounded-full" :class="statusBarColor(s)" :style="'width:' + agentStatusPct(agentKey, s) + '%'"></div>`);
240
+ L.push(` </div>`);
241
+ L.push(` <span class="w-6 text-right" x-text="agentStatusCount(agentKey, s)"></span>`);
242
+ L.push(` </div>`);
243
+ L.push(` </template>`);
244
+ L.push(` </div>`);
245
+ L.push(` </div>`);
246
+ L.push(` </div>`);
247
+ L.push(` </template>`);
248
+ L.push(` </div>`);
249
+ L.push(` </div>`);
250
+ // ======= STATS VIEW =======
251
+ L.push(``);
252
+ L.push(` <!-- Stats View -->`);
253
+ L.push(` <div x-show="activeTab === 'stats'" x-cloak>`);
254
+ L.push(` <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">`);
255
+ // Overall Progress card
256
+ L.push(` <!-- Overall Progress -->`);
257
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border rounded-lg p-6">`);
258
+ L.push(` <h2 class="text-lg font-semibold mb-4">Overall Progress</h2>`);
259
+ L.push(` <div class="flex items-end gap-4 mb-4">`);
260
+ L.push(` <span class="text-5xl font-bold text-cyan-400" x-text="stats.completionPct + '%'"></span>`);
261
+ L.push(` <span class="text-sm ${isDark ? "text-gray-500" : "text-gray-400"} mb-1" x-text="stats.done + '/' + stats.total + ' tasks'"></span>`);
262
+ L.push(` </div>`);
263
+ L.push(` <div class="w-full h-4 ${isDark ? "bg-gray-800" : "bg-gray-200"} rounded-full overflow-hidden">`);
264
+ L.push(` <div class="h-full bg-cyan-500 rounded-full progress-bar" :style="'width:' + stats.completionPct + '%'"></div>`);
265
+ L.push(` </div>`);
266
+ L.push(` </div>`);
267
+ // Status Distribution card
268
+ L.push(` <!-- Status Distribution -->`);
269
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border rounded-lg p-6">`);
270
+ L.push(` <h2 class="text-lg font-semibold mb-4">Status Distribution</h2>`);
271
+ L.push(` <div class="space-y-3">`);
272
+ for (const s of statusCols) {
273
+ L.push(` <div>`);
274
+ L.push(` <div class="flex justify-between text-sm mb-1">`);
275
+ L.push(` <span class="flex items-center gap-2"><span class="w-2.5 h-2.5 rounded-full bg-${s.color}-500"></span>${s.label}</span>`);
276
+ L.push(` <span x-text="stats.byStatus?.${s.key} ?? 0"></span>`);
277
+ L.push(` </div>`);
278
+ L.push(` <div class="w-full h-2 ${isDark ? "bg-gray-800" : "bg-gray-200"} rounded-full overflow-hidden">`);
279
+ L.push(` <div class="h-full bg-${s.color}-500 rounded-full progress-bar" :style="'width:' + statusPct('${s.key}') + '%'"></div>`);
280
+ L.push(` </div>`);
281
+ L.push(` </div>`);
282
+ }
283
+ L.push(` </div>`);
284
+ L.push(` </div>`);
285
+ // Priority Breakdown card
286
+ L.push(` <!-- Priority Breakdown -->`);
287
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border rounded-lg p-6">`);
288
+ L.push(` <h2 class="text-lg font-semibold mb-4">Priority Breakdown</h2>`);
289
+ L.push(` <div class="grid grid-cols-4 gap-3">`);
290
+ const priorities = [
291
+ { key: "critical", label: "Critical", color: "red" },
292
+ { key: "high", label: "High", color: "orange" },
293
+ { key: "medium", label: "Medium", color: "yellow" },
294
+ { key: "low", label: "Low", color: "green" },
295
+ ];
296
+ for (const p of priorities) {
297
+ L.push(` <div class="${isDark ? "bg-gray-800" : "bg-gray-100"} rounded-lg p-3 text-center">`);
298
+ L.push(` <div class="text-2xl font-bold text-${p.color}-400" x-text="stats.byPriority?.${p.key} ?? 0"></div>`);
299
+ L.push(` <div class="text-xs ${isDark ? "text-gray-500" : "text-gray-400"} mt-1">${p.label}</div>`);
300
+ L.push(` </div>`);
301
+ }
302
+ L.push(` </div>`);
303
+ L.push(` </div>`);
304
+ // Category Breakdown card
305
+ L.push(` <!-- Category Breakdown -->`);
306
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-800" : "bg-white border-gray-200"} border rounded-lg p-6">`);
307
+ L.push(` <h2 class="text-lg font-semibold mb-4">Categories</h2>`);
308
+ L.push(` <div class="space-y-2">`);
309
+ L.push(` <template x-for="[cat, count] in Object.entries(stats.byCategory || {})" :key="cat">`);
310
+ L.push(` <div class="flex items-center gap-3">`);
311
+ L.push(` <span class="w-24 text-sm truncate" x-text="cat"></span>`);
312
+ L.push(` <div class="flex-1 h-2 ${isDark ? "bg-gray-800" : "bg-gray-200"} rounded-full overflow-hidden">`);
313
+ L.push(` <div class="h-full bg-cyan-500 rounded-full progress-bar" :style="'width:' + (stats.total ? (count/stats.total*100) : 0) + '%'"></div>`);
314
+ L.push(` </div>`);
315
+ L.push(` <span class="w-8 text-right text-sm" x-text="count"></span>`);
316
+ L.push(` </div>`);
317
+ L.push(` </template>`);
318
+ L.push(` </div>`);
319
+ L.push(` </div>`);
320
+ L.push(` </div>`);
321
+ L.push(` </div>`);
322
+ L.push(` </main>`);
323
+ // ======= TASK DETAIL MODAL =======
324
+ L.push(``);
325
+ L.push(` <!-- Task Detail Modal -->`);
326
+ L.push(` <div x-show="modalOpen" x-cloak class="fixed inset-0 z-50 flex items-center justify-center modal-backdrop" @click.self="modalOpen = false" @keydown.escape.window="modalOpen = false">`);
327
+ L.push(` <div class="${isDark ? "bg-gray-900 border-gray-700" : "bg-white border-gray-300"} border rounded-xl w-full max-w-2xl mx-4 max-h-[85vh] overflow-y-auto shadow-2xl" @click.stop>`);
328
+ L.push(` <!-- Modal Header -->`);
329
+ L.push(` <div class="${isDark ? "border-gray-800" : "border-gray-200"} border-b px-6 py-4 flex items-center justify-between">`);
330
+ L.push(` <div class="flex items-center gap-3">`);
331
+ L.push(` <span class="text-lg font-bold" :class="statusColor(selectedTask?.status)" x-text="selectedTask?.taskId"></span>`);
332
+ L.push(` <span class="status-badge" :class="statusBadgeClass(selectedTask?.status)" x-text="selectedTask?.status"></span>`);
333
+ L.push(` </div>`);
334
+ L.push(` <button @click="modalOpen = false" class="${isDark ? "text-gray-400 hover:text-gray-200" : "text-gray-500 hover:text-gray-700"} text-xl">&times;</button>`);
335
+ L.push(` </div>`);
336
+ L.push(` <!-- Modal Body -->`);
337
+ L.push(` <div class="px-6 py-4 space-y-4">`);
338
+ L.push(` <h2 class="text-xl font-semibold" x-text="selectedTask?.title"></h2>`);
339
+ L.push(` <p class="text-sm ${isDark ? "text-gray-400" : "text-gray-600"} whitespace-pre-wrap" x-text="selectedTask?.description || 'No description'"></p>`);
340
+ L.push(``);
341
+ L.push(` <div class="grid grid-cols-2 gap-4 text-sm">`);
342
+ L.push(` <div>`);
343
+ L.push(` <span class="${isDark ? "text-gray-500" : "text-gray-400"}">Agent</span>`);
344
+ L.push(` <div class="mt-1"><span class="agent-badge" :style="'background:' + (AGENTS[selectedTask?.agent]?.color || '#666') + '22; color:' + (AGENTS[selectedTask?.agent]?.color || '#999')" x-text="AGENTS[selectedTask?.agent]?.label || selectedTask?.agent"></span></div>`);
345
+ L.push(` </div>`);
346
+ L.push(` <div>`);
347
+ L.push(` <span class="${isDark ? "text-gray-500" : "text-gray-400"}">Priority</span>`);
348
+ L.push(` <div class="mt-1 font-medium" x-text="selectedTask?.priority"></div>`);
349
+ L.push(` </div>`);
350
+ L.push(` <div>`);
351
+ L.push(` <span class="${isDark ? "text-gray-500" : "text-gray-400"}">Category</span>`);
352
+ L.push(` <div class="mt-1" x-text="selectedTask?.category || '—'"></div>`);
353
+ L.push(` </div>`);
354
+ L.push(` <div>`);
355
+ L.push(` <span class="${isDark ? "text-gray-500" : "text-gray-400"}">Wave</span>`);
356
+ L.push(` <div class="mt-1" x-text="selectedTask?.wave ?? '—'"></div>`);
357
+ L.push(` </div>`);
358
+ L.push(` </div>`);
359
+ // Dependencies
360
+ L.push(``);
361
+ L.push(` <div x-show="selectedTask?.dependencies?.length > 0">`);
362
+ L.push(` <span class="text-sm ${isDark ? "text-gray-500" : "text-gray-400"}">Dependencies</span>`);
363
+ L.push(` <div class="flex flex-wrap gap-1 mt-1">`);
364
+ L.push(` <template x-for="dep in selectedTask?.dependencies || []" :key="dep">`);
365
+ L.push(` <span class="text-xs px-2 py-0.5 rounded ${isDark ? "bg-gray-800 text-gray-300" : "bg-gray-100 text-gray-700"}" x-text="dep"></span>`);
366
+ L.push(` </template>`);
367
+ L.push(` </div>`);
368
+ L.push(` </div>`);
369
+ // Blockers
370
+ L.push(``);
371
+ L.push(` <div x-show="selectedTask?.blockers?.length > 0">`);
372
+ L.push(` <span class="text-sm text-red-400">Blockers</span>`);
373
+ L.push(` <div class="space-y-1 mt-1">`);
374
+ L.push(` <template x-for="(blocker, idx) in selectedTask?.blockers || []" :key="idx">`);
375
+ L.push(` <div class="flex items-center gap-2 text-sm">`);
376
+ L.push(` <span class="w-5 h-5 rounded flex items-center justify-center text-xs" :class="blocker.resolved ? '${isDark ? "bg-green-900 text-green-400" : "bg-green-100 text-green-600"}' : '${isDark ? "bg-red-900 text-red-400" : "bg-red-100 text-red-600"}'" x-text="blocker.resolved ? '✓' : '!'"></span>`);
377
+ L.push(` <span x-text="blocker.reason"></span>`);
378
+ L.push(` </div>`);
379
+ L.push(` </template>`);
380
+ L.push(` </div>`);
381
+ L.push(` </div>`);
382
+ // Tags
383
+ L.push(``);
384
+ L.push(` <div x-show="selectedTask?.tags?.length > 0">`);
385
+ L.push(` <span class="text-sm ${isDark ? "text-gray-500" : "text-gray-400"}">Tags</span>`);
386
+ L.push(` <div class="flex flex-wrap gap-1 mt-1">`);
387
+ L.push(` <template x-for="tag in selectedTask?.tags || []" :key="tag">`);
388
+ L.push(` <span class="text-xs px-2 py-0.5 rounded ${isDark ? "bg-cyan-900/30 text-cyan-400" : "bg-cyan-50 text-cyan-600"}" x-text="tag"></span>`);
389
+ L.push(` </template>`);
390
+ L.push(` </div>`);
391
+ L.push(` </div>`);
392
+ L.push(` </div>`);
393
+ // Modal Footer — Status Change
394
+ L.push(` <!-- Modal Footer -->`);
395
+ L.push(` <div class="${isDark ? "border-gray-800 bg-gray-900/50" : "border-gray-200 bg-gray-50"} border-t px-6 py-4 flex items-center justify-between">`);
396
+ L.push(` <div class="flex items-center gap-3">`);
397
+ L.push(` <label class="text-sm ${isDark ? "text-gray-400" : "text-gray-500"}">Change Status:</label>`);
398
+ L.push(` <select x-model="newStatus" class="px-3 py-1.5 text-sm rounded ${isDark ? "bg-gray-800 border-gray-700 text-gray-200" : "bg-white border-gray-300 text-gray-800"} border">`);
399
+ L.push(` <option value="backlog">Backlog</option>`);
400
+ L.push(` <option value="todo">To Do</option>`);
401
+ L.push(` <option value="in_progress">In Progress</option>`);
402
+ L.push(` <option value="blocked">Blocked</option>`);
403
+ L.push(` <option value="review">Review</option>`);
404
+ L.push(` <option value="done">Done</option>`);
405
+ L.push(` </select>`);
406
+ L.push(` </div>`);
407
+ L.push(` <div class="flex items-center gap-2">`);
408
+ L.push(` <button @click="modalOpen = false" class="px-4 py-2 text-sm rounded ${isDark ? "bg-gray-800 hover:bg-gray-700 text-gray-300" : "bg-gray-200 hover:bg-gray-300 text-gray-700"} transition">Cancel</button>`);
409
+ L.push(` <button @click="updateTaskStatus()" :disabled="updating" class="px-4 py-2 text-sm rounded bg-cyan-600 hover:bg-cyan-500 text-white font-medium transition disabled:opacity-50">`);
410
+ L.push(` <span x-show="!updating">Update</span>`);
411
+ L.push(` <span x-show="updating">Updating...</span>`);
412
+ L.push(` </button>`);
413
+ L.push(` </div>`);
414
+ L.push(` </div>`);
415
+ L.push(` </div>`);
416
+ L.push(` </div>`);
417
+ // ======= JAVASCRIPT =======
418
+ L.push(``);
419
+ L.push(` <script>`);
420
+ // Config object
421
+ L.push(` // --- Configuration ---`);
422
+ L.push(` const CONFIG = {`);
423
+ L.push(` // IMPORTANT: Set your Convex HTTP Actions URL here`);
424
+ L.push(` // Find it in your Convex dashboard → Settings → URL`);
425
+ L.push(` // It looks like: https://your-project-123.convex.site`);
426
+ L.push(` CONVEX_URL: localStorage.getItem('CONVEX_URL') || '',`);
427
+ L.push(` POLLING_INTERVAL: ${pollingInterval},`);
428
+ L.push(` THEME: '${theme}',`);
429
+ L.push(` };`);
430
+ L.push(``);
431
+ // Agents config
432
+ L.push(` const AGENTS = {`);
433
+ for (const [key, agent] of Object.entries(agents)) {
434
+ L.push(` '${key}': { label: '${agent.label}', icon: '${agent.icon}', color: '${agent.color}' },`);
435
+ }
436
+ L.push(` };`);
437
+ L.push(``);
438
+ // Status colors map
439
+ L.push(` const STATUS_COLORS = {`);
440
+ L.push(` backlog: 'text-gray-500',`);
441
+ L.push(` todo: 'text-blue-400',`);
442
+ L.push(` in_progress: 'text-cyan-400',`);
443
+ L.push(` blocked: 'text-red-400',`);
444
+ L.push(` review: 'text-yellow-400',`);
445
+ L.push(` done: 'text-green-400',`);
446
+ L.push(` archived: 'text-gray-600',`);
447
+ L.push(` };`);
448
+ L.push(``);
449
+ L.push(` const STATUS_BADGE_CLASSES = {`);
450
+ L.push(` backlog: '${isDark ? "bg-gray-800 text-gray-400" : "bg-gray-200 text-gray-600"}',`);
451
+ L.push(` todo: '${isDark ? "bg-blue-900/40 text-blue-400" : "bg-blue-100 text-blue-700"}',`);
452
+ L.push(` in_progress: '${isDark ? "bg-cyan-900/40 text-cyan-400" : "bg-cyan-100 text-cyan-700"}',`);
453
+ L.push(` blocked: '${isDark ? "bg-red-900/40 text-red-400" : "bg-red-100 text-red-700"}',`);
454
+ L.push(` review: '${isDark ? "bg-yellow-900/40 text-yellow-400" : "bg-yellow-100 text-yellow-700"}',`);
455
+ L.push(` done: '${isDark ? "bg-green-900/40 text-green-400" : "bg-green-100 text-green-700"}',`);
456
+ L.push(` archived: '${isDark ? "bg-gray-800 text-gray-500" : "bg-gray-200 text-gray-500"}',`);
457
+ L.push(` };`);
458
+ L.push(``);
459
+ L.push(` const STATUS_BAR_COLORS = {`);
460
+ L.push(` todo: 'bg-blue-500',`);
461
+ L.push(` in_progress: 'bg-cyan-500',`);
462
+ L.push(` blocked: 'bg-red-500',`);
463
+ L.push(` review: 'bg-yellow-500',`);
464
+ L.push(` done: 'bg-green-500',`);
465
+ L.push(` };`);
466
+ L.push(``);
467
+ // API client
468
+ L.push(` // --- API Client ---`);
469
+ L.push(` async function api(path, options = {}) {`);
470
+ L.push(` if (!CONFIG.CONVEX_URL) {`);
471
+ L.push(` console.warn('CONVEX_URL not configured. Set it in localStorage or edit dashboard.html');`);
472
+ L.push(` return null;`);
473
+ L.push(` }`);
474
+ L.push(` const url = new URL(path, CONFIG.CONVEX_URL);`);
475
+ L.push(` if (options.params) {`);
476
+ L.push(` Object.entries(options.params).forEach(([k, v]) => {`);
477
+ L.push(` if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, v);`);
478
+ L.push(` });`);
479
+ L.push(` }`);
480
+ L.push(` try {`);
481
+ L.push(` const res = await fetch(url.toString(), {`);
482
+ L.push(` method: options.method || 'GET',`);
483
+ L.push(` headers: options.body ? { 'Content-Type': 'application/json' } : {},`);
484
+ L.push(` body: options.body ? JSON.stringify(options.body) : undefined,`);
485
+ L.push(` });`);
486
+ L.push(` if (!res.ok) throw new Error(res.status + ' ' + res.statusText);`);
487
+ L.push(` return await res.json();`);
488
+ L.push(` } catch (err) {`);
489
+ L.push(` console.error('API error:', path, err);`);
490
+ L.push(` return null;`);
491
+ L.push(` }`);
492
+ L.push(` }`);
493
+ L.push(``);
494
+ // Alpine.js component
495
+ L.push(` // --- Alpine.js Dashboard Component ---`);
496
+ L.push(` function dashboard() {`);
497
+ L.push(` return {`);
498
+ L.push(` // State`);
499
+ L.push(` activeTab: 'kanban',`);
500
+ L.push(` tasks: [],`);
501
+ L.push(` kanban: {},`);
502
+ L.push(` stats: { total: 0, done: 0, completionPct: 0, byStatus: {}, byPriority: {}, byCategory: {} },`);
503
+ L.push(` connected: false,`);
504
+ L.push(` lastUpdated: 'never',`);
505
+ L.push(` pollingTimer: null,`);
506
+ L.push(``);
507
+ L.push(` // Modal`);
508
+ L.push(` modalOpen: false,`);
509
+ L.push(` selectedTask: null,`);
510
+ L.push(` newStatus: '',`);
511
+ L.push(` updating: false,`);
512
+ L.push(``);
513
+ L.push(` // Table`);
514
+ L.push(` tableFilter: '',`);
515
+ L.push(` tableStatusFilter: '',`);
516
+ L.push(` tableAgentFilter: '',`);
517
+ L.push(` tableSortKey: 'taskId',`);
518
+ L.push(` tableSortDir: 'asc',`);
519
+ L.push(``);
520
+ L.push(` // Constants`);
521
+ L.push(` AGENTS,`);
522
+ L.push(``);
523
+ // init()
524
+ L.push(` async init() {`);
525
+ L.push(` // Check for URL in query string`);
526
+ L.push(` const params = new URLSearchParams(window.location.search);`);
527
+ L.push(` const urlParam = params.get('convex_url');`);
528
+ L.push(` if (urlParam) {`);
529
+ L.push(` CONFIG.CONVEX_URL = urlParam;`);
530
+ L.push(` localStorage.setItem('CONVEX_URL', urlParam);`);
531
+ L.push(` }`);
532
+ L.push(``);
533
+ L.push(` if (!CONFIG.CONVEX_URL) {`);
534
+ L.push(` const url = prompt('Enter your Convex HTTP Actions URL (e.g. https://your-project.convex.site):');`);
535
+ L.push(` if (url) {`);
536
+ L.push(` CONFIG.CONVEX_URL = url.replace(/\\/$/, '');`);
537
+ L.push(` localStorage.setItem('CONVEX_URL', CONFIG.CONVEX_URL);`);
538
+ L.push(` }`);
539
+ L.push(` }`);
540
+ L.push(``);
541
+ L.push(` mermaid.initialize({ startOnLoad: false, theme: '${isDark ? "dark" : "default"}', securityLevel: 'loose' });`);
542
+ L.push(` await this.fetchAll();`);
543
+ L.push(` this.pollingTimer = setInterval(() => this.fetchAll(), CONFIG.POLLING_INTERVAL);`);
544
+ L.push(` },`);
545
+ L.push(``);
546
+ // fetchAll()
547
+ L.push(` async fetchAll() {`);
548
+ L.push(` const [tasksData, statsData, kanbanData] = await Promise.all([`);
549
+ L.push(` api('/api/tasks/list'),`);
550
+ L.push(` api('/api/tasks/stats'),`);
551
+ L.push(` api('/api/tasks/kanban'),`);
552
+ L.push(` ]);`);
553
+ L.push(``);
554
+ L.push(` if (tasksData !== null) {`);
555
+ L.push(` this.tasks = Array.isArray(tasksData) ? tasksData : (tasksData.tasks || []);`);
556
+ L.push(` this.connected = true;`);
557
+ L.push(` } else {`);
558
+ L.push(` this.connected = false;`);
559
+ L.push(` }`);
560
+ L.push(``);
561
+ L.push(` if (statsData) {`);
562
+ L.push(` this.stats = {`);
563
+ L.push(` total: statsData.total ?? this.tasks.length,`);
564
+ L.push(` done: statsData.done ?? this.tasks.filter(t => t.status === 'done').length,`);
565
+ L.push(` completionPct: statsData.completionPct ?? (this.tasks.length ? Math.round(this.tasks.filter(t => t.status === 'done').length / this.tasks.length * 100) : 0),`);
566
+ L.push(` byStatus: statsData.byStatus ?? {},`);
567
+ L.push(` byPriority: statsData.byPriority ?? {},`);
568
+ L.push(` byCategory: statsData.byCategory ?? {},`);
569
+ L.push(` byAgent: statsData.byAgent ?? {},`);
570
+ L.push(` };`);
571
+ L.push(` }`);
572
+ L.push(``);
573
+ L.push(` if (kanbanData) {`);
574
+ L.push(` this.kanban = kanbanData;`);
575
+ L.push(` } else if (this.tasks.length) {`);
576
+ L.push(` // Build kanban locally from tasks`);
577
+ L.push(` this.kanban = {};`);
578
+ L.push(` this.tasks.forEach(t => {`);
579
+ L.push(` if (!this.kanban[t.status]) this.kanban[t.status] = [];`);
580
+ L.push(` this.kanban[t.status].push(t);`);
581
+ L.push(` });`);
582
+ L.push(` }`);
583
+ L.push(``);
584
+ L.push(` this.lastUpdated = new Date().toLocaleTimeString();`);
585
+ L.push(``);
586
+ L.push(` // Re-render graph if visible`);
587
+ L.push(` if (this.activeTab === 'graph') this.renderGraph();`);
588
+ L.push(` },`);
589
+ L.push(``);
590
+ // openTask()
591
+ L.push(` openTask(task) {`);
592
+ L.push(` this.selectedTask = { ...task };`);
593
+ L.push(` this.newStatus = task.status;`);
594
+ L.push(` this.modalOpen = true;`);
595
+ L.push(` },`);
596
+ L.push(``);
597
+ // updateTaskStatus()
598
+ L.push(` async updateTaskStatus() {`);
599
+ L.push(` if (!this.selectedTask || this.newStatus === this.selectedTask.status) return;`);
600
+ L.push(` this.updating = true;`);
601
+ L.push(` const result = await api('/api/tasks/changeStatus', {`);
602
+ L.push(` method: 'POST',`);
603
+ L.push(` body: { taskId: this.selectedTask.taskId, newStatus: this.newStatus, note: 'Updated via dashboard', agent: 'dashboard' },`);
604
+ L.push(` });`);
605
+ L.push(` this.updating = false;`);
606
+ L.push(` if (result !== null) {`);
607
+ L.push(` this.modalOpen = false;`);
608
+ L.push(` await this.fetchAll();`);
609
+ L.push(` }`);
610
+ L.push(` },`);
611
+ L.push(``);
612
+ // Table helpers
613
+ L.push(` filteredTasks() {`);
614
+ L.push(` let result = [...this.tasks];`);
615
+ L.push(` if (this.tableFilter) {`);
616
+ L.push(` const q = this.tableFilter.toLowerCase();`);
617
+ L.push(` result = result.filter(t => t.taskId?.toLowerCase().includes(q) || t.title?.toLowerCase().includes(q) || t.description?.toLowerCase().includes(q));`);
618
+ L.push(` }`);
619
+ L.push(` if (this.tableStatusFilter) result = result.filter(t => t.status === this.tableStatusFilter);`);
620
+ L.push(` if (this.tableAgentFilter) result = result.filter(t => t.agent === this.tableAgentFilter);`);
621
+ L.push(``);
622
+ L.push(` result.sort((a, b) => {`);
623
+ L.push(` const aVal = a[this.tableSortKey] ?? '';`);
624
+ L.push(` const bVal = b[this.tableSortKey] ?? '';`);
625
+ L.push(` const cmp = String(aVal).localeCompare(String(bVal));`);
626
+ L.push(` return this.tableSortDir === 'asc' ? cmp : -cmp;`);
627
+ L.push(` });`);
628
+ L.push(` return result;`);
629
+ L.push(` },`);
630
+ L.push(``);
631
+ L.push(` sortTable(key) {`);
632
+ L.push(` if (this.tableSortKey === key) {`);
633
+ L.push(` this.tableSortDir = this.tableSortDir === 'asc' ? 'desc' : 'asc';`);
634
+ L.push(` } else {`);
635
+ L.push(` this.tableSortKey = key;`);
636
+ L.push(` this.tableSortDir = 'asc';`);
637
+ L.push(` }`);
638
+ L.push(` },`);
639
+ L.push(``);
640
+ // Mermaid graph
641
+ L.push(` async renderGraph() {`);
642
+ L.push(` const container = document.getElementById('mermaid-graph');`);
643
+ L.push(` if (!container || !this.tasks.length) return;`);
644
+ L.push(``);
645
+ L.push(` let diagram = 'flowchart LR\\n';`);
646
+ L.push(` const statusStyles = { todo: 'fill:#3b82f6,color:#fff', in_progress: 'fill:#06b6d4,color:#fff', blocked: 'fill:#ef4444,color:#fff', review: 'fill:#eab308,color:#000', done: 'fill:#22c55e,color:#fff' };`);
647
+ L.push(``);
648
+ L.push(` this.tasks.forEach(t => {`);
649
+ L.push(` const safe = (t.title || '').replace(/"/g, "'").substring(0, 40);`);
650
+ L.push(` diagram += ' ' + t.taskId + '["' + t.taskId + ': ' + safe + '"]\\n';`);
651
+ L.push(` });`);
652
+ L.push(``);
653
+ L.push(` this.tasks.forEach(t => {`);
654
+ L.push(` (t.dependencies || []).forEach(dep => {`);
655
+ L.push(` diagram += ' ' + dep + ' --> ' + t.taskId + '\\n';`);
656
+ L.push(` });`);
657
+ L.push(` });`);
658
+ L.push(``);
659
+ L.push(` // Apply styles`);
660
+ L.push(` this.tasks.forEach(t => {`);
661
+ L.push(` const style = statusStyles[t.status] || 'fill:#6b7280,color:#fff';`);
662
+ L.push(` diagram += ' style ' + t.taskId + ' ' + style + '\\n';`);
663
+ L.push(` });`);
664
+ L.push(``);
665
+ L.push(` try {`);
666
+ L.push(` const { svg } = await mermaid.render('graph-' + Date.now(), diagram);`);
667
+ L.push(` container.innerHTML = svg;`);
668
+ L.push(` } catch (err) {`);
669
+ L.push(` container.innerHTML = '<p class="text-red-400 text-sm">Failed to render graph: ' + err.message + '</p>';`);
670
+ L.push(` }`);
671
+ L.push(` },`);
672
+ L.push(``);
673
+ // Agent helpers
674
+ L.push(` agentTasks(agentKey) {`);
675
+ L.push(` return this.tasks.filter(t => t.agent === agentKey);`);
676
+ L.push(` },`);
677
+ L.push(``);
678
+ L.push(` agentProgress(agentKey) {`);
679
+ L.push(` const at = this.agentTasks(agentKey);`);
680
+ L.push(` if (!at.length) return 0;`);
681
+ L.push(` return Math.round(at.filter(t => t.status === 'done').length / at.length * 100);`);
682
+ L.push(` },`);
683
+ L.push(``);
684
+ L.push(` agentStat(agentKey, type) {`);
685
+ L.push(` const at = this.agentTasks(agentKey);`);
686
+ L.push(` if (type === 'total') return at.length;`);
687
+ L.push(` if (type === 'active') return at.filter(t => t.status === 'in_progress').length;`);
688
+ L.push(` if (type === 'done') return at.filter(t => t.status === 'done').length;`);
689
+ L.push(` return 0;`);
690
+ L.push(` },`);
691
+ L.push(``);
692
+ L.push(` agentStatusCount(agentKey, status) {`);
693
+ L.push(` return this.agentTasks(agentKey).filter(t => t.status === status).length;`);
694
+ L.push(` },`);
695
+ L.push(``);
696
+ L.push(` agentStatusPct(agentKey, status) {`);
697
+ L.push(` const at = this.agentTasks(agentKey);`);
698
+ L.push(` if (!at.length) return 0;`);
699
+ L.push(` return Math.round(at.filter(t => t.status === status).length / at.length * 100);`);
700
+ L.push(` },`);
701
+ L.push(``);
702
+ // Stats helpers
703
+ L.push(` statusPct(status) {`);
704
+ L.push(` if (!this.stats.total) return 0;`);
705
+ L.push(` return Math.round((this.stats.byStatus?.[status] ?? 0) / this.stats.total * 100);`);
706
+ L.push(` },`);
707
+ L.push(``);
708
+ // Style helpers
709
+ L.push(` statusColor(status) {`);
710
+ L.push(` return STATUS_COLORS[status] || 'text-gray-400';`);
711
+ L.push(` },`);
712
+ L.push(``);
713
+ L.push(` statusBadgeClass(status) {`);
714
+ L.push(` return STATUS_BADGE_CLASSES[status] || '';`);
715
+ L.push(` },`);
716
+ L.push(``);
717
+ L.push(` statusBarColor(status) {`);
718
+ L.push(` return STATUS_BAR_COLORS[status] || 'bg-gray-500';`);
719
+ L.push(` },`);
720
+ L.push(` };`);
721
+ L.push(` }`);
722
+ L.push(` <\/script>`);
723
+ L.push(`</body>`);
724
+ L.push(`</html>`);
725
+ const content = L.join("\n");
726
+ const outPath = join(targetDir, "dashboard.html");
727
+ if (!dryRun) {
728
+ writeFileSync(outPath, content, "utf-8");
729
+ }
730
+ return { file: outPath, lines: L.length };
731
+ }
732
+ //# sourceMappingURL=dashboard.js.map