claude-code-kanban 2.0.0-rc.1 → 2.0.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/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
  [![license](https://img.shields.io/npm/l/claude-code-kanban)](LICENSE)
5
5
  [![npm downloads](https://img.shields.io/npm/dm/claude-code-kanban)](https://www.npmjs.com/package/claude-code-kanban)
6
6
 
7
- > Watch Claude Code think, in real time.
7
+ **[Live Demo & Docs](https://nikiforovall.blog/claude-code-kanban/)**
8
+
9
+ > Watch Claude Code work, in real time.
8
10
 
9
11
  ![Dark mode](assets/screenshot-dark-v2.png)
10
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.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
@@ -366,14 +366,49 @@
366
366
  }
367
367
 
368
368
  /* Live Updates */
369
+ .collapse-chevron {
370
+ width: 14px;
371
+ height: 14px;
372
+ stroke: var(--text-muted);
373
+ fill: none;
374
+ stroke-width: 2;
375
+ transition: transform 0.2s ease;
376
+ flex-shrink: 0;
377
+ }
378
+
379
+ .collapse-chevron.rotated {
380
+ transform: rotate(-90deg);
381
+ }
382
+
383
+ .collapsible-section {
384
+ transition: max-height 0.2s ease, padding 0.2s ease, opacity 0.2s ease;
385
+ overflow: hidden;
386
+ }
387
+
388
+ .collapsible-section.collapsed {
389
+ max-height: 0 !important;
390
+ padding-top: 0 !important;
391
+ padding-bottom: 0 !important;
392
+ opacity: 0;
393
+ overflow: hidden;
394
+ }
395
+
369
396
  .live-updates {
370
- padding: 0 16px 12px;
371
- max-height: 180px;
397
+ padding: 0 16px 8px;
398
+ max-height: 140px;
372
399
  overflow-y: auto;
400
+ transition: max-height 0.2s ease, padding 0.2s ease, opacity 0.2s ease;
401
+ }
402
+
403
+ .live-updates.collapsed {
404
+ max-height: 0;
405
+ padding: 0 16px;
406
+ overflow: hidden;
407
+ opacity: 0;
373
408
  }
374
409
 
375
410
  .live-empty {
376
- padding: 16px;
411
+ padding: 8px;
377
412
  text-align: center;
378
413
  font-size: 11px;
379
414
  color: var(--text-muted);
@@ -382,12 +417,12 @@
382
417
  .live-item {
383
418
  display: flex;
384
419
  align-items: flex-start;
385
- gap: 10px;
386
- padding: 10px 12px;
420
+ gap: 8px;
421
+ padding: 6px 10px;
387
422
  background: var(--bg-deep);
388
423
  border: 1px solid transparent;
389
- border-radius: 8px;
390
- margin-bottom: 4px;
424
+ border-radius: 6px;
425
+ margin-bottom: 3px;
391
426
  cursor: pointer;
392
427
  transition: all 0.15s ease;
393
428
  }
@@ -397,14 +432,14 @@
397
432
  }
398
433
 
399
434
  .live-item .pulse {
400
- width: 8px;
401
- height: 8px;
435
+ width: 6px;
436
+ height: 6px;
402
437
  margin-top: 4px;
403
438
  background: var(--accent);
404
439
  border-radius: 50%;
405
440
  flex-shrink: 0;
406
441
  animation: pulse 2s ease-in-out infinite;
407
- box-shadow: 0 0 12px var(--accent-glow);
442
+ box-shadow: 0 0 8px var(--accent-glow);
408
443
  }
409
444
 
410
445
  .live-item-content {
@@ -413,7 +448,7 @@
413
448
  }
414
449
 
415
450
  .live-item-action {
416
- font-size: 13px;
451
+ font-size: 11px;
417
452
  color: var(--text-primary);
418
453
  white-space: nowrap;
419
454
  overflow: hidden;
@@ -421,9 +456,9 @@
421
456
  }
422
457
 
423
458
  .live-item-session {
424
- font-size: 11px;
459
+ font-size: 10px;
425
460
  color: var(--text-tertiary);
426
- margin-top: 2px;
461
+ margin-top: 1px;
427
462
  white-space: nowrap;
428
463
  overflow: hidden;
429
464
  text-overflow: ellipsis;
@@ -2233,6 +2268,12 @@
2233
2268
  color: var(--text-primary);
2234
2269
  }
2235
2270
 
2271
+ .project-group-header.kb-selected {
2272
+ color: var(--text-primary);
2273
+ background: var(--bg-hover);
2274
+ border-radius: 4px;
2275
+ }
2276
+
2236
2277
  .project-group-header .group-chevron {
2237
2278
  transition: transform 0.15s ease;
2238
2279
  flex-shrink: 0;
@@ -2395,8 +2436,11 @@
2395
2436
 
2396
2437
  <!-- Live Updates -->
2397
2438
  <div class="sidebar-section">
2398
- <div class="section-header">
2439
+ <div class="section-header" onclick="toggleLiveUpdates()" style="cursor: pointer;">
2399
2440
  <span>Live Updates</span>
2441
+ <svg id="live-updates-chevron" class="collapse-chevron" viewBox="0 0 24 24">
2442
+ <path d="M6 9l6 6 6-6"/>
2443
+ </svg>
2400
2444
  </div>
2401
2445
  <div id="live-updates" class="live-updates">
2402
2446
  <div class="live-empty">No active tasks</div>
@@ -2405,46 +2449,50 @@
2405
2449
 
2406
2450
  <!-- Tasks -->
2407
2451
  <div class="sidebar-section flex-1">
2408
- <div class="section-header">
2452
+ <div class="section-header" onclick="toggleSection('sessions-filters', 'sessions-chevron')" style="cursor: pointer;">
2409
2453
  <span>Sessions</span>
2410
- <button onclick="showAllTasks()" style="background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px; padding: 3px 8px; font-size: 10px; color: var(--text-secondary); cursor: pointer; font-family: var(--mono);">All Tasks</button>
2411
- </div>
2412
- <div class="search-container">
2413
- <input
2414
- id="search-input"
2415
- type="text"
2416
- class="search-input"
2417
- placeholder="Search tasks, sessions, projects..."
2418
- oninput="handleSearch(this.value)"
2419
- />
2420
- <button id="search-clear-btn" class="search-clear" onclick="clearSearch()" title="Clear search" aria-label="Clear search">
2421
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2422
- <path d="M18 6L6 18M6 6l12 12"/>
2423
- </svg>
2424
- </button>
2425
- </div>
2426
- <div class="filter-row">
2427
- <select id="project-filter" class="filter-dropdown" onchange="filterByProject(this.value)" aria-label="Filter by project">
2428
- <option value="">All Projects</option>
2429
- </select>
2430
- <select id="session-filter" class="filter-dropdown" onchange="filterBySessions(this.value)" aria-label="Filter by session status">
2431
- <option value="all">All Sessions</option>
2432
- <option value="active">Active Only</option>
2433
- </select>
2454
+ <svg id="sessions-chevron" class="collapse-chevron" viewBox="0 0 24 24">
2455
+ <path d="M6 9l6 6 6-6"/>
2456
+ </svg>
2434
2457
  </div>
2435
- <div class="filter-row">
2436
- <select id="session-limit" class="filter-dropdown" onchange="changeSessionLimit(this.value)" aria-label="Number of sessions to show">
2437
- <option value="10">Show 10</option>
2438
- <option value="20">Show 20</option>
2439
- <option value="50">Show 50</option>
2440
- <option value="all">Show All</option>
2441
- </select>
2442
- <button class="icon-btn reset-btn" onclick="resetState()" title="Reset all filters" aria-label="Reset all filters">
2443
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
2444
- <path d="M3 12a9 9 0 1 1 9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
2445
- <path d="M3 22v-6h6"/>
2446
- </svg>
2447
- </button>
2458
+ <div id="sessions-filters" class="collapsible-section">
2459
+ <div class="search-container">
2460
+ <input
2461
+ id="search-input"
2462
+ type="text"
2463
+ class="search-input"
2464
+ placeholder="Search tasks, sessions, projects..."
2465
+ oninput="handleSearch(this.value)"
2466
+ />
2467
+ <button id="search-clear-btn" class="search-clear" onclick="clearSearch()" title="Clear search" aria-label="Clear search">
2468
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2469
+ <path d="M18 6L6 18M6 6l12 12"/>
2470
+ </svg>
2471
+ </button>
2472
+ </div>
2473
+ <div class="filter-row">
2474
+ <select id="project-filter" class="filter-dropdown" onchange="filterByProject(this.value)" aria-label="Filter by project">
2475
+ <option value="">All Projects</option>
2476
+ </select>
2477
+ <select id="session-filter" class="filter-dropdown" onchange="filterBySessions(this.value)" aria-label="Filter by session status">
2478
+ <option value="all">All Sessions</option>
2479
+ <option value="active">Active Only</option>
2480
+ </select>
2481
+ </div>
2482
+ <div class="filter-row">
2483
+ <select id="session-limit" class="filter-dropdown" onchange="changeSessionLimit(this.value)" aria-label="Number of sessions to show">
2484
+ <option value="10">Show 10</option>
2485
+ <option value="20">Show 20</option>
2486
+ <option value="50">Show 50</option>
2487
+ <option value="all">Show All</option>
2488
+ </select>
2489
+ <button class="icon-btn reset-btn" onclick="resetState()" title="Reset all filters" aria-label="Reset all filters">
2490
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
2491
+ <path d="M3 12a9 9 0 1 1 9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
2492
+ <path d="M3 22v-6h6"/>
2493
+ </svg>
2494
+ </button>
2495
+ </div>
2448
2496
  </div>
2449
2497
  <div id="sessions-list" class="sessions-list"></div>
2450
2498
  </div>
@@ -2943,6 +2991,18 @@
2943
2991
  renderLiveUpdates(activeTasks);
2944
2992
  }
2945
2993
 
2994
+ function toggleSection(containerId, chevronId) {
2995
+ const container = document.getElementById(containerId);
2996
+ const chevron = document.getElementById(chevronId);
2997
+ const collapsed = container.classList.toggle('collapsed');
2998
+ chevron.classList.toggle('rotated', collapsed);
2999
+ localStorage.setItem(containerId + 'Collapsed', collapsed);
3000
+ }
3001
+
3002
+ function toggleLiveUpdates() {
3003
+ toggleSection('live-updates', 'live-updates-chevron');
3004
+ }
3005
+
2946
3006
  function renderLiveUpdates(activeTasks) {
2947
3007
  const container = document.getElementById('live-updates');
2948
3008
 
@@ -3536,7 +3596,7 @@
3536
3596
  const id = `expand-${++_expandIdCounter}`;
3537
3597
  const fontSize = opts.fontSize || '0.8rem';
3538
3598
  const maxHeight = opts.maxHeight || '';
3539
- const btn = `<button onclick="var f=document.getElementById('${id}'),t=this.parentElement.nextElementSibling,expand=f.style.display==='none';f.style.display=expand?'block':'none';t.style.display=expand?'none':'block';this.textContent=expand?'Show less':'Show more'" style="background:none;border:none;color:var(--accent);cursor:pointer;font-size:${fontSize};text-decoration:underline">Show more</button>`;
3599
+ const btn = `<button onclick="var f=document.getElementById('${id}'),t=this.parentElement.nextElementSibling,expand=f.style.display==='none';f.style.display=expand?'block':'none';t.style.display=expand?'none':'block';this.textContent=expand?'Show less':'Show more'" style="background:none;border:none;color:var(--accent);cursor:pointer;font-size:${fontSize};text-decoration:underline;margin-left:6px">Show more</button>`;
3540
3600
  const mhStyle = maxHeight ? `max-height:${maxHeight};` : '';
3541
3601
  const full = `<pre id="${id}" class="msg-detail-pre" style="${mhStyle}overflow:auto;display:none">${fullHtml}</pre>`;
3542
3602
  return { btn, full };
@@ -4047,29 +4107,34 @@
4047
4107
  sessionsList.innerHTML = filteredSessions.map(renderSessionCard).join('');
4048
4108
  }
4049
4109
 
4050
- const items = getSessionItems();
4051
- const activeIdx = items.findIndex(el => el.classList.contains('active'));
4110
+ const navItems = getNavigableItems();
4111
+ const allSessions = getSessionItems();
4112
+ const activeIdx = allSessions.findIndex(el => el.classList.contains('active'));
4052
4113
  if (activeIdx >= 0 && (selectedSessionIdx < 0 || sessionJustSelected)) {
4053
- selectedSessionIdx = activeIdx;
4054
- selectedSessionKbId = items[activeIdx].dataset.sessionId || null;
4114
+ const navIdx = navItems.indexOf(allSessions[activeIdx]);
4115
+ selectedSessionIdx = navIdx >= 0 ? navIdx : 0;
4116
+ selectedSessionKbId = allSessions[activeIdx].dataset.sessionId || null;
4055
4117
  sessionJustSelected = false;
4056
4118
  }
4057
4119
 
4058
4120
  if (selectedSessionKbId && focusZone === 'sidebar') {
4059
- const restoredIdx = items.findIndex(el => el.dataset.sessionId === selectedSessionKbId);
4121
+ const restoredIdx = navItems.findIndex(el =>
4122
+ getKbId(el) === selectedSessionKbId
4123
+ );
4060
4124
  if (restoredIdx >= 0) {
4061
4125
  selectedSessionIdx = restoredIdx;
4062
- items[restoredIdx].classList.add('kb-selected');
4126
+ navItems[restoredIdx].classList.add('kb-selected');
4063
4127
  } else {
4064
4128
  selectedSessionIdx = -1;
4065
4129
  selectedSessionKbId = null;
4066
4130
  }
4067
4131
  } else if (focusZone === 'sidebar' && selectedSessionIdx >= 0) {
4068
- if (items.length > 0) {
4069
- const clamped = Math.min(selectedSessionIdx, items.length - 1);
4132
+ if (navItems.length > 0) {
4133
+ const clamped = Math.min(selectedSessionIdx, navItems.length - 1);
4070
4134
  selectedSessionIdx = clamped;
4071
- selectedSessionKbId = items[clamped].dataset.sessionId || null;
4072
- items[clamped].classList.add('kb-selected');
4135
+ const el = navItems[clamped];
4136
+ selectedSessionKbId = getKbId(el);
4137
+ el.classList.add('kb-selected');
4073
4138
  } else {
4074
4139
  selectedSessionIdx = -1;
4075
4140
  selectedSessionKbId = null;
@@ -4303,31 +4368,71 @@
4303
4368
  }
4304
4369
  }
4305
4370
 
4371
+ function getKbId(el) {
4372
+ return el.dataset.sessionId || el.dataset.groupPath || null;
4373
+ }
4374
+
4375
+ function getGroupSessionsContainer(header) {
4376
+ let el = header.nextElementSibling;
4377
+ while (el && !el.classList.contains('project-group-sessions')) el = el.nextElementSibling;
4378
+ return el;
4379
+ }
4380
+
4381
+ function getNavigableItems() {
4382
+ const items = [];
4383
+ for (const el of sessionsList.children) {
4384
+ if (el.classList.contains('project-group-header')) {
4385
+ items.push(el);
4386
+ if (!collapsedProjectGroups.has(el.dataset.groupPath)) {
4387
+ const container = getGroupSessionsContainer(el);
4388
+ if (container) {
4389
+ for (const s of container.querySelectorAll('.session-item')) items.push(s);
4390
+ }
4391
+ }
4392
+ } else if (el.classList.contains('session-item')) {
4393
+ items.push(el);
4394
+ }
4395
+ }
4396
+ return items;
4397
+ }
4398
+
4306
4399
  function getSessionItems() {
4307
4400
  return Array.from(sessionsList.querySelectorAll('.session-item'));
4308
4401
  }
4309
4402
 
4310
- function selectSessionByIndex(idx) {
4311
- const items = getSessionItems();
4312
- if (items.length === 0) return;
4313
- const prev = sessionsList.querySelector('.session-item.kb-selected');
4403
+ function clearKbSelection() {
4404
+ const prev = sessionsList.querySelector('.kb-selected');
4314
4405
  if (prev) prev.classList.remove('kb-selected');
4406
+ }
4407
+
4408
+ function selectSessionByIndex(idx, items) {
4409
+ items = items || getNavigableItems();
4410
+ if (items.length === 0) return;
4411
+ clearKbSelection();
4315
4412
  selectedSessionIdx = Math.max(0, Math.min(idx, items.length - 1));
4316
- selectedSessionKbId = items[selectedSessionIdx].dataset.sessionId || null;
4317
- items[selectedSessionIdx].classList.add('kb-selected');
4318
- items[selectedSessionIdx].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
4413
+ const el = items[selectedSessionIdx];
4414
+ selectedSessionKbId = getKbId(el);
4415
+ el.classList.add('kb-selected');
4416
+ el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
4319
4417
  }
4320
4418
 
4321
- function navigateSession(direction) {
4322
- const items = getSessionItems();
4419
+ function navigateSession(direction, items) {
4420
+ items = items || getNavigableItems();
4323
4421
  if (items.length === 0) return;
4324
4422
  if (selectedSessionIdx < 0) {
4325
- selectSessionByIndex(0);
4423
+ selectSessionByIndex(0, items);
4326
4424
  return;
4327
4425
  }
4328
- const newIdx = selectedSessionIdx + direction;
4426
+ const currentEl = items[selectedSessionIdx];
4427
+ let newIdx = selectedSessionIdx + direction;
4428
+ if (!currentEl || !currentEl.isConnected) {
4429
+ const restoredIdx = selectedSessionKbId
4430
+ ? items.findIndex(el => getKbId(el) === selectedSessionKbId)
4431
+ : -1;
4432
+ newIdx = restoredIdx >= 0 ? restoredIdx : 0;
4433
+ }
4329
4434
  if (newIdx >= 0 && newIdx < items.length) {
4330
- selectSessionByIndex(newIdx);
4435
+ selectSessionByIndex(newIdx, items);
4331
4436
  }
4332
4437
  }
4333
4438
 
@@ -4338,26 +4443,56 @@
4338
4443
  if (collapsed) collapsedProjectGroups.add(projectPath);
4339
4444
  else collapsedProjectGroups.delete(projectPath);
4340
4445
  header.classList.toggle('collapsed', collapsed);
4341
- let el = header.nextElementSibling;
4342
- while (el && !el.classList.contains('project-group-sessions')) el = el.nextElementSibling;
4343
- if (el) el.classList.toggle('collapsed', collapsed);
4446
+ const container = getGroupSessionsContainer(header);
4447
+ if (container) container.classList.toggle('collapsed', collapsed);
4344
4448
  try { localStorage.setItem('collapsedGroups', JSON.stringify([...collapsedProjectGroups])); } catch(_) {}
4345
4449
  }
4346
4450
 
4347
- function toggleSelectedSessionGroup(collapse) {
4348
- const items = getSessionItems();
4451
+ function handleSidebarHorizontal(direction) {
4452
+ const items = getNavigableItems();
4349
4453
  if (selectedSessionIdx < 0 || selectedSessionIdx >= items.length) return;
4350
- const container = items[selectedSessionIdx].closest('.project-group-sessions');
4351
- if (!container) return;
4352
- let header = container.previousElementSibling;
4353
- while (header && !header.classList.contains('project-group-header')) header = header.previousElementSibling;
4354
- setGroupCollapsed(header, collapse);
4454
+ const el = items[selectedSessionIdx];
4455
+ const isHeader = el.classList.contains('project-group-header');
4456
+ const collapse = direction < 0;
4457
+
4458
+ if (isHeader) {
4459
+ const groupPath = el.dataset.groupPath;
4460
+ const isCollapsed = collapsedProjectGroups.has(groupPath);
4461
+ if (collapse) {
4462
+ if (!isCollapsed) setGroupCollapsed(el, true);
4463
+ } else {
4464
+ if (isCollapsed) {
4465
+ setGroupCollapsed(el, false);
4466
+ } else {
4467
+ navigateSession(1);
4468
+ }
4469
+ }
4470
+ } else {
4471
+ if (collapse) {
4472
+ const container = el.closest('.project-group-sessions');
4473
+ if (container) {
4474
+ let header = container.previousElementSibling;
4475
+ while (header && !header.classList.contains('project-group-header')) header = header.previousElementSibling;
4476
+ if (header) {
4477
+ const headerIdx = items.indexOf(header);
4478
+ if (headerIdx >= 0) selectSessionByIndex(headerIdx, items);
4479
+ }
4480
+ }
4481
+ } else {
4482
+ activateSelectedSession(items);
4483
+ }
4484
+ }
4355
4485
  }
4356
4486
 
4357
- function activateSelectedSession() {
4358
- const items = getSessionItems();
4359
- if (selectedSessionIdx >= 0 && selectedSessionIdx < items.length) {
4360
- items[selectedSessionIdx].click();
4487
+ function activateSelectedSession(items) {
4488
+ items = items || getNavigableItems();
4489
+ if (selectedSessionIdx < 0 || selectedSessionIdx >= items.length) return;
4490
+ const el = items[selectedSessionIdx];
4491
+ if (el.classList.contains('project-group-header')) {
4492
+ const groupPath = el.dataset.groupPath;
4493
+ setGroupCollapsed(el, !collapsedProjectGroups.has(groupPath));
4494
+ } else {
4495
+ el.click();
4361
4496
  }
4362
4497
  }
4363
4498
 
@@ -4365,8 +4500,7 @@
4365
4500
  const sidebar = document.querySelector('.sidebar');
4366
4501
  // Clear all zone visuals
4367
4502
  sidebar.classList.remove('sidebar-focused');
4368
- const prevSession = sessionsList.querySelector('.session-item.kb-selected');
4369
- if (prevSession) prevSession.classList.remove('kb-selected');
4503
+ clearKbSelection();
4370
4504
  const selCard = document.querySelector('.task-card.selected');
4371
4505
  if (selCard) selCard.classList.remove('selected');
4372
4506
 
@@ -4377,13 +4511,13 @@
4377
4511
  localStorage.setItem('sidebar-collapsed', false);
4378
4512
  }
4379
4513
  sidebar.classList.add('sidebar-focused');
4380
- const items = getSessionItems();
4514
+ const items = getNavigableItems();
4381
4515
  if (items.length > 0) {
4382
4516
  const activeIdx = items.findIndex(el => el.classList.contains('active'));
4383
4517
  if (activeIdx >= 0) {
4384
4518
  selectSessionByIndex(activeIdx);
4385
4519
  } else if (selectedSessionKbId) {
4386
- const restoredIdx = items.findIndex(el => el.dataset.sessionId === selectedSessionKbId);
4520
+ const restoredIdx = items.findIndex(el => getKbId(el) === selectedSessionKbId);
4387
4521
  selectSessionByIndex(restoredIdx >= 0 ? restoredIdx : 0);
4388
4522
  } else {
4389
4523
  selectSessionByIndex(0);
@@ -4860,6 +4994,10 @@
4860
4994
  // Tab toggles focus zone
4861
4995
  if (e.key === 'Tab') {
4862
4996
  e.preventDefault();
4997
+ if (focusZone === 'sidebar') {
4998
+ const hasCards = document.querySelector('.task-card');
4999
+ if (!hasCards) return;
5000
+ }
4863
5001
  setFocusZone(focusZone === 'board' ? 'sidebar' : 'board');
4864
5002
  return;
4865
5003
  }
@@ -4878,12 +5016,12 @@
4878
5016
  }
4879
5017
  if (e.key === 'h' || e.key === 'ArrowLeft') {
4880
5018
  e.preventDefault();
4881
- toggleSelectedSessionGroup(true);
5019
+ handleSidebarHorizontal(-1);
4882
5020
  return;
4883
5021
  }
4884
5022
  if (e.key === 'l' || e.key === 'ArrowRight') {
4885
5023
  e.preventDefault();
4886
- toggleSelectedSessionGroup(false);
5024
+ handleSidebarHorizontal(1);
4887
5025
  return;
4888
5026
  }
4889
5027
  if (e.key === 'Enter' || e.key === ' ') {
@@ -4897,14 +5035,14 @@
4897
5035
  }
4898
5036
  if (e.key === 'p' || e.key === 'P') {
4899
5037
  e.preventDefault();
4900
- const highlighted = sessionsList.querySelector('.session-item.kb-selected');
5038
+ const highlighted = sessionsList.querySelector('.kb-selected');
4901
5039
  const sid = highlighted?.dataset.sessionId || currentSessionId;
4902
5040
  if (sid) openPlanForSession(sid);
4903
5041
  return;
4904
5042
  }
4905
5043
  if (e.key === 'i' || e.key === 'I') {
4906
5044
  e.preventDefault();
4907
- const highlighted = sessionsList.querySelector('.session-item.kb-selected');
5045
+ const highlighted = sessionsList.querySelector('.kb-selected');
4908
5046
  const sid = highlighted?.dataset.sessionId || currentSessionId;
4909
5047
  if (sid) showSessionInfoModal(sid);
4910
5048
  return;
@@ -4920,6 +5058,10 @@
4920
5058
  const navKeys = ['j','k','h','l','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'];
4921
5059
  if (navKeys.includes(e.key)) {
4922
5060
  e.preventDefault();
5061
+ if (!selectedTaskId && !document.querySelector('.task-card.selected')) {
5062
+ setFocusZone('sidebar');
5063
+ return;
5064
+ }
4923
5065
  if (e.key === 'j' || e.key === 'ArrowDown') navigateVertical(1);
4924
5066
  else if (e.key === 'k' || e.key === 'ArrowUp') navigateVertical(-1);
4925
5067
  else if (e.key === 'h' || e.key === 'ArrowLeft') navigateHorizontal(-1);
@@ -5702,6 +5844,12 @@
5702
5844
 
5703
5845
  // Init
5704
5846
  loadTheme();
5847
+ ['live-updates', 'sessions-filters'].forEach(id => {
5848
+ if (localStorage.getItem(id + 'Collapsed') === 'true') {
5849
+ document.getElementById(id).classList.add('collapsed');
5850
+ document.getElementById(id === 'live-updates' ? 'live-updates-chevron' : 'sessions-chevron').classList.add('rotated');
5851
+ }
5852
+ });
5705
5853
 
5706
5854
  document.addEventListener('DOMContentLoaded', () => {
5707
5855
  if (typeof marked !== 'undefined' && typeof hljs !== 'undefined') {
package/server.js CHANGED
@@ -604,7 +604,7 @@ app.get('/api/sessions', async (req, res) => {
604
604
  }
605
605
 
606
606
  // Convert map to array and sort by most recently modified
607
- sessions = Array.from(sessionsMap.values());
607
+ let sessions = Array.from(sessionsMap.values());
608
608
  sessions.sort((a, b) => new Date(b.modifiedAt) - new Date(a.modifiedAt));
609
609
 
610
610
  // Apply limit if specified, but always include pinned sessions