claude-code-kanban 1.14.0 → 1.16.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.14.0",
3
+ "version": "1.16.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
@@ -36,6 +36,8 @@
36
36
  --warning-dim: rgba(240, 180, 41, 0.18);
37
37
  --team: #60a5fa;
38
38
  --team-dim: rgba(96, 165, 250, 0.18);
39
+ --plan: #86a886;
40
+ --plan-dim: rgba(134, 168, 134, 0.18);
39
41
  --mono: 'IBM Plex Mono', monospace;
40
42
  --serif: 'Playfair Display', serif;
41
43
  }
@@ -426,6 +428,15 @@
426
428
  opacity: 0.7;
427
429
  }
428
430
 
431
+ .session-plan {
432
+ font-size: 10px;
433
+ color: var(--plan);
434
+ margin-top: 2px;
435
+ white-space: nowrap;
436
+ overflow: hidden;
437
+ text-overflow: ellipsis;
438
+ }
439
+
429
440
  .session-progress {
430
441
  display: flex;
431
442
  align-items: center;
@@ -1274,6 +1285,26 @@
1274
1285
  border-color: var(--team);
1275
1286
  }
1276
1287
 
1288
+ .plan-indicator {
1289
+ width: 24px;
1290
+ height: 24px;
1291
+ display: inline-flex;
1292
+ align-items: center;
1293
+ justify-content: center;
1294
+ background: var(--plan-dim);
1295
+ border: 1px solid transparent;
1296
+ border-radius: 4px;
1297
+ color: var(--plan);
1298
+ cursor: pointer;
1299
+ flex-shrink: 0;
1300
+ transition: all 0.15s ease;
1301
+ }
1302
+
1303
+ .plan-indicator:hover {
1304
+ background: var(--plan-dim);
1305
+ border-color: var(--plan);
1306
+ }
1307
+
1277
1308
  /* Task owner badge */
1278
1309
  .task-owner-badge {
1279
1310
  display: inline-flex;
@@ -1375,6 +1406,8 @@
1375
1406
  --success-dim: rgba(26, 138, 90, 0.15);
1376
1407
  --warning: #b07d0a;
1377
1408
  --warning-dim: rgba(176, 125, 10, 0.15);
1409
+ --plan: #5a7a5a;
1410
+ --plan-dim: rgba(90, 122, 90, 0.15);
1378
1411
  }
1379
1412
 
1380
1413
  body.light::before {
@@ -1711,6 +1744,8 @@
1711
1744
  --success-dim: rgba(26, 138, 90, 0.15);
1712
1745
  --warning: #b07d0a;
1713
1746
  --warning-dim: rgba(176, 125, 10, 0.15);
1747
+ --plan: #5a7a5a;
1748
+ --plan-dim: rgba(90, 122, 90, 0.15);
1714
1749
  }
1715
1750
 
