claude-code-kanban 2.3.1 → 2.4.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 CHANGED
@@ -172,7 +172,7 @@ function readCustomTitle(jsonlPath, existingStat) {
172
172
  }
173
173
 
174
174
  function readSessionInfoFromJsonl(jsonlPath) {
175
- const result = { slug: null, projectPath: null, gitBranch: null, customTitle: null };
175
+ const result = { slug: null, projectPath: null, cwd: null, gitBranch: null, customTitle: null };
176
176
  let stat;
177
177
  try {
178
178
  stat = statSync(jsonlPath);
@@ -182,16 +182,22 @@ function readSessionInfoFromJsonl(jsonlPath) {
182
182
 
183
183
  const headBuf = Buffer.alloc(Math.min(HEAD_SIZE, stat.size));
184
184
  const hn = fs.readSync(fd, headBuf, 0, headBuf.length, 0);
185
+ let lastCwdFromHead = null;
185
186
  for (const line of headBuf.toString('utf8', 0, hn).split('\n')) {
186
187
  try {
187
188
  const data = JSON.parse(line);
188
189
  if (data.slug) result.slug = data.slug;
189
- if (data.cwd) result.projectPath = data.cwd;
190
+ if (data.cwd) {
191
+ if (!result.projectPath) result.projectPath = data.cwd;
192
+ lastCwdFromHead = data.cwd;
193
+ }
190
194
  if (data.gitBranch) result.gitBranch = data.gitBranch;
191
195
  if (result.slug && result.projectPath && result.gitBranch) break;
192
196
  } catch (e) {}
193
197
  }
194
198
 
199
+ result.cwd = lastCwdFromHead;
200
+
195
201
  if ((!result.slug || !result.projectPath || !result.gitBranch) && stat.size > HEAD_SIZE) {
196
202
  const tailStart = stat.size - TAIL_SIZE;
197
203
  const tailBuf = Buffer.alloc(TAIL_SIZE);
@@ -203,6 +209,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
203
209
  if (!result.slug && data.slug) result.slug = data.slug;
204
210
  if (!result.projectPath && data.cwd) result.projectPath = data.cwd;
205
211
  if (!result.gitBranch && data.gitBranch) result.gitBranch = data.gitBranch;
212
+ if (!result.cwd && data.cwd) result.cwd = data.cwd;
206
213
  if (result.slug && result.projectPath && result.gitBranch) break;
207
214
  } catch (e) {}
208
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "2.3.1",
3
+ "version": "2.4.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/app.js CHANGED
@@ -4825,6 +4825,9 @@ function showInfoModal(session, teamConfig, tasks, planContent) {
4825
4825
  const projectName = session.project.split(/[/\\]/).pop();
4826
4826
  infoRows.push(['Project', projectName, { openPath: session.projectDir }]);
4827
4827
  infoRows.push(['Path', session.project, { openPath: session.project }]);
4828
+ if (session.cwd && session.cwd !== session.project) {
4829
+ infoRows.push(['CWD', session.cwd, { openPath: session.cwd }]);
4830
+ }
4828
4831
  if (session.gitBranch) {
4829
4832
  infoRows.push(['Branch', session.gitBranch]);
4830
4833
  }
@@ -5290,6 +5293,10 @@ window.addEventListener('popstate', () => {
5290
5293
  e.preventDefault();
5291
5294
  window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
5292
5295
  }
5296
+ if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && /^[1-9]$/.test(e.key)) {
5297
+ e.preventDefault();
5298
+ window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
5299
+ }
5293
5300
  });
5294
5301
  })();
5295
5302
 
