claude-code-kanban 7.0.0 → 7.0.2

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
@@ -646,28 +646,47 @@ function countTranscriptsBornBefore(dir, birthtimeMs) {
646
646
  return n;
647
647
  }
648
648
 
649
+ // Cache: jsonlPath -> { ino, size, mtimeMs }, the transcript as it last grew.
650
+ // Claude Code, `claude --resume` and backup tools set a transcript's mtime without writing
651
+ // to it (#50), so only a file that grew, shrank or was replaced counts as activity.
652
+ const logActivityCache = new Map();
653
+
654
+ function transcriptActivityMs(jsonlPath, stat) {
655
+ const prev = logActivityCache.get(jsonlPath);
656
+ if (prev && prev.ino === stat.ino && prev.size === stat.size) return prev.mtimeMs;
657
+ logActivityCache.delete(jsonlPath);
658
+ logActivityCache.set(jsonlPath, { ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs });
659
+ if (logActivityCache.size > SESSION_INFO_CACHE_MAX) logActivityCache.delete(logActivityCache.keys().next().value);
660
+ sessionCacheDirty = true;
661
+ return stat.mtimeMs;
662
+ }
663
+
649
664
  const sessionCachesDirty = () => sessionCacheDirty;
650
665
 
651
666
  // Snapshot of the per-transcript caches for lib/session-cache.js. Entries of deleted
652
667
  // transcripts are left out, so the snapshot never holds more than the live set.
653
668
  function exportSessionCaches() {
654
669
  sessionCacheDirty = false;
655
- const livePaths = new Set([...sessionInfoCache.keys(), ...customTitleCache.keys()].filter((p) => fs.existsSync(p)));
670
+ const livePaths = new Set([...sessionInfoCache.keys(), ...customTitleCache.keys(), ...logActivityCache.keys()]);
671
+ for (const p of livePaths) if (!fs.existsSync(p)) livePaths.delete(p);
656
672
  const live = (map) => [...map].filter(([p]) => livePaths.has(p));
657
- return { info: live(sessionInfoCache), titles: live(customTitleCache) };
673
+ return { info: live(sessionInfoCache), titles: live(customTitleCache), activity: live(logActivityCache) };
658
674
  }
659
675
 
