claude-code-kanban 3.4.0 → 3.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "3.4.0",
3
+ "version": "3.5.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
@@ -78,11 +78,34 @@ function updateUrl() {
78
78
  const qs = params.toString();
79
79
  const url = qs ? `?${qs}` : window.location.pathname;
80
80
  history.replaceState(null, '', url);
81
+ persistLastView();
82
+ }
83
+
84
+ const LAST_VIEW_KEY = 'lastView';
85
+ function persistLastView() {
86
+ try {
87
+ const data = {
88
+ view: viewMode,
89
+ session: currentSessionId,
90
+ projectPath: viewMode === 'project' ? currentProjectPath : null,
91
+ };
92
+ localStorage.setItem(LAST_VIEW_KEY, JSON.stringify(data));
93
+ } catch (_) {}
94
+ }
95
+ function loadLastView() {
96
+ try {
97
+ return JSON.parse(localStorage.getItem(LAST_VIEW_KEY)) || null;
98
+ } catch (_) {
99
+ return null;
100
+ }
81
101
  }
82
102
 
83
103
  // biome-ignore lint/correctness/noUnusedVariables: used in HTML
84
104
  function resetState() {
85
105
  history.replaceState(null, '', window.location.pathname);
106
+ try {
107
+ localStorage.removeItem(LAST_VIEW_KEY);
108
+ } catch (_) {}
86
109
  sessionFilter = 'active';
87
110
  sessionLimit = '20';
88
111
  filterProject = '__recent__';
@@ -481,6 +504,7 @@ async function fetchTasks(sessionId) {
481
504
  resetAgentState();
482
505
  updateUrl();
483
506
  renderSession();
507
+ renderSessions();
484
508
  fetchAgents(sessionId);
485
509
  if (!agentLogMode) fetchMessages(sessionId);
486
510
  } catch (error) {
@@ -1999,34 +2023,35 @@ function showAgentModal(agentId) {
1999
2023
  const modalNameLabel = agent.agentName ? ` · ${escapeHtml(agent.agentName)}` : '';
2000
2024
  title.innerHTML = `${statusDot} ${escapeHtml(agent.type || 'unknown')}${modalNameLabel}`;
2001
2025
 
2002
- const rows = [
2003
- ['Status', agent.status],
2004
- ['Agent ID', `<code style="font-size:12px;color:var(--text-tertiary)">${escapeHtml(agent.agentId)}</code>`],
2005
- ['Duration', formatDuration(elapsed)],
2006
- ];
2026
+ const shortModel = agent.model ? agent.model.replace(/^claude-/, '').replace(/-\d{8}$/, '') : null;
2027
+ const shortId = agent.agentId ? agent.agentId.slice(0, 8) : '';
2028
+ const chip = (label, value, opts = {}) => {
2029
+ const cls = opts.cls ? ` ${opts.cls}` : '';
2030
+ const style = opts.style ? ` style="${opts.style}"` : '';
2031
+ const title = opts.title ? ` title="${escapeHtml(opts.title)}"` : '';
2032
+ const labelHtml = label ? `<span class="agent-chip-label">${label}</span>` : '';
2033
+ return `<span class="agent-chip${cls}"${style}${title}>${labelHtml}<span class="agent-chip-val">${value}</span></span>`;
2034
+ };
2035
+
2036
+ const chips = [];
2037
+ if (agent.agentId) chips.push(chip('id', escapeHtml(shortId), { cls: 'agent-chip-mono', title: agent.agentId }));
2038
+ chips.push(chip('', escapeHtml(agent.status), { cls: `agent-chip-status agent-chip-${agent.status}` }));
2039
+ chips.push(chip('⏱', formatDuration(elapsed)));
2040
+ if (shortModel) chips.push(chip('model', escapeHtml(shortModel), { cls: 'agent-chip-mono' }));
2007
2041
  if (agent.agentName) {
2008
- const ownerColor = getOwnerColor(agent.agentName);
2009
- rows.push([
2010
- 'Owner',
2011
- `<span class="task-owner-badge" style="background:${ownerColor.bg};color:${ownerColor.color}">${escapeHtml(agent.agentName)}</span>`,
2012
- ]);
2042
+ const c = getOwnerColor(agent.agentName);
2043
+ chips.push(
2044
+ chip('owner', escapeHtml(agent.agentName), {
2045
+ style: `background:${c.bg};color:${c.color};border-color:transparent;`,
2046
+ }),
2047
+ );
2013
2048
  }
2014
- if (agent.model)
2015
- rows.push(['Model', `<code style="font-size:12px;color:var(--text-tertiary)">${escapeHtml(agent.model)}</code>`]);
2016
- if (started) rows.push(['Started', started.toLocaleTimeString()]);
2017
- if (stopped) rows.push(['Stopped', stopped.toLocaleTimeString()]);
2049
+ if (started) chips.push(chip('started', started.toLocaleTimeString()));
2050
+ if (stopped) chips.push(chip('stopped', stopped.toLocaleTimeString()));
2018
2051
 
2019
2052
  const agentMsg = currentMessages.find((m) => m.tool === 'Agent' && m.agentId === agentId);
2020
2053
 
2021
- let html =
2022
- `<table style="width:100%;border-collapse:collapse;">` +
2023
- rows
2024
- .map(
2025
- ([k, v]) =>
2026
- `<tr><td style="padding:6px 12px 6px 0;color:var(--text-tertiary);white-space:nowrap;vertical-align:top;">${k}</td><td style="padding:6px 0;color:var(--text-primary);">${v}</td></tr>`,
2027
- )
2028
- .join('') +
2029
- `</table>`;
2054
+ let html = `<div class="agent-chips">${chips.join('')}</div>`;
2030
2055
 
2031
2056
  const promptText = stripTeammateWrapper(agentMsg?.agentPrompt || agent.prompt || null);
2032
2057
  const responseText = agent.lastMessage ? stripAnsi(agent.lastMessage.trim()) : null;
@@ -4124,6 +4149,8 @@ document.addEventListener('keydown', (e) => {
4124
4149
  e.preventDefault();
4125
4150
  if (_manualRefreshing) return;
4126
4151
  _manualRefreshing = true;
4152
+ lastSessionsHash = '';
4153
+ lastTasksHash = '';
4127
4154
  const refreshes = [fetchSessions()];
4128
4155
  if (currentSessionId) refreshes.push(fetchTasks(currentSessionId));
4129
4156
  Promise.all(refreshes)
@@ -4955,11 +4982,18 @@ async function updateProjectDropdown() {
4955
4982
  projectsCacheDirty = false;
4956
4983
 
4957
4984
  const cutoff = Date.now() - 24 * 60 * 60 * 1000;
4985
+ const prevRecent = recentProjects;
4958
4986
  recentProjects = new Set(
4959
4987
  projects.filter((p) => p.modifiedAt && new Date(p.modifiedAt).getTime() > cutoff).map((p) => p.path),
4960
4988
  );
4961
4989
 
4962
4990
  renderProjectDropdown(dropdown, projects);
4991
+
4992
+ // recentProjects was empty before — sidebar rendered with __recent__ filter
4993
+ // dropping every session. Re-render now that we know which projects qualify.
4994
+ if (filterProject === '__recent__' && prevRecent.size === 0 && recentProjects.size > 0) {
4995
+ renderSessions();
4996
+ }
4963
4997
  }
4964
4998
 
4965
4999
  function renderProjectDropdown(dropdown, projects) {
@@ -5753,8 +5787,21 @@ Promise.all([
5753
5787
  }
5754
5788
  } else if (urlState.session) {
5755
5789
  await fetchTasks(urlState.session);
5756
- } else {
5790
+ } else if (urlState.view === 'all') {
5757
5791
  showAllTasks();
5792
+ } else {
5793
+ const last = loadLastView();
5794
+ if (last?.view === 'project' && last.projectPath && sessions.some((s) => s.project === last.projectPath)) {
5795
+ try {
5796
+ await fetchProjectView(last.projectPath);
5797
+ } catch (_) {
5798
+ showAllTasks();
5799
+ }
5800
+ } else if (last?.view === 'session' && last.session && sessions.some((s) => s.id === last.session)) {
5801
+ await fetchTasks(last.session);
5802
+ } else {
5803
+ showAllTasks();
5804
+ }
5758
5805
  }
5759
5806
  if (urlState.messages && currentSessionId) {
5760
5807
  toggleMessagePanel();
package/public/style.css CHANGED
@@ -1707,6 +1707,61 @@ body::before {
1707
1707
  flex-shrink: 0;
1708
1708
  }
1709
1709
 
1710
+ .agent-chips {
1711
+ display: flex;
1712
+ flex-wrap: wrap;
1713
+ gap: 6px;
1714
+ margin-bottom: 10px;
1715
+ }
1716
+ .agent-chip {
1717
+ display: inline-flex;
1718
+ align-items: center;
1719
+ gap: 4px;
1720
+ padding: 3px 9px;
1721
+ font-size: 11px;
1722
+ font-weight: 500;
1723
+ border-radius: 999px;
1724
+ border: 1px solid var(--border-color, rgba(127, 127, 127, 0.25));
1725
+ background: var(--bg-secondary, rgba(127, 127, 127, 0.08));
1726
+ color: var(--text-secondary);
1727
+ white-space: nowrap;
1728
+ line-height: 1.4;
1729
+ }
1730
+ .agent-chip-label {
1731
+ color: var(--text-tertiary);
1732
+ text-transform: uppercase;
1733
+ font-size: 9.5px;
1734
+ font-weight: 600;
1735
+ letter-spacing: 0.5px;
1736
+ opacity: 0.75;
1737
+ }
1738
+ .agent-chip-val {
1739
+ color: inherit;
1740
+ }
1741
+ .agent-chip-mono .agent-chip-val {
1742
+ font-family: var(--font-mono, monospace);
1743
+ font-size: 10.5px;
1744
+ letter-spacing: 0.2px;
1745
+ }
1746
+ .agent-chip-status {
1747
+ text-transform: capitalize;
1748
+ }
1749
+ .agent-chip-running {
1750
+ background: rgba(34, 197, 94, 0.15);
1751
+ color: rgb(34, 160, 80);
1752
+ border-color: rgba(34, 197, 94, 0.3);
1753
+ }
1754
+ .agent-chip-stopped {
1755
+ background: rgba(127, 127, 127, 0.15);
1756
+ color: var(--text-tertiary);
1757
+ border-color: rgba(127, 127, 127, 0.3);
1758
+ }
1759
+ .agent-chip-error,
1760
+ .agent-chip-failed {
1761
+ background: rgba(239, 68, 68, 0.15);
1762
+ color: rgb(220, 60, 60);
1763
+ border-color: rgba(239, 68, 68, 0.3);
1764
+ }
1710
1765
  .team-modal-desc {
1711
1766
  font-size: 12px;
1712
1767
  color: var(--text-secondary);