1716
1751
  body:not(.dark-forced)::before {
@@ -2444,7 +2479,7 @@
2444
2479
 
2445
2480
  let filteredSessions = sessions;
2446
2481
  if (sessionFilter === 'active') {
2447
- filteredSessions = filteredSessions.filter(s => s.pending > 0 || s.inProgress > 0);
2482
+ filteredSessions = filteredSessions.filter(s => s.pending > 0 || s.inProgress > 0 || s.hasPlan);
2448
2483
  }
2449
2484
  if (filterProject) {
2450
2485
  filteredSessions = filteredSessions.filter(s => matchesProjectFilter(s.project));
@@ -2530,10 +2565,12 @@
2530
2565
  <div class="session-name">${escapeHtml(primaryName)}</div>
2531
2566
  ${secondaryName ? `<div class="session-secondary">${escapeHtml(secondaryName)}</div>` : ''}
2532
2567
  ${gitBranch ? `<div class="session-branch">${gitBranch}</div>` : ''}
2568
+ ${session.planTitle ? `<div class="session-plan">${escapeHtml(session.planTitle)}</div>` : ''}
2533
2569
  <div class="session-progress">
2534
2570
  <span class="session-indicators">
2535
2571
  ${isTeam ? `<span class="team-badge" title="${memberCount} team members"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>${memberCount}</span>` : ''}
2536
2572
  ${(isTeam || session.project) ? `<span class="team-info-btn" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="View session info">ℹ</span>` : ''}
2573
+ ${session.hasPlan ? `<span class="plan-indicator" onclick="event.stopPropagation(); openPlanForSession('${session.id}')" title="View plan"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>` : ''}
2537
2574
  ${hasInProgress ? '<span class="pulse"></span>' : ''}
2538
2575
  </span>
2539
2576
  <div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
package/server.js CHANGED
@@ -206,7 +206,7 @@ function loadSessionMetadata() {
206
206
  console.error('Error loading session metadata:', e);
207
207
  }
208
208
 
209
- // For team sessions with no JSONL match, try team config for project path
209
+ // For team sessions with no JSONL match, resolve from team config + parent session
210
210
  if (existsSync(TASKS_DIR)) {
211
211
  const taskDirs = readdirSync(TASKS_DIR, { withFileTypes: true })
212
212
  .filter(d => d.isDirectory());
@@ -214,11 +214,18 @@ function loadSessionMetadata() {
214
214
  if (!metadata[dir.name]) {
215
215
  const teamConfig = loadTeamConfig(dir.name);
216
216
  if (teamConfig) {
217
+ const parentMeta = teamConfig.leadSessionId ? metadata[teamConfig.leadSessionId] : null;
218
+ const leadMember = teamConfig.members?.find(m => m.agentId === teamConfig.leadAgentId) || teamConfig.members?.[0];
219
+ const project = parentMeta?.project || leadMember?.cwd || teamConfig.working_dir || null;
220
+
217
221
  metadata[dir.name] = {
218
- customTitle: null,
219
- slug: null,
220
- project: teamConfig.working_dir || null,
221
- jsonlPath: null
222
+ customTitle: parentMeta?.customTitle || null,
223
+ slug: parentMeta?.slug || null,
224
+ project,
225
+ jsonlPath: parentMeta?.jsonlPath || null,
226
+ description: parentMeta?.description || teamConfig.description || null,
227
+ gitBranch: parentMeta?.gitBranch || null,
228
+ created: parentMeta?.created || null
222
229
  };
223
230
  }
224
231
  }
@@ -233,6 +240,19 @@ function loadSessionMetadata() {
233
240
  /**
234
241
  * Get display name for a session: customTitle > slug > null (frontend shows UUID)
235
242
  */
243
+ function getPlanInfo(slug) {
244
+ if (!slug) return { hasPlan: false, planTitle: null };
245
+ const planPath = path.join(PLANS_DIR, `${slug}.md`);
246
+ if (!existsSync(planPath)) return { hasPlan: false, planTitle: null };
247
+ try {
248
+ const head = readFileSync(planPath, 'utf8').slice(0, 512);
249
+ const match = head.match(/^#\s+(.+)$/m);
250
+ return { hasPlan: true, planTitle: match ? match[1].trim() : null };
251
+ } catch (e) {
252
+ return { hasPlan: true, planTitle: null };
253
+ }
254
+ }
255
+
236
256
  function getSessionDisplayName(sessionId, meta) {
237
257
  if (meta?.customTitle) return meta.customTitle;
238
258
  if (meta?.slug) return meta.slug;
@@ -296,6 +316,7 @@ app.get('/api/sessions', async (req, res) => {
296
316
 
297
317
  const isTeam = isTeamSession(entry.name);
298
318
  const memberCount = isTeam ? (loadTeamConfig(entry.name)?.members?.length || 0) : 0;
319
+ const planInfo = getPlanInfo(meta.slug);
299
320
 
300
321
  sessionsMap.set(entry.name, {
301
322
  id: entry.name,
@@ -311,7 +332,8 @@ app.get('/api/sessions', async (req, res) => {
311
332
  createdAt: meta.created || null,
312
333
  modifiedAt: modifiedAt,
313
334
  isTeam,
314
- memberCount
335
+ memberCount,
336
+ ...planInfo
315
337
  });
316
338
  }
317
339
  }
@@ -324,6 +346,7 @@ app.get('/api/sessions', async (req, res) => {
324
346
  if (!modifiedAt && meta.jsonlPath) {
325
347
  try { modifiedAt = statSync(meta.jsonlPath).mtime.toISOString(); } catch (e) {}
326
348
  }
349
+ const planInfo = getPlanInfo(meta.slug);
327
350
  sessionsMap.set(sessionId, {
328
351
  id: sessionId,
329
352
  name: getSessionDisplayName(sessionId, meta),
@@ -338,7 +361,8 @@ app.get('/api/sessions', async (req, res) => {
338
361
  createdAt: meta.created || null,
339
362
  modifiedAt: modifiedAt || new Date(0).toISOString(),
340
363
  isTeam: false,
341
- memberCount: 0
364
+ memberCount: 0,
365
+ ...planInfo
342
366
  });
343
367
  }
344
368
  }
@@ -672,6 +696,19 @@ projectsWatcher.on('all', (event, filePath) => {
672
696
  }
673
697
  });
674
698
 
699
+ const plansWatcher = chokidar.watch(PLANS_DIR, {
700
+ persistent: true,
701
+ ignoreInitial: true,
702
+ depth: 0
703
+ });
704
+
705
+ plansWatcher.on('all', (event, filePath) => {
706
+ if ((event === 'add' || event === 'change' || event === 'unlink') && filePath.endsWith('.md')) {
707
+ lastMetadataRefresh = 0;
708
+ broadcast({ type: 'metadata-update' });
709
+ }
710
+ });
711
+
675
712
  app.use('/api', (req, res) => {
676
713
  res.status(404).json({ error: 'Not found' });
677
714
  });