claude-code-kanban 1.16.0 → 1.17.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "A web-based Kanban board for viewing Claude Code tasks with agent teams support",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -2361,7 +2361,7 @@
2361
2361
  try {
2362
2362
  const res = await fetch('/api/tasks/all');
2363
2363
  const allTasks = await res.json();
2364
- let activeTasks = allTasks.filter(t => t.status === 'in_progress');
2364
+ let activeTasks = allTasks.filter(t => t.status === 'in_progress' && !isInternalTask(t));
2365
2365
  if (filterProject) {
2366
2366
  activeTasks = activeTasks.filter(t => matchesProjectFilter(t.project));
2367
2367
  }
@@ -2457,8 +2457,9 @@
2457
2457
  sessionView.classList.add('visible');
2458
2458
  document.getElementById('owner-filter-bar').classList.remove('visible');
2459
2459
 
2460
- const totalTasks = currentTasks.length;
2461
- const completed = currentTasks.filter(t => t.status === 'completed').length;
2460
+ const visibleTasks = currentTasks.filter(t => !isInternalTask(t));
2461
+ const totalTasks = visibleTasks.length;
2462
+ const completed = visibleTasks.filter(t => t.status === 'completed').length;
2462
2463
  const percent = totalTasks > 0 ? Math.round((completed / totalTasks) * 100) : 0;
2463
2464
 
2464
2465
  const isFiltered = filterProject && filterProject !== '__recent__';
@@ -2674,7 +2675,7 @@
2674
2675
  }
2675
2676
 
2676
2677
  function renderKanban() {
2677
- let filtered = currentTasks;
2678
+ let filtered = currentTasks.filter(t => !isInternalTask(t));
2678
2679
  if (ownerFilter) {
2679
2680
  filtered = filtered.filter(t => t.owner === ownerFilter);
2680
2681
  }
@@ -3429,6 +3430,10 @@
3429
3430
  debouncedRefresh(data.sessionId, data.type === 'metadata-update');
3430
3431
  }
3431
3432
 
3433
+ if (data.type === 'plan-update') {
3434
+ refreshOpenPlan();
3435
+ }
3436
+
3432
3437
  if (data.type === 'team-update') {
3433
3438
  console.log('[SSE] Team update:', data.teamName);
3434
3439
  debouncedRefresh(data.teamName, false);
@@ -3470,6 +3475,10 @@
3470
3475
  { bg: 'rgba(99, 102, 241, 0.14)', color: '#4f46e5' }, // indigo
3471
3476
  ];
3472
3477
  const ownerColorCache = {};
3478
+ function isInternalTask(task) {
3479
+ return task.metadata && task.metadata._internal === true;
3480
+ }
3481
+
3473
3482
  function getOwnerColor(name) {
3474
3483
  if (ownerColorCache[name]) return ownerColorCache[name];
3475
3484
  let hash = 5381;
@@ -3665,7 +3674,13 @@
3665
3674
 
3666
3675
  await Promise.all(promises);
3667
3676
 
3668
- const tasks = currentSessionId === sessionId ? currentTasks : [];
3677
+ let tasks = currentSessionId === sessionId ? currentTasks : [];
3678
+ if (tasks.length === 0) {
3679
+ try {
3680
+ const r = await fetch(`/api/sessions/${sessionId}`);
3681
+ if (r.ok) tasks = await r.json();
3682
+ } catch {}
3683
+ }
3669
3684
  _planSessionId = sessionId;
3670
3685
  showInfoModal(session, teamConfig, tasks, planContent);
3671
3686
  }
@@ -3723,8 +3738,13 @@
3723
3738
  // Team info section