package/server.js CHANGED
@@ -359,6 +359,20 @@ function loadSessionMetadata() {
359
359
  const files = readdirSync(projectPath).filter(f => f.endsWith('.jsonl'));
360
360
  const sessionIds = [];
361
361
 
362
+ // Read sessions-index.json first for canonical projectPath
363
+ let indexProjectPath = null;
364
+ const indexPath = path.join(projectPath, 'sessions-index.json');
365
+ let indexEntries = [];
366
+ if (existsSync(indexPath)) {
367
+ try {
368
+ const indexData = JSON.parse(readFileSync(indexPath, 'utf8'));
369
+ indexEntries = indexData.entries || [];
370
+ for (const entry of indexEntries) {
371
+ if (entry.projectPath) { indexProjectPath = entry.projectPath; break; }
372
+ }
373
+ } catch (e) {}
374
+ }
375
+
362
376
  // First pass: read all JSONL files
363
377
  let resolvedProjectPath = null;
364
378
  for (const file of files) {
@@ -372,7 +386,8 @@ function loadSessionMetadata() {
372
386
 
373
387
  metadata[sessionId] = {
374
388
  slug: sessionInfo.slug,
375
- project: sessionInfo.projectPath || null,
389
+ project: indexProjectPath || sessionInfo.projectPath || null,
390
+ cwd: sessionInfo.cwd || null,
376
391
  gitBranch: sessionInfo.gitBranch || null,
377
392
  customTitle: sessionInfo.customTitle || null,
378
393
  jsonlPath: jsonlPath
@@ -381,38 +396,30 @@ function loadSessionMetadata() {
381
396
  }
382
397
 
383
398
  // Second pass: fill in missing project paths from siblings
384
- if (resolvedProjectPath) {
399
+ const canonicalProject = indexProjectPath || resolvedProjectPath;
400
+ if (canonicalProject) {
385
401
  for (const sid of sessionIds) {
386
402
  if (!metadata[sid].project) {
387
- metadata[sid].project = resolvedProjectPath;
403
+ metadata[sid].project = canonicalProject;
388
404
  }
389
405
  }
390
406
  }
391
407
 
392
- // Also check sessions-index.json for custom names (if /rename was used)
393
- const indexPath = path.join(projectPath, 'sessions-index.json');
394
- if (existsSync(indexPath)) {
395
- try {
396
- const indexData = JSON.parse(readFileSync(indexPath, 'utf8'));
397
- const entries = indexData.entries || [];
398
-
399
- for (const entry of entries) {
400
- if (entry.sessionId) {
401
- if (!metadata[entry.sessionId]) {
402
- metadata[entry.sessionId] = {
403
- slug: null,
404
- project: entry.projectPath || null,
405
- jsonlPath: null
406
- };
407
- }
408
- metadata[entry.sessionId].description = entry.description || null;
409
- if (entry.gitBranch) metadata[entry.sessionId].gitBranch = entry.gitBranch;
410
- if (entry.customTitle) metadata[entry.sessionId].customTitle = entry.customTitle;
411
- metadata[entry.sessionId].created = entry.created || null;
412
- }
408
+ // Apply index metadata (descriptions, custom titles, etc.)
409
+ for (const entry of indexEntries) {
410
+ if (entry.sessionId) {
411
+ if (!metadata[entry.sessionId]) {
412
+ metadata[entry.sessionId] = {
413
+ slug: null,
414
+ project: indexProjectPath || entry.projectPath || null,
415
+ cwd: null,
416
+ jsonlPath: null
417
+ };
413
418
  }
414
- } catch (e) {
415
- // Skip invalid index files
419
+ metadata[entry.sessionId].description = entry.description || null;
420
+ if (entry.gitBranch) metadata[entry.sessionId].gitBranch = entry.gitBranch;
421
+ if (entry.customTitle) metadata[entry.sessionId].customTitle = entry.customTitle;
422
+ metadata[entry.sessionId].created = entry.created || null;
416
423
  }
417
424
  }
418
425
  }
@@ -480,6 +487,7 @@ function buildSessionObject(id, meta, overrides = {}) {
480
487
  name: getSessionDisplayName(id, meta),
481
488
  slug: meta.slug || null,
482
489
  project: meta.project || null,
490
+ cwd: meta.cwd || null,
483
491
  description: meta.description || null,
484
492
  gitBranch: meta.gitBranch || null,
485
493
  customTitle: meta.customTitle || null,