enhance_swarm 2.0.0 → 2.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.
- checksums.yaml +4 -4
- data/.claude/CLAUDE.md +164 -0
- data/.claude/MCP.md +117 -0
- data/.claude/PERSONAS.md +114 -0
- data/.claude/RULES.md +221 -0
- data/.enhance_swarm/logs/general_output.log +0 -404
- data/.enhance_swarm.yml +33 -0
- data/CHANGELOG.md +71 -0
- data/README.md +128 -3
- data/lib/enhance_swarm/agent_spawner.rb +205 -12
- data/lib/enhance_swarm/cli.rb +129 -1
- data/lib/enhance_swarm/task_coordinator.rb +363 -86
- data/lib/enhance_swarm/version.rb +1 -1
- metadata +13 -97
- data/PRODUCTION_TEST_LOG.md +0 -502
- data/debug_agent_spawner.rb +0 -99
- data/debug_cli_spawn.rb +0 -95
- data/debug_fixes.rb +0 -209
- data/debug_script_execution.rb +0 -124
- data/debug_session_issue.rb +0 -87
- data/debug_spawn.rb +0 -113
- data/debug_spawn_step_by_step.rb +0 -190
- data/debug_worktree.rb +0 -77
- data/enhance_swarm-0.1.1.gem +0 -0
- data/enhance_swarm-1.0.0.gem +0 -0
- data/final_validation_test.rb +0 -199
- data/setup.sh +0 -86
- data/test_blog_app/.enhance_swarm/archives/session_1751187575_e119ea73_20250629_105935.json +0 -16
- data/test_blog_app/.enhance_swarm/archives/session_1751187637_7fda97dd_20250629_110037.json +0 -32
- data/test_blog_app/.enhance_swarm/archives/session_1751190527_4c99147e_20250629_114847.json +0 -32
- data/test_blog_app/.enhance_swarm/archives/session_1751190541_8dc83406_20250629_114901.json +0 -16
- data/test_blog_app/.ruby-version +0 -1
- data/test_blog_app/Gemfile +0 -18
- data/test_blog_app/Gemfile.lock +0 -206
- data/test_blog_app/README.md +0 -24
- data/test_blog_app/Rakefile +0 -6
- data/test_blog_app/app/assets/images/.keep +0 -0
- data/test_blog_app/app/assets/stylesheets/application.css +0 -10
- data/test_blog_app/app/controllers/application_controller.rb +0 -4
- data/test_blog_app/app/controllers/concerns/.keep +0 -0
- data/test_blog_app/app/helpers/application_helper.rb +0 -2
- data/test_blog_app/app/models/application_record.rb +0 -3
- data/test_blog_app/app/models/concerns/.keep +0 -0
- data/test_blog_app/app/views/layouts/application.html.erb +0 -27
- data/test_blog_app/app/views/pwa/manifest.json.erb +0 -22
- data/test_blog_app/app/views/pwa/service-worker.js +0 -26
- data/test_blog_app/bin/dev +0 -2
- data/test_blog_app/bin/rails +0 -4
- data/test_blog_app/bin/rake +0 -4
- data/test_blog_app/bin/setup +0 -34
- data/test_blog_app/config/application.rb +0 -42
- data/test_blog_app/config/boot.rb +0 -3
- data/test_blog_app/config/credentials.yml.enc +0 -1
- data/test_blog_app/config/database.yml +0 -32
- data/test_blog_app/config/environment.rb +0 -5
- data/test_blog_app/config/environments/development.rb +0 -51
- data/test_blog_app/config/environments/production.rb +0 -67
- data/test_blog_app/config/environments/test.rb +0 -42
- data/test_blog_app/config/initializers/assets.rb +0 -7
- data/test_blog_app/config/initializers/content_security_policy.rb +0 -25
- data/test_blog_app/config/initializers/filter_parameter_logging.rb +0 -8
- data/test_blog_app/config/initializers/inflections.rb +0 -16
- data/test_blog_app/config/locales/en.yml +0 -31
- data/test_blog_app/config/master.key +0 -1
- data/test_blog_app/config/puma.rb +0 -38
- data/test_blog_app/config/routes.rb +0 -14
- data/test_blog_app/config.ru +0 -6
- data/test_blog_app/db/seeds.rb +0 -9
- data/test_blog_app/lib/tasks/.keep +0 -0
- data/test_blog_app/log/.keep +0 -0
- data/test_blog_app/public/400.html +0 -114
- data/test_blog_app/public/404.html +0 -114
- data/test_blog_app/public/406-unsupported-browser.html +0 -114
- data/test_blog_app/public/422.html +0 -114
- data/test_blog_app/public/500.html +0 -114
- data/test_blog_app/public/icon.png +0 -0
- data/test_blog_app/public/icon.svg +0 -3
- data/test_blog_app/public/robots.txt +0 -1
- data/test_blog_app/script/.keep +0 -0
- data/test_blog_app/storage/.keep +0 -0
- data/test_blog_app/test/controllers/.keep +0 -0
- data/test_blog_app/test/fixtures/files/.keep +0 -0
- data/test_blog_app/test/helpers/.keep +0 -0
- data/test_blog_app/test/integration/.keep +0 -0
- data/test_blog_app/test/models/.keep +0 -0
- data/test_blog_app/test/test_helper.rb +0 -15
- data/test_blog_app/test_enhance_swarm_e2e.rb +0 -244
- data/test_blog_app/test_realistic_workflow.rb +0 -292
- data/test_blog_app/tmp/.keep +0 -0
- data/test_blog_app/tmp/pids/.keep +0 -0
- data/test_blog_app/tmp/storage/.keep +0 -0
- data/test_blog_app/vendor/.keep +0 -0
- data/test_builtin_functionality.rb +0 -121
- data/test_complete_system.rb +0 -267
- data/test_core_components.rb +0 -156
- data/test_real_claude_integration.rb +0 -285
- data/test_security.rb +0 -150
- data/test_smart_defaults.rb +0 -155
- data/test_task_integration.rb +0 -173
- data/test_web_ui.rb +0 -245
- data/web/assets/css/main.css +0 -645
- data/web/assets/js/kanban.js +0 -499
- data/web/assets/js/main.js +0 -525
- data/web/templates/dashboard.html.erb +0 -226
- data/web/templates/kanban.html.erb +0 -193
data/web/assets/js/kanban.js
DELETED
@@ -1,499 +0,0 @@
|
|
1
|
-
// Kanban Board JavaScript for EnhanceSwarm
|
2
|
-
|
3
|
-
// Kanban state
|
4
|
-
window.KanbanBoard = {
|
5
|
-
columns: [
|
6
|
-
{ id: 'todo', title: 'To Do', tasks: [] },
|
7
|
-
{ id: 'in_progress', title: 'In Progress', tasks: [] },
|
8
|
-
{ id: 'review', title: 'Review', tasks: [] },
|
9
|
-
{ id: 'done', title: 'Done', tasks: [] }
|
10
|
-
],
|
11
|
-
sortables: {}
|
12
|
-
};
|
13
|
-
|
14
|
-
// Initialize kanban board
|
15
|
-
function initializeKanban() {
|
16
|
-
console.log('Initializing Kanban board...');
|
17
|
-
}
|
18
|
-
|
19
|
-
// Load kanban data
|
20
|
-
async function loadKanbanData() {
|
21
|
-
try {
|
22
|
-
const taskData = await apiRequest('/api/tasks');
|
23
|
-
|
24
|
-
// Process task data
|
25
|
-
processTaskData(taskData);
|
26
|
-
|
27
|
-
// Render kanban board
|
28
|
-
renderKanbanBoard();
|
29
|
-
|
30
|
-
// Update stats
|
31
|
-
updateKanbanStats();
|
32
|
-
|
33
|
-
} catch (error) {
|
34
|
-
console.error('Failed to load kanban data:', error);
|
35
|
-
showKanbanError('Failed to load task data');
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
function processTaskData(taskData) {
|
40
|
-
// Reset columns
|
41
|
-
window.KanbanBoard.columns.forEach(column => {
|
42
|
-
column.tasks = [];
|
43
|
-
});
|
44
|
-
|
45
|
-
// Process swarm-tasks data
|
46
|
-
if (taskData.tasks && Array.isArray(taskData.tasks)) {
|
47
|
-
taskData.tasks.forEach(task => {
|
48
|
-
const column = findColumnByTaskStatus(task.status);
|
49
|
-
if (column) {
|
50
|
-
column.tasks.push(formatTask(task));
|
51
|
-
}
|
52
|
-
});
|
53
|
-
}
|
54
|
-
|
55
|
-
// Process folder-based tasks
|
56
|
-
if (taskData.folders && Array.isArray(taskData.folders)) {
|
57
|
-
taskData.folders.forEach(folder => {
|
58
|
-
const column = window.KanbanBoard.columns.find(col => col.id === folder.name);
|
59
|
-
if (column && folder.task_count > 0) {
|
60
|
-
// Add placeholder tasks for folder-based tasks
|
61
|
-
for (let i = 0; i < folder.task_count; i++) {
|
62
|
-
column.tasks.push({
|
63
|
-
id: `${folder.name}_${i}`,
|
64
|
-
title: `Task from ${folder.name}`,
|
65
|
-
description: `File-based task in ${folder.path}`,
|
66
|
-
priority: 'medium',
|
67
|
-
category: 'file-based',
|
68
|
-
agents: [],
|
69
|
-
created_at: new Date().toISOString()
|
70
|
-
});
|
71
|
-
}
|
72
|
-
}
|
73
|
-
});
|
74
|
-
}
|
75
|
-
}
|
76
|
-
|
77
|
-
function findColumnByTaskStatus(status) {
|
78
|
-
const statusMap = {
|
79
|
-
'todo': 'todo',
|
80
|
-
'pending': 'todo',
|
81
|
-
'active': 'in_progress',
|
82
|
-
'in_progress': 'in_progress',
|
83
|
-
'working': 'in_progress',
|
84
|
-
'review': 'review',
|
85
|
-
'testing': 'review',
|
86
|
-
'done': 'done',
|
87
|
-
'completed': 'done',
|
88
|
-
'finished': 'done'
|
89
|
-
};
|
90
|
-
|
91
|
-
const mappedStatus = statusMap[status] || 'todo';
|
92
|
-
return window.KanbanBoard.columns.find(col => col.id === mappedStatus);
|
93
|
-
}
|
94
|
-
|
95
|
-
function formatTask(task) {
|
96
|
-
return {
|
97
|
-
id: task.id || generateTaskId(),
|
98
|
-
title: task.title || task.name || 'Untitled Task',
|
99
|
-
description: task.description || task.content || '',
|
100
|
-
priority: task.priority || 'medium',
|
101
|
-
category: task.category || task.type || 'general',
|
102
|
-
agents: task.agents || task.recommended_agents || [],
|
103
|
-
created_at: task.created_at || task.start_time || new Date().toISOString(),
|
104
|
-
updated_at: task.updated_at || task.end_time,
|
105
|
-
status: task.status || 'todo'
|
106
|
-
};
|
107
|
-
}
|
108
|
-
|
109
|
-
function generateTaskId() {
|
110
|
-
return 'task_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
111
|
-
}
|
112
|
-
|
113
|
-
function renderKanbanBoard() {
|
114
|
-
const board = document.getElementById('kanban-board');
|
115
|
-
if (!board) return;
|
116
|
-
|
117
|
-
board.innerHTML = window.KanbanBoard.columns.map(column =>
|
118
|
-
renderKanbanColumn(column)
|
119
|
-
).join('');
|
120
|
-
|
121
|
-
// Initialize drag and drop
|
122
|
-
initializeDragAndDrop();
|
123
|
-
}
|
124
|
-
|
125
|
-
function renderKanbanColumn(column) {
|
126
|
-
return `
|
127
|
-
<div class="kanban-column" data-column-id="${column.id}">
|
128
|
-
<div class="kanban-column-header">
|
129
|
-
<span class="kanban-column-title">${column.title}</span>
|
130
|
-
<span class="kanban-column-count">${column.tasks.length}</span>
|
131
|
-
</div>
|
132
|
-
<div class="kanban-column-body" id="column-${column.id}">
|
133
|
-
${column.tasks.map(task => renderTaskCard(task)).join('')}
|
134
|
-
</div>
|
135
|
-
</div>
|
136
|
-
`;
|
137
|
-
}
|
138
|
-
|
139
|
-
function renderTaskCard(task) {
|
140
|
-
return `
|
141
|
-
<div class="task-card" data-task-id="${task.id}" onclick="openTaskDetails('${task.id}')">
|
142
|
-
<div class="task-title">${escapeHtml(task.title)}</div>
|
143
|
-
${task.description ? `<div class="task-description">${escapeHtml(task.description.substring(0, 100))}${task.description.length > 100 ? '...' : ''}</div>` : ''}
|
144
|
-
<div class="task-meta">
|
145
|
-
<span class="task-priority ${task.priority}">${task.priority.toUpperCase()}</span>
|
146
|
-
<span class="task-date">${formatTaskDate(task.created_at)}</span>
|
147
|
-
</div>
|
148
|
-
${task.agents && task.agents.length > 0 ? `
|
149
|
-
<div class="task-agents">
|
150
|
-
${task.agents.slice(0, 3).map(agent => `<span class="task-agent">${agent}</span>`).join('')}
|
151
|
-
${task.agents.length > 3 ? `<span class="task-agent-more">+${task.agents.length - 3}</span>` : ''}
|
152
|
-
</div>
|
153
|
-
` : ''}
|
154
|
-
</div>
|
155
|
-
`;
|
156
|
-
}
|
157
|
-
|
158
|
-
function initializeDragAndDrop() {
|
159
|
-
// Destroy existing sortables
|
160
|
-
Object.values(window.KanbanBoard.sortables).forEach(sortable => {
|
161
|
-
if (sortable && typeof sortable.destroy === 'function') {
|
162
|
-
sortable.destroy();
|
163
|
-
}
|
164
|
-
});
|
165
|
-
window.KanbanBoard.sortables = {};
|
166
|
-
|
167
|
-
// Check if Sortable is available
|
168
|
-
if (typeof Sortable === 'undefined') {
|
169
|
-
console.warn('Sortable.js not loaded, drag and drop disabled');
|
170
|
-
return;
|
171
|
-
}
|
172
|
-
|
173
|
-
// Initialize sortable for each column
|
174
|
-
window.KanbanBoard.columns.forEach(column => {
|
175
|
-
const columnElement = document.getElementById(`column-${column.id}`);
|
176
|
-
if (columnElement) {
|
177
|
-
window.KanbanBoard.sortables[column.id] = Sortable.create(columnElement, {
|
178
|
-
group: 'kanban',
|
179
|
-
animation: 150,
|
180
|
-
ghostClass: 'task-card-ghost',
|
181
|
-
dragClass: 'task-card-drag',
|
182
|
-
onEnd: function(evt) {
|
183
|
-
handleTaskMove(evt);
|
184
|
-
}
|
185
|
-
});
|
186
|
-
}
|
187
|
-
});
|
188
|
-
}
|
189
|
-
|
190
|
-
function handleTaskMove(evt) {
|
191
|
-
const taskId = evt.item.dataset.taskId;
|
192
|
-
const newColumnId = evt.to.closest('.kanban-column').dataset.columnId;
|
193
|
-
const oldColumnId = evt.from.closest('.kanban-column').dataset.columnId;
|
194
|
-
|
195
|
-
if (newColumnId === oldColumnId) return;
|
196
|
-
|
197
|
-
console.log(`Moving task ${taskId} from ${oldColumnId} to ${newColumnId}`);
|
198
|
-
|
199
|
-
// Update local state
|
200
|
-
moveTaskBetweenColumns(taskId, oldColumnId, newColumnId);
|
201
|
-
|
202
|
-
// Update column counts
|
203
|
-
updateColumnCounts();
|
204
|
-
|
205
|
-
// Show success message
|
206
|
-
showNotification(`Task moved to ${getColumnTitle(newColumnId)}`, 'success');
|
207
|
-
|
208
|
-
// TODO: Send API request to update task status
|
209
|
-
// updateTaskStatus(taskId, newColumnId);
|
210
|
-
}
|
211
|
-
|
212
|
-
function moveTaskBetweenColumns(taskId, fromColumnId, toColumnId) {
|
213
|
-
const fromColumn = window.KanbanBoard.columns.find(col => col.id === fromColumnId);
|
214
|
-
const toColumn = window.KanbanBoard.columns.find(col => col.id === toColumnId);
|
215
|
-
|
216
|
-
if (!fromColumn || !toColumn) return;
|
217
|
-
|
218
|
-
const taskIndex = fromColumn.tasks.findIndex(task => task.id === taskId);
|
219
|
-
if (taskIndex === -1) return;
|
220
|
-
|
221
|
-
const task = fromColumn.tasks.splice(taskIndex, 1)[0];
|
222
|
-
task.status = toColumnId;
|
223
|
-
task.updated_at = new Date().toISOString();
|
224
|
-
|
225
|
-
toColumn.tasks.push(task);
|
226
|
-
}
|
227
|
-
|
228
|
-
function getColumnTitle(columnId) {
|
229
|
-
const column = window.KanbanBoard.columns.find(col => col.id === columnId);
|
230
|
-
return column ? column.title : columnId;
|
231
|
-
}
|
232
|
-
|
233
|
-
function updateColumnCounts() {
|
234
|
-
window.KanbanBoard.columns.forEach(column => {
|
235
|
-
const countElement = document.querySelector(`[data-column-id="${column.id}"] .kanban-column-count`);
|
236
|
-
if (countElement) {
|
237
|
-
countElement.textContent = column.tasks.length;
|
238
|
-
}
|
239
|
-
});
|
240
|
-
}
|
241
|
-
|
242
|
-
function updateKanbanStats() {
|
243
|
-
const totalTasks = window.KanbanBoard.columns.reduce((sum, col) => sum + col.tasks.length, 0);
|
244
|
-
const activeTasks = window.KanbanBoard.columns.find(col => col.id === 'in_progress')?.tasks.length || 0;
|
245
|
-
const completedTasks = window.KanbanBoard.columns.find(col => col.id === 'done')?.tasks.length || 0;
|
246
|
-
|
247
|
-
document.getElementById('total-tasks').textContent = totalTasks;
|
248
|
-
document.getElementById('active-tasks').textContent = activeTasks;
|
249
|
-
document.getElementById('completed-tasks').textContent = completedTasks;
|
250
|
-
}
|
251
|
-
|
252
|
-
// Task management functions
|
253
|
-
function createTask() {
|
254
|
-
openModal('create-task-modal');
|
255
|
-
}
|
256
|
-
|
257
|
-
function openTaskDetails(taskId) {
|
258
|
-
const task = findTaskById(taskId);
|
259
|
-
if (!task) return;
|
260
|
-
|
261
|
-
const modal = document.getElementById('task-details-modal');
|
262
|
-
const title = document.getElementById('task-details-title');
|
263
|
-
const body = document.getElementById('task-details-body');
|
264
|
-
|
265
|
-
title.textContent = task.title;
|
266
|
-
body.innerHTML = `
|
267
|
-
<div class="task-details">
|
268
|
-
<div class="detail-row">
|
269
|
-
<strong>Description:</strong>
|
270
|
-
<p>${escapeHtml(task.description) || 'No description provided'}</p>
|
271
|
-
</div>
|
272
|
-
<div class="detail-row">
|
273
|
-
<strong>Priority:</strong>
|
274
|
-
<span class="task-priority ${task.priority}">${task.priority.toUpperCase()}</span>
|
275
|
-
</div>
|
276
|
-
<div class="detail-row">
|
277
|
-
<strong>Category:</strong>
|
278
|
-
<span>${task.category}</span>
|
279
|
-
</div>
|
280
|
-
<div class="detail-row">
|
281
|
-
<strong>Status:</strong>
|
282
|
-
<span>${task.status}</span>
|
283
|
-
</div>
|
284
|
-
<div class="detail-row">
|
285
|
-
<strong>Created:</strong>
|
286
|
-
<span>${formatTaskDate(task.created_at)}</span>
|
287
|
-
</div>
|
288
|
-
${task.updated_at ? `
|
289
|
-
<div class="detail-row">
|
290
|
-
<strong>Updated:</strong>
|
291
|
-
<span>${formatTaskDate(task.updated_at)}</span>
|
292
|
-
</div>
|
293
|
-
` : ''}
|
294
|
-
${task.agents && task.agents.length > 0 ? `
|
295
|
-
<div class="detail-row">
|
296
|
-
<strong>Recommended Agents:</strong>
|
297
|
-
<div class="agent-tags">
|
298
|
-
${task.agents.map(agent => `<span class="agent-tag">${agent}</span>`).join('')}
|
299
|
-
</div>
|
300
|
-
</div>
|
301
|
-
` : ''}
|
302
|
-
</div>
|
303
|
-
`;
|
304
|
-
|
305
|
-
openModal('task-details-modal');
|
306
|
-
}
|
307
|
-
|
308
|
-
function findTaskById(taskId) {
|
309
|
-
for (const column of window.KanbanBoard.columns) {
|
310
|
-
const task = column.tasks.find(t => t.id === taskId);
|
311
|
-
if (task) return task;
|
312
|
-
}
|
313
|
-
return null;
|
314
|
-
}
|
315
|
-
|
316
|
-
// Form handling for task creation
|
317
|
-
document.addEventListener('submit', function(event) {
|
318
|
-
if (event.target.id === 'create-task-form') {
|
319
|
-
event.preventDefault();
|
320
|
-
handleCreateTask(event.target);
|
321
|
-
}
|
322
|
-
});
|
323
|
-
|
324
|
-
function handleCreateTask(form) {
|
325
|
-
const formData = new FormData(form);
|
326
|
-
const agents = Array.from(form.querySelectorAll('input[name="agents"]:checked')).map(cb => cb.value);
|
327
|
-
|
328
|
-
const task = {
|
329
|
-
id: generateTaskId(),
|
330
|
-
title: formData.get('title'),
|
331
|
-
description: formData.get('description'),
|
332
|
-
priority: formData.get('priority'),
|
333
|
-
category: formData.get('category'),
|
334
|
-
agents: agents,
|
335
|
-
created_at: new Date().toISOString(),
|
336
|
-
status: 'todo'
|
337
|
-
};
|
338
|
-
|
339
|
-
// Add to todo column
|
340
|
-
const todoColumn = window.KanbanBoard.columns.find(col => col.id === 'todo');
|
341
|
-
if (todoColumn) {
|
342
|
-
todoColumn.tasks.push(task);
|
343
|
-
}
|
344
|
-
|
345
|
-
// Re-render the board
|
346
|
-
renderKanbanBoard();
|
347
|
-
updateKanbanStats();
|
348
|
-
|
349
|
-
// Close modal and reset form
|
350
|
-
closeModal('create-task-modal');
|
351
|
-
form.reset();
|
352
|
-
|
353
|
-
showNotification('Task created successfully', 'success');
|
354
|
-
}
|
355
|
-
|
356
|
-
// Utility functions
|
357
|
-
function refreshKanban() {
|
358
|
-
loadKanbanData();
|
359
|
-
}
|
360
|
-
|
361
|
-
function exportTasks() {
|
362
|
-
const allTasks = [];
|
363
|
-
window.KanbanBoard.columns.forEach(column => {
|
364
|
-
allTasks.push(...column.tasks.map(task => ({
|
365
|
-
...task,
|
366
|
-
column: column.title
|
367
|
-
})));
|
368
|
-
});
|
369
|
-
|
370
|
-
const dataStr = JSON.stringify(allTasks, null, 2);
|
371
|
-
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
372
|
-
|
373
|
-
const link = document.createElement('a');
|
374
|
-
link.href = URL.createObjectURL(dataBlob);
|
375
|
-
link.download = 'enhance-swarm-tasks.json';
|
376
|
-
link.click();
|
377
|
-
|
378
|
-
showNotification('Tasks exported successfully', 'success');
|
379
|
-
}
|
380
|
-
|
381
|
-
function showKanbanError(message) {
|
382
|
-
const board = document.getElementById('kanban-board');
|
383
|
-
if (board) {
|
384
|
-
board.innerHTML = `
|
385
|
-
<div class="kanban-error">
|
386
|
-
<i class="fas fa-exclamation-triangle"></i>
|
387
|
-
<h3>Unable to load tasks</h3>
|
388
|
-
<p>${escapeHtml(message)}</p>
|
389
|
-
<button class="btn btn-primary" onclick="loadKanbanData()">
|
390
|
-
<i class="fas fa-retry"></i> Retry
|
391
|
-
</button>
|
392
|
-
</div>
|
393
|
-
`;
|
394
|
-
}
|
395
|
-
}
|
396
|
-
|
397
|
-
function formatTaskDate(dateString) {
|
398
|
-
if (!dateString) return 'Unknown';
|
399
|
-
|
400
|
-
try {
|
401
|
-
const date = new Date(dateString);
|
402
|
-
const now = new Date();
|
403
|
-
const diffMs = now - date;
|
404
|
-
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
405
|
-
|
406
|
-
if (diffDays === 0) return 'Today';
|
407
|
-
if (diffDays === 1) return 'Yesterday';
|
408
|
-
if (diffDays < 7) return `${diffDays} days ago`;
|
409
|
-
|
410
|
-
return date.toLocaleDateString();
|
411
|
-
} catch (error) {
|
412
|
-
return 'Unknown';
|
413
|
-
}
|
414
|
-
}
|
415
|
-
|
416
|
-
function escapeHtml(text) {
|
417
|
-
if (!text) return '';
|
418
|
-
const div = document.createElement('div');
|
419
|
-
div.textContent = text;
|
420
|
-
return div.innerHTML;
|
421
|
-
}
|
422
|
-
|
423
|
-
// Add custom styles for kanban
|
424
|
-
const kanbanStyles = `
|
425
|
-
.task-card-ghost {
|
426
|
-
opacity: 0.5;
|
427
|
-
}
|
428
|
-
|
429
|
-
.task-card-drag {
|
430
|
-
transform: rotate(5deg);
|
431
|
-
}
|
432
|
-
|
433
|
-
.task-agents {
|
434
|
-
margin-top: 0.5rem;
|
435
|
-
display: flex;
|
436
|
-
flex-wrap: wrap;
|
437
|
-
gap: 0.25rem;
|
438
|
-
}
|
439
|
-
|
440
|
-
.task-agent {
|
441
|
-
background: var(--primary-color);
|
442
|
-
color: white;
|
443
|
-
padding: 0.125rem 0.5rem;
|
444
|
-
border-radius: 12px;
|
445
|
-
font-size: 0.75rem;
|
446
|
-
}
|
447
|
-
|
448
|
-
.task-agent-more {
|
449
|
-
background: var(--text-secondary);
|
450
|
-
color: white;
|
451
|
-
padding: 0.125rem 0.5rem;
|
452
|
-
border-radius: 12px;
|
453
|
-
font-size: 0.75rem;
|
454
|
-
}
|
455
|
-
|
456
|
-
.kanban-error {
|
457
|
-
display: flex;
|
458
|
-
flex-direction: column;
|
459
|
-
align-items: center;
|
460
|
-
justify-content: center;
|
461
|
-
height: 400px;
|
462
|
-
text-align: center;
|
463
|
-
color: var(--text-secondary);
|
464
|
-
}
|
465
|
-
|
466
|
-
.kanban-error i {
|
467
|
-
font-size: 3rem;
|
468
|
-
margin-bottom: 1rem;
|
469
|
-
color: var(--warning-color);
|
470
|
-
}
|
471
|
-
|
472
|
-
.detail-row {
|
473
|
-
margin-bottom: 1rem;
|
474
|
-
}
|
475
|
-
|
476
|
-
.detail-row strong {
|
477
|
-
display: block;
|
478
|
-
margin-bottom: 0.5rem;
|
479
|
-
color: var(--text-primary);
|
480
|
-
}
|
481
|
-
|
482
|
-
.agent-tags {
|
483
|
-
display: flex;
|
484
|
-
flex-wrap: wrap;
|
485
|
-
gap: 0.5rem;
|
486
|
-
}
|
487
|
-
|
488
|
-
.agent-tag {
|
489
|
-
background: var(--light-bg);
|
490
|
-
padding: 0.25rem 0.75rem;
|
491
|
-
border-radius: 16px;
|
492
|
-
border: 1px solid var(--border-color);
|
493
|
-
font-size: 0.875rem;
|
494
|
-
}
|
495
|
-
`;
|
496
|
-
|
497
|
-
const kanbanStyleSheet = document.createElement('style');
|
498
|
-
kanbanStyleSheet.textContent = kanbanStyles;
|
499
|
-
document.head.appendChild(kanbanStyleSheet);
|