660
676
  function importSessionCaches(snapshot) {
661
- const valid = (e) => e && typeof e === 'object' && typeof e.ino === 'number' && typeof e.scannedUpTo === 'number' && typeof e.mtimeMs === 'number';
662
- const load = (map, entries) => {
677
+ const num = (v) => typeof v === 'number';
678
+ const scanned = (e) => num(e.ino) && num(e.scannedUpTo) && num(e.mtimeMs);
679
+ const grown = (e) => num(e.ino) && num(e.size) && num(e.mtimeMs);
680
+ const load = (map, entries, valid) => {
663
681
  if (!Array.isArray(entries)) return;
664
682
  for (const pair of entries) {
665
- if (!Array.isArray(pair) || typeof pair[0] !== 'string' || !valid(pair[1])) continue;
683
+ if (!Array.isArray(pair) || typeof pair[0] !== 'string' || !pair[1] || typeof pair[1] !== 'object' || !valid(pair[1])) continue;
666
684
  map.set(pair[0], pair[1]);
667
685
  }
668
686
  };
669
- load(sessionInfoCache, snapshot?.info?.slice(-SESSION_INFO_CACHE_MAX));
670
- load(customTitleCache, snapshot?.titles?.slice(-SESSION_INFO_CACHE_MAX));
687
+ load(sessionInfoCache, snapshot?.info?.slice(-SESSION_INFO_CACHE_MAX), scanned);
688
+ load(customTitleCache, snapshot?.titles?.slice(-SESSION_INFO_CACHE_MAX), scanned);
689
+ load(logActivityCache, snapshot?.activity?.slice(-SESSION_INFO_CACHE_MAX), grown);
671
690
  }
672
691
 
673
692
  // Background-task completion records arrive as a user message whose content is a
@@ -1972,6 +1991,7 @@ module.exports = {
1972
1991
  exportSessionCaches,
1973
1992
  importSessionCaches,
1974
1993
  sessionCachesDirty,
1994
+ transcriptActivityMs,
1975
1995
  countTranscriptsBornBefore,
1976
1996
  readRecentMessages,
1977
1997
  readMessagesPage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "7.0.0",
3
+ "version": "7.0.2",
4
4
  "description": "A web-based Kanban board for viewing Claude Code tasks with agent teams support",
5
5
  "main": "server.js",
6
6
  "type": "commonjs",
package/public/app.js CHANGED
@@ -11986,7 +11986,7 @@ function syncPanes() {
11986
11986
  function renderPaneTabs(sid, layout) {
11987
11987
  const active = getActivePaneId(sid);
11988
11988
  const tab = (id, cls, inner, tip) =>
11989
- `<div class="pane-tab ${cls}${active === id ? ' on' : ''}" role="tab" data-pane="${escapeHtml(id)}" title="${escapeHtml(tip)}">${inner}</div>`;
11989
+ `<div class="pane-tab ${escapeHtml(cls)}${active === id ? ' on' : ''}" role="tab" data-pane="${escapeHtml(id)}" title="${escapeHtml(tip)}">${inner}</div>`;
11990
11990
  const board = tab(
11991
11991
  'board',
11992
11992
  'pinned',
@@ -11999,7 +11999,7 @@ function renderPaneTabs(sid, layout) {
11999
11999
  return tab(
12000
12000
  p.id,
12001
12001
  '',
12002
- `${p.kind === 'url' ? PANE_ICONS.url : PANE_ICONS.file}<span class="pane-title">${escapeHtml(p.title)}</span><span class="state ${state}" title="${stateTip}"></span><button class="pane-x" data-close="${escapeHtml(p.id)}" title="Close pane" aria-label="Close pane">×</button>`,
12002
+ `${p.kind === 'url' ? PANE_ICONS.url : PANE_ICONS.file}<span class="pane-title">${escapeHtml(p.title)}</span><span class="state ${escapeHtml(state)}" title="${escapeHtml(stateTip)}"></span><button class="pane-x" data-close="${escapeHtml(p.id)}" title="Close pane" aria-label="Close pane">×</button>`,
12003
12003
  p.target,
12004
12004
  );
12005
12005
  });
@@ -12262,7 +12262,7 @@ function openPaneMenu(x, y, id) {
12262
12262
  sgCloseMenu();
12263
12263
  const url = pane.kind === 'url';
12264
12264
  const item = (act, label) =>
12265
- `<button class="pane-menu-item" role="menuitem" data-pane-act="${act}">${label}</button>`;
12265
+ `<button class="pane-menu-item" role="menuitem" data-pane-act="${escapeHtml(act)}">${label}</button>`;
12266
12266
  const menu = document.createElement('div');
12267
12267
  menu.id = 'pane-menu';
12268
12268
  menu.className = 'pane-menu';
package/server.js CHANGED
@@ -24,6 +24,7 @@ const {
24
24
  readRecentMessages: _readRecentMessagesUncached,
25
25
  readMessagesPage: _readMessagesPageUncached,
26
26
  readSessionInfoFromJsonl,
27
+ transcriptActivityMs,
27
28
  modelDisplayName,
28
29
  buildSessionDigest,
29
30
  readCompactSummaries,
@@ -362,7 +363,7 @@ function getSessionLogStat(meta) {
362
363
  if (!meta.jsonlPath) return { mtime: null, hasMessages: false };
363
364
  try {
364
365
  const st = statSync(meta.jsonlPath);
365
- return { mtime: st.mtimeMs, hasMessages: st.size > 1000 };
366
+ return { mtime: transcriptActivityMs(meta.jsonlPath, st), hasMessages: st.size > 1000 };
366
367
  } catch { return { mtime: null, hasMessages: false }; }
367
368
  }
368
369