3724
3739
  if (teamConfig) {
3725
3740
  const ownerCounts = {};
3741
+ const memberDescriptions = {};
3726
3742
  tasks.forEach(t => {
3727
- if (t.owner) ownerCounts[t.owner] = (ownerCounts[t.owner] || 0) + 1;
3743
+ if (isInternalTask(t) && t.subject) {
3744
+ memberDescriptions[t.subject] = t.description;
3745
+ } else if (t.owner) {
3746
+ ownerCounts[t.owner] = (ownerCounts[t.owner] || 0) + 1;
3747
+ }
3728
3748
  });
3729
3749
 
3730
3750
  const members = teamConfig.members || [];
@@ -3739,11 +3759,13 @@
3739
3759
 
3740
3760
  members.forEach(member => {
3741
3761
  const taskCount = ownerCounts[member.name] || 0;
3762
+ const memberDesc = memberDescriptions[member.name];
3742
3763
  html += `
3743
3764
  <div class="team-member-card">
3744
3765
  <div class="member-name">🟢 ${escapeHtml(member.name)}</div>
3745
3766
  <div class="member-detail">Role: ${escapeHtml(member.agentType || 'unknown')}</div>
3746
3767
  ${member.model ? `<div class="member-detail">Model: ${escapeHtml(member.model)}</div>` : ''}
3768
+ ${memberDesc ? `<div class="member-detail" style="margin-top: 4px; font-style: italic; color: var(--text-secondary);">${escapeHtml(memberDesc.split('\n')[0])}</div>` : ''}
3747
3769
  <div class="member-tasks">Tasks: ${taskCount} assigned</div>
3748
3770
  </div>
3749
3771
  `;
@@ -3784,6 +3806,20 @@
3784
3806
 
3785
3807
  let _planSessionId = null;
3786
3808
 
3809
+ function refreshOpenPlan() {
3810
+ if (!_planSessionId || !document.getElementById('plan-modal').classList.contains('visible')) return;
3811
+ fetch(`/api/sessions/${_planSessionId}/plan`)
3812
+ .then(r => r.ok ? r.json() : null)
3813
+ .then(data => {
3814
+ if (data?.content) {
3815
+ _pendingPlanContent = data.content;
3816
+ const body = document.getElementById('plan-modal-body');
3817
+ body.innerHTML = DOMPurify.sanitize(marked.parse(_pendingPlanContent));
3818
+ }
3819
+ })
3820
+ .catch(() => {});
3821
+ }
3822
+
3787
3823
  function openPlanForSession(sid) {
3788
3824
  fetch(`/api/sessions/${sid}/plan`).then(r => r.ok ? r.json() : null).catch(() => null)
3789
3825
  .then(data => {
@@ -3832,7 +3868,7 @@
3832
3868
  }
3833
3869
 
3834
3870
  bar.classList.add('visible');
3835
- const owners = [...new Set(currentTasks.map(t => t.owner).filter(Boolean))].sort();
3871
+ const owners = [...new Set(currentTasks.filter(t => !isInternalTask(t)).map(t => t.owner).filter(Boolean))].sort();
3836
3872
  select.innerHTML = '<option value="">All Members</option>' +
3837
3873
  owners.map(o => {
3838
3874
  const c = getOwnerColor(o);
package/server.js CHANGED
@@ -294,6 +294,7 @@ app.get('/api/sessions', async (req, res) => {
294
294
  try {
295
295
  const taskPath = path.join(sessionPath, file);
296
296
  const task = JSON.parse(readFileSync(taskPath, 'utf8'));
297
+ if (task.metadata && task.metadata._internal) continue;
297
298
  if (task.status === 'completed') completed++;
298
299
  else if (task.status === 'in_progress') inProgress++;
299
300
  else pending++;
@@ -706,6 +707,10 @@ plansWatcher.on('all', (event, filePath) => {
706
707
  if ((event === 'add' || event === 'change' || event === 'unlink') && filePath.endsWith('.md')) {
707
708
  lastMetadataRefresh = 0;
708
709
  broadcast({ type: 'metadata-update' });
710
+ if (event === 'change') {
711
+ const slug = path.basename(filePath, '.md');
712
+ broadcast({ type: 'plan-update', slug });
713
+ }
709
714
  }
710
715
  });
711
716