claude-code-kanban 4.22.0 → 4.23.1

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/lib/file-url.js CHANGED
@@ -24,6 +24,8 @@ function fileUrlToPath(value) {
24
24
  if (typeof value !== 'string' || !/^file:/i.test(value)) return value;
25
25
  // Backslashes are not legal in a URL, so this cannot corrupt a well-formed one.
26
26
  let url = value.replace(/\\/g, '/');
27
+ // A scheme with no path body: POSIX Node resolves it to "/" rather than throwing.
28
+ if (/^file:\/*$/i.test(url)) return value;
27
29
  const drive = DRIVE_URL.exec(url);
28
30
  if (drive) url = `file:///${drive[1]}`;
29
31
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "4.22.0",
3
+ "version": "4.23.1",
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/app.js CHANGED
@@ -1460,8 +1460,8 @@ function getToolIcon(toolName) {
1460
1460
  }
1461
1461
  const AGENT_LOG_ICON =
1462
1462
  '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>';
1463
- function agentLogButton(agentId) {
1464
- return `<button class="msg-agent-log-btn" onclick="event.stopPropagation();viewAgentLog('${escAttrJs(agentId)}')" title="View agent log">${AGENT_LOG_ICON}</button>`;
1463
+ function agentLogButton(agentId, cls = 'msg-agent-log-btn') {
1464
+ return `<button class="${escapeHtml(cls)}" onclick="event.stopPropagation();viewAgentLog('${escAttrJs(agentId)}')" title="View agent log">${AGENT_LOG_ICON}</button>`;
1465
1465
  }
1466
1466
 
1467
1467
  function getPinId(m) {
@@ -2763,9 +2763,14 @@ function renderAgentFooter() {
2763
2763
  const agentColor = resolveNamedColor(a.color);
2764
2764
  const colorStyle = agentColor ? ` style="border-left:3px solid ${agentColor.color}"` : '';
2765
2765
  const selectedClass = a.agentId === currentAgentModalId ? ' selected' : '';
2766
+ const cardModel = shortModelName(a.model);
2767
+ const modelChipHtml = cardModel
2768
+ ? `<span class="agent-card-model" title="${escapeHtml(a.model)}">${escapeHtml(cardModel)}</span>`
2769
+ : '';
2766
2770
  return `<div class="agent-card${selectedClass}" data-agent-id="${escapeHtml(a.agentId)}"${colorStyle} onclick="showAgentModal('${escAttrJs(a.agentId)}')">
2771
+ ${agentLogButton(a.agentId, 'agent-card-log-btn')}
2767
2772
  <div class="agent-type-row">${typeNs ? `<span class="agent-type-ns">${escapeHtml(typeNs)}</span>` : ''}<span class="agent-type-name">${escapeHtml(typeName)}</span>${nameBadgeHtml}</div>
2768
- <div class="agent-status-row"><span class="agent-dot ${a.status}"></span><span class="agent-status">${statusText}</span></div>
2773
+ <div class="agent-status-row"><span class="agent-dot ${a.status}"></span><span class="agent-status">${statusText}</span>${modelChipHtml}</div>
2769
2774
  ${msgHtml}
2770
2775
  </div>`;
2771
2776
  })
@@ -3137,9 +3142,9 @@ function renderSessions() {
3137
3142
  const total = session.taskCount;
3138
3143
  const percent = total > 0 ? Math.round((session.completed / total) * 100) : 0;
3139
3144
  const isActive = session.id === currentSessionId && viewMode === 'session';
3140
- const hasInProgress = session.inProgress > 0;
3141
- const isLive =
3142
- hasInProgress || (session.modifiedAt && Date.now() - new Date(session.modifiedAt).getTime() <= LIVE_INDICATOR_MS);
3145
+ // hasRecentLog carries the server's registry-idle correction: an open-but-idle
3146
+ // terminal keeps touching its JSONL, so mtime alone is not proof of activity.
3147
+ const isLive = session.hasRecentLog && Date.now() - Date.parse(session.modifiedAt) <= LIVE_INDICATOR_MS;
3143
3148
  const rawName = session.name || session.id;
3144
3149
  const sessionName = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawName)
3145
3150
  ? rawName.slice(0, 8)
@@ -4612,8 +4617,6 @@ function activateSelectedSession(items) {
4612
4617
 
4613
4618
  function setFocusZone(zone) {
4614
4619
  const sidebar = document.querySelector('.sidebar');
4615
- // Clear all zone visuals
4616
- sidebar.classList.remove('sidebar-focused');
4617
4620
  clearKbSelection();
4618
4621
  clearTaskSelection();
4619
4622
 
@@ -4623,7 +4626,6 @@ function setFocusZone(zone) {
4623
4626
  sidebar.classList.remove('collapsed');
4624
4627
  localStorage.setItem('sidebar-collapsed', false);
4625
4628
  }
4626
- sidebar.classList.add('sidebar-focused');
4627
4629
  const items = getNavigableItems();
4628
4630
  if (items.length > 0) {
4629
4631
  const activeIdx = items.findIndex((el) => el.classList.contains('active'));
@@ -5767,7 +5769,6 @@ document.addEventListener('keydown', (e) => {
5767
5769
  }
5768
5770
  if (e.key === 'Escape') {
5769
5771
  // Plain unfocus — drop the keyboard cursor without jumping into the board
5770
- document.querySelector('.sidebar').classList.remove('sidebar-focused');
5771
5772
  clearKbSelection();
5772
5773
  focusZone = 'board';
5773
5774
  return;
package/public/style.css CHANGED
@@ -562,6 +562,7 @@ body::before {
562
562
  }
563
563
 
564
564
  .session-item {
565
+ position: relative;
565
566
  display: block;
566
567
  width: 100%;
567
568
  padding: 13px 15px;
@@ -574,11 +575,63 @@ body::before {
574
575
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
575
576
  }
576
577
 
578
+ /* `.active` is the open session, `.kb-selected` the keyboard cursor. The cursor
579
+ must win when both land on one card, which is why it is ordered second. */
577
580
  .session-item.active {
578
581
  background: var(--bg-deep);
579
582
  border-color: var(--accent);
580
583
  }
581
584
 
585
+ .session-item.kb-selected {
586
+ background: var(--bg-elevated);
587
+ border-color: var(--accent);
588
+ box-shadow: 0 0 0 1px var(--accent-dim);
589
+ }
590
+
591
+ .session-item.stale {
592
+ opacity: 0.85;
593
+ }
594
+
595
+ .session-item.stale:hover,
596
+ .session-item.stale.active,
597
+ .session-item.stale.kb-selected,
598
+ .session-item.stale:has(.session-pin-btn.sticky) {
599
+ opacity: 1;
600
+ }
601
+
602
+ .session-item.stale .session-time,
603
+ .session-item.warm .session-time {
604
+ display: inline-flex;
605
+ align-items: center;
606
+ gap: 6px;
607
+ }
608
+
609
+ .session-item.stale .session-time::before,
610
+ .session-item.warm .session-time::before {
611
+ content: '';
612
+ width: 6px;
613
+ height: 6px;
614
+ border-radius: 50%;
615
+ flex-shrink: 0;
616
+ }
617
+
618
+ .session-item.warm .session-time {
619
+ color: var(--success);
620
+ }
621
+
622
+ .session-item.warm .session-time::before {
623
+ background: var(--success);
624
+ }
625
+
626
+ .session-item.stale .session-time {
627
+ color: var(--text-muted);
628
+ }
629
+
630
+ .session-item.stale .session-time::before {
631
+ background: transparent;
632
+ border: 1px solid var(--text-muted);
633
+ }
634
+
582
635
  .session-name {
583
636
  font-size: 13px;
584
637
  color: var(--text-primary);
@@ -590,12 +643,11 @@ body::before {
590
643
  }
591
644
 
592
645
  .session-indicators .pulse {
593
- width: 8px;
594
- height: 8px;
646
+ width: 6px;
647
+ height: 6px;
595
648
  background: var(--accent);
596
649
  border-radius: 50%;
597
650
  animation: pulse 2s ease-in-out infinite;
598
- box-shadow: 0 0 12px var(--accent-glow);
599
651
  }
600
652
 
601
653
  @keyframes pulse {
@@ -694,6 +746,10 @@ body::before {
694
746
  transition: width 0.5s ease-out;
695
747
  }
696
748
 
749
+ .session-item:not(.active) .progress-fill {
750
+ background: color-mix(in srgb, var(--text-tertiary) 55%, transparent);
751
+ }
752
+
697
753
  .progress-text {
698
754
  font-size: 11px;
699
755
  font-weight: 500;
@@ -3597,6 +3653,25 @@ body::before {
3597
3653
  .agent-card:hover {
3598
3654
  border-color: color-mix(in srgb, var(--accent) 60%, var(--border));
3599
3655
  }
3656
+ .agent-card-log-btn {
3657
+ position: absolute;
3658
+ top: 4px;
3659
+ right: 4px;
3660
+ display: flex;
3661
+ padding: 2px;
3662
+ background: none;
3663
+ border: none;
3664
+ color: var(--text-muted);
3665
+ cursor: pointer;
3666
+ opacity: 0.45;
3667
+ transition:
3668
+ opacity 0.15s,
3669
+ color 0.15s;
3670
+ }
3671
+ .agent-card-log-btn:hover {
3672
+ opacity: 1;
3673
+ color: var(--accent);
3674
+ }
3600
3675
  .agent-card.selected,
3601
3676
  .agent-card.selected:hover {
3602
3677
  background: var(--bg-elevated);
@@ -3662,6 +3737,20 @@ body::before {
3662
3737
  font-size: 11px;
3663
3738
  color: var(--text-tertiary);
3664
3739
  }
3740
+ .agent-card-model {
3741
+ margin-left: auto;
3742
+ padding: 1px 6px;
3743
+ font-family: var(--font-mono, monospace);
3744
+ font-size: 9.5px;
3745
+ line-height: 1.4;
3746
+ color: var(--text-tertiary);
3747
+ border: 1px solid var(--border);
3748
+ border-radius: 999px;
3749
+ white-space: nowrap;
3750
+ overflow: hidden;
3751
+ text-overflow: ellipsis;
3752
+ max-width: 45%;
3753
+ }
3665
3754
  .agent-message {
3666
3755
  font-size: 11px;
3667
3756
  color: var(--text-muted);
@@ -5415,79 +5504,6 @@ pre.mermaid svg {
5415
5504
  color: var(--accent);
5416
5505
  }
5417
5506
 
5418
- /* Session list hover accent bar */
5419
- .session-item {
5420
- position: relative;
5421
- }
5422
-
5423
- .session-item::before {
5424
- content: '';
5425
- position: absolute;
5426
- left: 0;
5427
- top: 50%;
5428
- transform: translateY(-50%);
5429
- width: 0;
5430
- height: 60%;
5431
- background: var(--accent);
5432
- border-radius: 0 2px 2px 0;
5433
- transition: width 0.15s ease;
5434
- }
5435
-
5436
- .session-item:hover::before {
5437
- width: 0;
5438
- }
5439
-
5440
- .sidebar-focused .session-item.active::before {
5441
- width: 0;
5442
- }
5443
-
5444
- .session-item.kb-selected,
5445
- .session-item.kb-selected:hover,
5446
- .session-item.active.kb-selected {
5447
- background: var(--bg-elevated);
5448
- border-color: var(--accent);
5449
- box-shadow: 0 0 0 1px var(--accent-dim);
5450
- }
5451
-
5452
- .session-item.kb-selected::before {
5453
- width: 0;
5454
- }
5455
- .session-item.stale .session-time,
5456
- .session-item.warm .session-time {
5457
- display: inline-flex;
5458
- align-items: center;
5459
- gap: 6px;
5460
- }
5461
- .session-item.stale .session-time::before,
5462
- .session-item.warm .session-time::before {
5463
- content: '';
5464
- width: 6px;
5465
- height: 6px;
5466
- border-radius: 50%;
5467
- flex-shrink: 0;
5468
- }
5469
- .session-item.warm .session-time {
5470
- color: var(--success);
5471
- }
5472
- .session-item.warm .session-time::before {
5473
- background: var(--success);
5474
- }
5475
- .session-item.stale {
5476
- opacity: 0.85;
5477
- }
5478
- .session-item.stale:hover,
5479
- .session-item.stale.active,
5480
- .session-item.stale.kb-selected,
5481
- .session-item.stale:has(.session-pin-btn.sticky) {
5482
- opacity: 1;
5483
- }
5484
- .session-item.stale .session-time {
5485
- color: var(--text-muted);
5486
- }
5487
- .session-item.stale .session-time::before {
5488
- background: transparent;
5489
- border: 1px solid var(--text-muted);
5490
- }
5491
5507
  /* #endregion */
5492
5508
 
5493
5509
  /* #region SESSION_GROUPS */