claude-code-kanban 2.2.1-rc.1 → 2.3.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": "2.2.1-rc.1",
3
+ "version": "2.3.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
@@ -25,6 +25,7 @@ let agentPollInterval = null;
25
25
  let selectedTaskId = null;
26
26
  let selectedSessionId = null;
27
27
  let focusZone = 'board'; // 'board' | 'sidebar'
28
+ let appConfig = { marketplaceUrl: null };
28
29
  let selectedSessionIdx = -1;
29
30
  let selectedSessionKbId = null;
30
31
  let sessionJustSelected = false;
@@ -36,6 +37,7 @@ let msgUserScrolledUp = false;
36
37
  const MSG_MAX_LOADED = 200;
37
38
  let currentProjectPath = null;
38
39
  let currentProjectSessionIds = [];
40
+ const dismissedSessionIds = new Set();
39
41
 
40
42
  function resetMessageScrollState() {
41
43
  msgUserScrolledUp = false;
@@ -1320,6 +1322,8 @@ function _renderPinToDetail(pin) {
1320
1322
  }
1321
1323
 
1322
1324
  const SESSION_PIN_SVG = PIN_SVG.replace('width="14" height="14"', 'width="12" height="12"');
1325
+ const MARKETPLACE_SVG =
1326
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>';
1323
1327
 
1324
1328
  //#endregion
1325
1329
 
@@ -1541,8 +1545,15 @@ function formatTaskToolDetail(params) {
1541
1545
  }
1542
1546
  function getToolDetail(tool, params, detail) {
1543
1547
  if (TASK_TOOLS.has(tool)) return formatTaskToolDetail(params);
1544
- if (detail) return ` <span style="color:var(--text-muted)">${escapeHtml(detail)}</span>`;
1545
- return '';
1548
+ if (!detail) return '';
1549
+ let extra = '';
1550
+ if (tool === 'Read' && params) {
1551
+ const parts = [];
1552
+ if (params.offset) parts.push(`L${params.offset}`);
1553
+ if (params.limit) parts.push(`+${params.limit}`);
1554
+ if (parts.length) extra = ` <span style="color:var(--text-muted);opacity:.7">${parts.join(' ')}</span>`;
1555
+ }
1556
+ return ` <span style="color:var(--text-muted)">${escapeHtml(detail)}</span>${extra}`;
1546
1557
  }
1547
1558
  function renderTaskResult(toolResult) {
1548
1559
  if (!toolResult) return '';
@@ -2096,6 +2107,7 @@ function renderSessions() {
2096
2107
  const now = Date.now();
2097
2108
  const activeSessionIds = new Set();
2098
2109
  filteredSessions = filteredSessions.filter((s) => {
2110
+ if (dismissedSessionIds.has(s.id)) return false;
2099
2111
  const isActive =
2100
2112
  s.hasMessages &&
2101
2113
  ((!s.sharedTaskList && (s.pending > 0 || s.inProgress > 0)) ||
@@ -2224,6 +2236,7 @@ function renderSessions() {
2224
2236
  ${session.hasRunningAgents ? '<span class="agent-badge" title="Active agents">🤖</span>' : ''}
2225
2237
  ${session.planSourceSessionId ? `<span class="plan-indicator" title="Implements plan — click to reveal plan session" onclick="event.stopPropagation(); revealPlanSession('${escapeHtml(session.planSourceSessionId)}')">📋</span>` : ''}
2226
2238
  ${session.hasWaitingForUser ? '<span class="agent-badge" title="Waiting for user">❓</span>' : ''}
2239
+ ${(window.__HUB__?.enabled || appConfig.marketplaceUrl) && session.project ? `<span class="marketplace-btn" data-project-path="${escapeHtml(session.project)}" onclick="event.stopPropagation(); openMarketplace(this.dataset.projectPath)" title="Open in Marketplace">${MARKETPLACE_SVG}</span>` : ''}
2227
2240
  ${isLive ? '<span class="pulse"></span>' : ''}
2228
2241
  </span>
2229
2242
  <div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
@@ -4924,6 +4937,7 @@ function showInfoModal(session, teamConfig, tasks, planContent) {
4924
4937
  bodyEl.innerHTML = html;
4925
4938
  _infoModalSessionId = session.id;
4926
4939
  updateStickyBtnState();
4940
+ updateDismissBtnState();
4927
4941
  modal.classList.add('visible');
4928
4942
 
4929
4943
  const keyHandler = (e) => {
@@ -4941,6 +4955,25 @@ function closeTeamModal() {
4941
4955
  document.getElementById('team-modal').classList.remove('visible');
4942
4956
  }
4943
4957
 
4958
+ // biome-ignore lint/correctness/noUnusedVariables: used in HTML
4959
+ function toggleDismissSession(sessionId) {
4960
+ if (dismissedSessionIds.has(sessionId)) {
4961
+ dismissedSessionIds.delete(sessionId);
4962
+ } else {
4963
+ dismissedSessionIds.add(sessionId);
4964
+ }
4965
+ updateDismissBtnState();
4966
+ renderSessions();
4967
+ }
4968
+
4969
+ function updateDismissBtnState() {
4970
+ const btn = document.getElementById('session-info-dismiss-btn');
4971
+ if (!btn || !_infoModalSessionId) return;
4972
+ const isDismissed = dismissedSessionIds.has(_infoModalSessionId);
4973
+ btn.textContent = isDismissed ? 'Restore' : 'Dismiss';
4974
+ btn.title = isDismissed ? 'Restore — show in active list again' : 'Dismiss — hide from active list';
4975
+ }
4976
+
4944
4977
  let _planSessionId = null;
4945
4978
 
4946
4979
  //#endregion
@@ -5007,6 +5040,18 @@ function openFolderInEditor(folder, file) {
5007
5040
  postAndToast('/api/open-folder', body, 'folder');
5008
5041
  }
5009
5042
 
5043
+ // biome-ignore lint/correctness/noUnusedVariables: used in HTML
5044
+ function openMarketplace(projectPath) {
5045
+ const params = new URLSearchParams({ project: projectPath });
5046
+ if (window.__HUB__?.enabled) {
5047
+ hubNavigate('marketplace', `?${params}`);
5048
+ } else if (appConfig.marketplaceUrl) {
5049
+ const url = new URL(appConfig.marketplaceUrl);
5050
+ url.search = params.toString();
5051
+ window.open(url.toString(), '_blank');
5052
+ }
5053
+ }
5054
+
5010
5055
  //#endregion
5011
5056
 
5012
5057
  //#region OWNER_FILTER
@@ -5185,6 +5230,13 @@ if (urlState.search) {
5185
5230
  document.getElementById('search-clear-btn').classList.add('visible');
5186
5231
  }
5187
5232
 
5233
+ fetch('/api/config')
5234
+ .then((r) => r.json())
5235
+ .then((c) => {
5236
+ appConfig = c;
5237
+ })
5238
+ .catch(() => {});
5239
+
5188
5240
  fetchSessions().then(async () => {
5189
5241
  if (urlState.projectView) {
5190
5242
  try {
@@ -5225,3 +5277,24 @@ window.addEventListener('popstate', () => {
5225
5277
  if (s.messages !== messagePanelOpen) toggleMessagePanel();
5226
5278
  });
5227
5279
  //#endregion
5280
+
5281
+ // #region HUB_INTEGRATION
5282
+ (async function initHub() {
5283
+ const cfg = await fetch('/hub-config')
5284
+ .then((r) => r.json())
5285
+ .catch(() => ({}));
5286
+ if (!cfg.enabled) return;
5287
+ window.__HUB__ = cfg;
5288
+ document.addEventListener('keydown', (e) => {
5289
+ if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
5290
+ e.preventDefault();
5291
+ window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
5292
+ }
5293
+ });
5294
+ })();
5295
+
5296
+ window.hubNavigate = function hubNavigate(app, url) {
5297
+ if (!window.__HUB__?.enabled) return;
5298
+ window.parent?.postMessage({ type: 'hub:navigate', app, url }, '*');
5299
+ };
5300
+ // #endregion HUB_INTEGRATION
package/public/index.html CHANGED
@@ -496,6 +496,7 @@
496
496
  </div>
497
497
  <div id="team-modal-body" class="modal-body"></div>
498
498
  <div class="modal-footer">
499
+ <button id="session-info-dismiss-btn" class="btn btn-secondary" onclick="toggleDismissSession(_infoModalSessionId)">Dismiss</button>
499
500
  <button class="btn btn-primary" onclick="closeTeamModal()">Close</button>
500
501
  </div>
501
502
  </div>
package/public/style.css CHANGED
@@ -3330,6 +3330,22 @@ pre.mermaid svg {
3330
3330
  color: var(--accent);
3331
3331
  }
3332
3332
 
3333
+ .marketplace-btn {
3334
+ color: #888;
3335
+ cursor: pointer;
3336
+ display: inline-flex;
3337
+ align-items: center;
3338
+ transition:
3339
+ color 0.15s,
3340
+ filter 0.15s;
3341
+ border-radius: 3px;
3342
+ }
3343
+
3344
+ .marketplace-btn:hover {
3345
+ color: var(--accent);
3346
+ filter: drop-shadow(0 0 3px var(--accent));
3347
+ }
3348
+
3333
3349
  .project-group-header .group-count {
3334
3350
  font-weight: 400;
3335
3351
  color: var(--text-muted);
package/server.js CHANGED
@@ -48,6 +48,17 @@ function getClaudeDir() {
48
48
  return process.env.CLAUDE_DIR || path.join(os.homedir(), '.claude');
49
49
  }
50
50
 
51
+ function getMarketplaceUrl() {
52
+ const idx = process.argv.findIndex(arg => arg.startsWith('--marketplace-url'));
53
+ if (idx !== -1) {
54
+ const arg = process.argv[idx];
55
+ if (arg.includes('=')) return arg.split('=').slice(1).join('=');
56
+ if (process.argv[idx + 1]) return process.argv[idx + 1];
57
+ }
58
+ return process.env.MARKETPLACE_URL || null;
59
+ }
60
+
61
+ const MARKETPLACE_URL = getMarketplaceUrl();
51
62
  const CLAUDE_DIR = getClaudeDir();
52
63
  const TASKS_DIR = path.join(CLAUDE_DIR, 'tasks');
53
64
  const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
@@ -181,6 +192,10 @@ app.param('taskId', (req, res, next, val) => {
181
192
  // Parse JSON bodies
182
193
  app.use(express.json());
183
194
 
195
+ app.get('/hub-config', (_req, res) => {
196
+ res.json({ enabled: !!process.env.CLAUDE_HUB, url: process.env.HUB_URL || null });
197
+ });
198
+
184
199
  // Serve static files
185
200
  app.get('/sw.js', (req, res) => {
186
201
  res.setHeader('Cache-Control', 'no-cache');
@@ -1251,6 +1266,10 @@ app.get('/api/version', (req, res) => {
1251
1266
  res.json({ version: pkg.version });
1252
1267
  });
1253
1268
 
1269
+ app.get('/api/config', (req, res) => {
1270
+ res.json({ marketplaceUrl: MARKETPLACE_URL });
1271
+ });
1272
+
1254
1273
  // API: Get all tasks across all sessions
1255
1274
  app.get('/api/tasks/all', async (req, res) => {
1256
1275
  try {