claude-code-kanban 4.6.0 → 4.7.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/lib/parsers.js +35 -2
- package/package.json +1 -1
- package/public/app.js +48 -9
- package/public/style.css +4 -16
package/lib/parsers.js
CHANGED
|
@@ -442,13 +442,20 @@ function getSystemMessageLabel(text) {
|
|
|
442
442
|
const statusMatch = text.match(/<status>([^<]+)<\/status>/);
|
|
443
443
|
return statusMatch ? `Background task ${statusMatch[1]}` : 'Background task notification';
|
|
444
444
|
}
|
|
445
|
-
|
|
445
|
+
// The "Compacted (ctrl+o…)" stdout echo is redundant with the isCompactSummary
|
|
446
|
+
// chip (modern Claude Code stores the summary inline). Skipping it lets the one
|
|
447
|
+
// summary-bearing chip stand alone instead of trailing a bare marker that the
|
|
448
|
+
// resume prompt sorts away from, preventing collapse.
|
|
449
|
+
if (text.includes('<local-command-stdout>') && text.includes('Compacted')) return '__skip__';
|
|
446
450
|
if (text.includes('<local-command-stdout>')) return 'Command output';
|
|
447
451
|
if (text.includes('<local-command-caveat>')) return 'System notification';
|
|
448
452
|
if (text.includes('.output completed') && text.includes('Background command')) return 'Background task completed';
|
|
449
453
|
if (text.startsWith('This session is being continued from a previous conversation')) return '__skip__';
|
|
450
454
|
if (text.includes('<command-name>/clear</command-name>')) return '__skip__';
|
|
451
|
-
|
|
455
|
+
// The /compact trigger record is redundant: the boundary stdout + isCompactSummary
|
|
456
|
+
// record already render as one expandable "Compacted" chip. Skip it so the chip
|
|
457
|
+
// collapses cleanly instead of leaving a separate marker where the user typed it.
|
|
458
|
+
if (text.includes('<command-name>/compact</command-name>')) return '__skip__';
|
|
452
459
|
return null;
|
|
453
460
|
}
|
|
454
461
|
|
|
@@ -641,6 +648,26 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
641
648
|
}
|
|
642
649
|
}
|
|
643
650
|
} else if (obj.type === 'user' && obj.message?.role === 'user' && !obj.isMeta) {
|
|
651
|
+
if (obj.isCompactSummary) {
|
|
652
|
+
// Surface the compaction summary as a single expandable "Compacted"
|
|
653
|
+
// chip (compactSummary), never a raw bubble. The boundary marker plus
|
|
654
|
+
// the /compact command/stdout records collapse into this one entry below.
|
|
655
|
+
const raw = typeof obj.message.content === 'string'
|
|
656
|
+
? obj.message.content
|
|
657
|
+
: Array.isArray(obj.message.content)
|
|
658
|
+
? obj.message.content.filter((b) => b.type === 'text').map((b) => b.text).join('\n')
|
|
659
|
+
: '';
|
|
660
|
+
const summary = raw
|
|
661
|
+
.replace(/^This session is being continued[^\n]*\n+(The summary below[^\n]*\n+)?/i, '')
|
|
662
|
+
.trim();
|
|
663
|
+
messages.push({
|
|
664
|
+
type: 'user',
|
|
665
|
+
systemLabel: 'Compacted',
|
|
666
|
+
compactSummary: summary || null,
|
|
667
|
+
timestamp: obj.timestamp
|
|
668
|
+
});
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
644
671
|
if (typeof obj.message.content === 'string') {
|
|
645
672
|
const t = obj.message.content;
|
|
646
673
|
const tmMatch = t.match(/<teammate-message\s+([^>]*)>([\s\S]*?)<\/teammate-message>/);
|
|
@@ -801,6 +828,12 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
801
828
|
messages.sort((a, b) => (a.timestamp || '').localeCompare(b.timestamp || ''));
|
|
802
829
|
for (let i = messages.length - 1; i > 0; i--) {
|
|
803
830
|
if (messages[i].systemLabel === 'Compacted' && messages[i - 1].systemLabel === 'Compacted') {
|
|
831
|
+
// Carry the summary body onto the surviving chip so collapsing the
|
|
832
|
+
// boundary + /compact command + stdout + isCompactSummary records into
|
|
833
|
+
// one entry never drops the expandable summary.
|
|
834
|
+
if (messages[i].compactSummary && !messages[i - 1].compactSummary) {
|
|
835
|
+
messages[i - 1].compactSummary = messages[i].compactSummary;
|
|
836
|
+
}
|
|
804
837
|
messages.splice(i, 1);
|
|
805
838
|
}
|
|
806
839
|
}
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -511,6 +511,8 @@ function setActivityFilter(kind) {
|
|
|
511
511
|
// waiting is a sub-state of active — couple them so one click covers all running sessions
|
|
512
512
|
toggleActivityKind('active');
|
|
513
513
|
toggleActivityKind('waiting');
|
|
514
|
+
// Expand sections that have active sessions so they're visible on click
|
|
515
|
+
if (activityFilter.has('active')) expandActiveGroups();
|
|
514
516
|
} else {
|
|
515
517
|
toggleActivityKind(kind);
|
|
516
518
|
}
|
|
@@ -2829,6 +2831,8 @@ function renderSessions() {
|
|
|
2829
2831
|
|
|
2830
2832
|
// Group active sessions by project
|
|
2831
2833
|
if (sessionFilter === 'active') {
|
|
2834
|
+
// Auto-expand a collapsed section when newly-active work lands in it (live refresh).
|
|
2835
|
+
expandActiveGroups({ onlyNew: true });
|
|
2832
2836
|
const groups = new Map();
|
|
2833
2837
|
const ungrouped = [];
|
|
2834
2838
|
for (const session of filteredSessions) {
|
|
@@ -3212,8 +3216,7 @@ async function onColumnDrop(e) {
|
|
|
3212
3216
|
|
|
3213
3217
|
//#region KEYBOARD_NAV
|
|
3214
3218
|
function selectTask(taskId, sessionId) {
|
|
3215
|
-
|
|
3216
|
-
if (prev) prev.classList.remove('selected');
|
|
3219
|
+
clearTaskSelection();
|
|
3217
3220
|
selectedTaskId = taskId;
|
|
3218
3221
|
selectedSessionId = sessionId;
|
|
3219
3222
|
taskHighlightDimmed = false;
|
|
@@ -3330,6 +3333,11 @@ function clearKbSelection() {
|
|
|
3330
3333
|
if (prev) prev.classList.remove('kb-selected');
|
|
3331
3334
|
}
|
|
3332
3335
|
|
|
3336
|
+
function clearTaskSelection() {
|
|
3337
|
+
const prev = document.querySelector('.task-card.selected');
|
|
3338
|
+
if (prev) prev.classList.remove('selected');
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3333
3341
|
function selectSessionByIndex(idx, items) {
|
|
3334
3342
|
items = items || getNavigableItems();
|
|
3335
3343
|
if (items.length === 0) return;
|
|
@@ -3368,11 +3376,37 @@ function setGroupCollapsed(header, collapsed) {
|
|
|
3368
3376
|
header.classList.toggle('collapsed', collapsed);
|
|
3369
3377
|
const container = getGroupSessionsContainer(header);
|
|
3370
3378
|
if (container) container.classList.toggle('collapsed', collapsed);
|
|
3379
|
+
persistCollapsedGroups();
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
function persistCollapsedGroups() {
|
|
3371
3383
|
try {
|
|
3372
3384
|
localStorage.setItem('collapsedGroups', JSON.stringify([...collapsedProjectGroups]));
|
|
3373
3385
|
} catch (_) {}
|
|
3374
3386
|
}
|
|
3375
3387
|
|
|
3388
|
+
let prevActiveSessionIds = null; // null until primed by the first onlyNew pass
|
|
3389
|
+
|
|
3390
|
+
// Expand collapsed project sections that contain active sessions; leave the rest collapsed.
|
|
3391
|
+
// onlyNew: expand only for sessions that became active since the last call, so a section the
|
|
3392
|
+
// user manually collapsed stays collapsed until NEW active work lands in it (live refresh path).
|
|
3393
|
+
// The first onlyNew pass only primes the tracking set — it respects saved collapse state on load.
|
|
3394
|
+
// onlyNew=false force-expands every section with active work (explicit chip click).
|
|
3395
|
+
function expandActiveGroups({ onlyNew = false } = {}) {
|
|
3396
|
+
const primed = prevActiveSessionIds !== null;
|
|
3397
|
+
const activeIds = new Set();
|
|
3398
|
+
let changed = false;
|
|
3399
|
+
for (const s of sessions) {
|
|
3400
|
+
if (!isSessionActive(s)) continue;
|
|
3401
|
+
activeIds.add(s.id);
|
|
3402
|
+
if (onlyNew && (!primed || prevActiveSessionIds.has(s.id))) continue;
|
|
3403
|
+
if (collapsedProjectGroups.delete(s.project || '__ungrouped__')) changed = true;
|
|
3404
|
+
}
|
|
3405
|
+
prevActiveSessionIds = activeIds;
|
|
3406
|
+
if (changed) persistCollapsedGroups();
|
|
3407
|
+
// renderSessions() re-renders headers/containers from the updated set
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3376
3410
|
function isGroupHeader(el) {
|
|
3377
3411
|
return el.classList.contains('project-group-header') || el.classList.contains('pinned-sub-header');
|
|
3378
3412
|
}
|
|
@@ -3434,8 +3468,7 @@ function setFocusZone(zone) {
|
|
|
3434
3468
|
// Clear all zone visuals
|
|
3435
3469
|
sidebar.classList.remove('sidebar-focused');
|
|
3436
3470
|
clearKbSelection();
|
|
3437
|
-
|
|
3438
|
-
if (selCard) selCard.classList.remove('selected');
|
|
3471
|
+
clearTaskSelection();
|
|
3439
3472
|
|
|
3440
3473
|
focusZone = zone;
|
|
3441
3474
|
if (zone === 'sidebar') {
|
|
@@ -3761,10 +3794,8 @@ async function addNote(event, taskId, sessionId) {
|
|
|
3761
3794
|
function closeDetailPanel() {
|
|
3762
3795
|
detailPanel.classList.remove('visible');
|
|
3763
3796
|
document.getElementById('delete-task-btn').style.display = 'none';
|
|
3764
|
-
// Keep the task selected
|
|
3765
|
-
taskHighlightDimmed =
|
|
3766
|
-
const sel = document.querySelector('.task-card.selected');
|
|
3767
|
-
if (sel) sel.classList.remove('selected');
|
|
3797
|
+
// Keep the task selected AND highlighted after closing (no dim-off).
|
|
3798
|
+
taskHighlightDimmed = false;
|
|
3768
3799
|
}
|
|
3769
3800
|
|
|
3770
3801
|
let deleteTaskId = null;
|
|
@@ -4618,7 +4649,10 @@ document.addEventListener('keydown', (e) => {
|
|
|
4618
4649
|
return;
|
|
4619
4650
|
}
|
|
4620
4651
|
if (e.key === 'Escape') {
|
|
4621
|
-
|
|
4652
|
+
// Plain unfocus — drop the keyboard cursor without jumping into the board
|
|
4653
|
+
document.querySelector('.sidebar').classList.remove('sidebar-focused');
|
|
4654
|
+
clearKbSelection();
|
|
4655
|
+
focusZone = 'board';
|
|
4622
4656
|
return;
|
|
4623
4657
|
}
|
|
4624
4658
|
}
|
|
@@ -4669,6 +4703,11 @@ document.addEventListener('keydown', (e) => {
|
|
|
4669
4703
|
if (detailPanel.classList.contains('visible')) closeDetailPanel();
|
|
4670
4704
|
else if (agentLogMode) exitAgentLogMode();
|
|
4671
4705
|
else if (messagePanelOpen) toggleMessagePanel();
|
|
4706
|
+
else {
|
|
4707
|
+
// Nothing open — plain unfocus: drop the task-card selection highlight
|
|
4708
|
+
clearTaskSelection();
|
|
4709
|
+
selectedTaskId = null;
|
|
4710
|
+
}
|
|
4672
4711
|
return;
|
|
4673
4712
|
}
|
|
4674
4713
|
|
package/public/style.css
CHANGED
|
@@ -454,22 +454,15 @@ body::before {
|
|
|
454
454
|
padding: 12px 14px;
|
|
455
455
|
margin-bottom: 4px;
|
|
456
456
|
background: transparent;
|
|
457
|
-
border: 1px solid
|
|
457
|
+
border: 1px solid var(--border);
|
|
458
458
|
border-radius: 8px;
|
|
459
459
|
text-align: left;
|
|
460
460
|
cursor: pointer;
|
|
461
461
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
462
|
-
box-shadow:
|
|
463
|
-
0 1px 3px rgba(0, 0, 0, 0.12),
|
|
464
|
-
0 1px 2px rgba(0, 0, 0, 0.08);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
.session-item:hover {
|
|
468
|
-
background: var(--bg-hover);
|
|
469
462
|
}
|
|
470
463
|
|
|
471
464
|
.session-item.active {
|
|
472
|
-
background: var(--bg-
|
|
465
|
+
background: var(--bg-deep);
|
|
473
466
|
border-color: var(--accent);
|
|
474
467
|
}
|
|
475
468
|
|
|
@@ -1173,7 +1166,7 @@ body::before {
|
|
|
1173
1166
|
height: 100vh;
|
|
1174
1167
|
background: var(--bg-surface);
|
|
1175
1168
|
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
1176
|
-
box-shadow: -
|
|
1169
|
+
box-shadow: -1px 0 12px rgba(0, 0, 0, 0.04);
|
|
1177
1170
|
display: none;
|
|
1178
1171
|
flex-direction: column;
|
|
1179
1172
|
z-index: 100;
|
|
@@ -2011,7 +2004,7 @@ body::before {
|
|
|
2011
2004
|
height: 100vh;
|
|
2012
2005
|
background: var(--bg-deep);
|
|
2013
2006
|
border-left: 1px solid color-mix(in srgb, var(--border) 35%, transparent);
|
|
2014
|
-
box-shadow: -
|
|
2007
|
+
box-shadow: -1px 0 12px rgba(0, 0, 0, 0.04);
|
|
2015
2008
|
display: none;
|
|
2016
2009
|
flex-direction: column;
|
|
2017
2010
|
z-index: 99;
|
|
@@ -4228,11 +4221,6 @@ pre.mermaid svg {
|
|
|
4228
4221
|
width: 0;
|
|
4229
4222
|
}
|
|
4230
4223
|
|
|
4231
|
-
.sidebar-focused .session-item.active {
|
|
4232
|
-
background: transparent;
|
|
4233
|
-
border-color: var(--accent);
|
|
4234
|
-
}
|
|
4235
|
-
|
|
4236
4224
|
.sidebar-focused .session-item.active::before {
|
|
4237
4225
|
width: 0;
|
|
4238
4226
|
}
|