claude-code-kanban 7.12.0 → 7.12.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/hub-app.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "icon": "columns",
6
6
  "run": { "entry": "server.js", "defaultPort": 3541 },
7
7
  "publishes": ["session.changed"],
8
- "keys": { "keeps": ["ctrl+alt+n", "ctrl+alt+r", "ctrl+alt+s", "ctrl+shift+d", "ctrl+shift+k", "ctrl+shift+p", "ctrl+shift+z"] },
8
+ "keys": { "keeps": ["ctrl+alt+n", "ctrl+alt+r", "ctrl+alt+s", "ctrl+shift+d", "ctrl+shift+k", "ctrl+shift+l", "ctrl+shift+m", "ctrl+shift+p", "ctrl+shift+z"] },
9
9
  "plugin": {
10
10
  "marketplace": "plugin",
11
11
  "name": "claude-code-kanban",
package/lib/worktrees.js CHANGED
@@ -34,6 +34,10 @@ function fromGitFile(dir, text) {
34
34
  return { repo: path.resolve(dir, sub[1], ...sub[2].split(/[/\\]modules[/\\]/)), name: sub[3] };
35
35
  }
36
36
 
37
+ function pick(entry) {
38
+ return { repo: entry.repo, name: entry.name };
39
+ }
40
+
37
41
  function fromPathShape(dir) {
38
42
  const m = CLAUDE_WORKTREE_RE.exec(dir);
39
43
  return m ? { repo: m[1], name: m[2] } : null;
@@ -67,20 +71,34 @@ function createWorktreeStore({ load, save, read = readGitFile, now = Date.now, d
67
71
  });
68
72
  }
69
73
 
74
+ // Walks up from `dir` the way git discovers its repo, so a session started in a subfolder of a
75
+ // worktree resolves too. A saved ancestor answers for a worktree whose checkout is gone; the
76
+ // claude path shape answers for one that was never seen.
77
+ function lookUp(dir) {
78
+ for (let d = dir, up; ; d = up) {
79
+ try {
80
+ return fromGitFile(d, read(d));
81
+ } catch (e) {
82
+ // An ordinary checkout's `.git` is a directory, so the read throws EISDIR. That is the
83
+ // answer, and it costs one syscall instead of a stat followed by a read.
84
+ if (e.code !== 'ENOENT') return null;
85
+ }
86
+ const shaped = fromPathShape(d);
87
+ if (shaped) return shaped;
88
+ up = path.dirname(d);
89
+ if (up === d) return null;
90
+ const hit = hits.get(up);
91
+ if (hit) return pick(hit);
92
+ }
93
+ }
94
+
70
95
  function resolve(dir) {
71
96
  if (!dir) return null;
72
97
  const hit = hits.get(dir);
73
- if (hit) return { repo: hit.repo, name: hit.name };
98
+ if (hit) return pick(hit);
74
99
  if (misses.has(dir)) return null;
75
100
 
76
- let worktree = null;
77
- try {
78
- worktree = fromGitFile(dir, read(dir));
79
- } catch (e) {
80
- // An ordinary checkout's `.git` is a directory, so the read throws EISDIR. That is the
81
- // answer, and it costs one syscall instead of a stat followed by a read.
82
- if (e.code === 'ENOENT') worktree = fromPathShape(dir);
83
- }
101
+ const worktree = lookUp(dir);
84
102
 
85
103
  if (!worktree) {
86
104
  misses.add(dir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-kanban",
3
- "version": "7.12.0",
3
+ "version": "7.12.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
  "type": "commonjs",
package/public/app.js CHANGED
@@ -6433,8 +6433,8 @@ const SHORTCUT_TABS = [
6433
6433
  { keys: ['>'], label: 'Toggle sticky' },
6434
6434
  { keys: ['Shift', 'F10'], combo: true, label: 'Session menu: move to group, fork (or Menu key)' },
6435
6435
  { keys: ['Ctrl', 'D'], combo: true, label: 'Dismiss session (pinned: to Pinned group)' },
6436
- { keys: ['Shift', 'L'], combo: true, label: 'Toggle session log' },
6437
- { keys: ['Shift', 'M'], combo: true, label: 'Open last message' },
6436
+ { keys: ['Ctrl', 'Shift', 'L'], combo: true, label: 'Toggle session log' },
6437
+ { keys: ['Ctrl', 'Shift', 'M'], combo: true, label: 'Open last message' },
6438
6438
  { keys: ['↑', '↓'], label: 'Previous / next message in detail' },
6439
6439
  { keys: ['Ctrl', 'Enter'], combo: true, label: 'Allow / approve the waiting prompt; send review comments' },
6440
6440
  ],
@@ -7548,6 +7548,18 @@ function shiftOnly(e, code) {
7548
7548
  return e.code === code && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey;
7549
7549
  }
7550
7550
 
7551
+ function toggleLastMessage() {
7552
+ if (document.getElementById('msg-detail-modal').classList.contains('visible')) {
7553
+ closeMsgDetailModal();
7554
+ } else if (isWaitingFresh()) {
7555
+ msgDetailFollowLatest = true;
7556
+ showWaitingDetail();
7557
+ } else if (currentMessages.length) {
7558
+ msgDetailFollowLatest = true;
7559
+ showMsgDetail(currentMessages.length - 1);
7560
+ }
7561
+ }
7562
+
7551
7563
  function dismissSessionFromList(sid) {
7552
7564
  const prevIdx = selectedSessionIdx;
7553
7565
  if (!setSessionDismissed(sid, true)) return;
@@ -7843,16 +7855,7 @@ document.addEventListener('keydown', (e) => {
7843
7855
  }
7844
7856
  if (shiftOnly(e, 'KeyM')) {
7845
7857
  e.preventDefault();
7846
- const msgDetailModal = document.getElementById('msg-detail-modal');
7847
- if (msgDetailModal.classList.contains('visible')) {
7848
- closeMsgDetailModal();
7849
- } else if (isWaitingFresh()) {
7850
- msgDetailFollowLatest = true;
7851
- showWaitingDetail();
7852
- } else if (currentMessages.length) {
7853
- msgDetailFollowLatest = true;
7854
- showMsgDetail(currentMessages.length - 1);
7855
- }
7858
+ toggleLastMessage();
7856
7859
  return;
7857
7860
  }
7858
7861
  if (shiftOnly(e, 'KeyS')) {
@@ -8619,20 +8622,21 @@ const ZOOM_CLAIMS = ['ctrlKey', 'metaKey'].flatMap((mod) =>
8619
8622
  ),
8620
8623
  );
8621
8624
 
8622
- // The sidebar and tab keys, so they work while a previewed page has focus. Matched by key where
8623
- // the board matches by key, so they follow the keyboard layout. A page that handles one first
8624
- // keeps it, and text fields keep every key.
8625
+ // The board keys a pane gets before the board's inPane return, so they work while a previewed page
8626
+ // has focus; a new key there needs a row here. Matched by key where the board matches by key, so
8627
+ // they follow the keyboard layout, and by code where it matches by code. A page that handles one
8628
+ // first keeps it, and text fields keep every key.
8625
8629
  const PANE_CLAIMS = [
8626
- ...['\\', '[', ']'].map((key) => ({ key, code: '' })),
8627
- ...['|', '{', '}'].map((key) => ({ shiftKey: true, key, code: '' })),
8630
+ ...['\\', '[', ']', 'ContextMenu', 'Escape'].map((key) => ({ key, code: '' })),
8631
+ ...['|', '{', '}', 'F10'].map((key) => ({ shiftKey: true, key, code: '' })),
8628
8632
  ...['BracketLeft', 'BracketRight', 'KeyW'].map((code) => ({ altKey: true, key: 'Unidentified', code })),
8633
+ ...['KeyL', 'KeyM', 'KeyP', 'KeyS', 'KeyZ'].map((code) => ({ shiftKey: true, key: 'Unidentified', code })),
8629
8634
  ];
8630
8635
 
8631
- // Ctrl+D, Alt+Enter and Esc act on the show card (showCardKey); other pages keep them. Alt+W is in PANE_CLAIMS.
8636
+ // Ctrl+D and Alt+Enter act on the show card (showCardKey); other pages keep them. Alt+W and Esc are in PANE_CLAIMS.
8632
8637
  const SHOW_CLAIMS = [
8633
8638
  ...['ctrlKey', 'metaKey'].map((mod) => ({ [mod]: true, key: 'd', code: '' })),
8634
8639
  { altKey: true, key: 'Enter', code: '' },
8635
- { key: 'Escape', code: '' },
8636
8640
  ];
8637
8641
 
8638
8642
  // Claimed only while comments wait, so a previewed page keeps Ctrl+Enter otherwise.
@@ -14780,6 +14784,8 @@ function terminalShortcut(e, probe = false) {
14780
14784
  // Ctrl+Shift+Z is redo in a text field, so only the terminal's own textarea gives it up.
14781
14785
  if (ctrlShift && e.code === 'KeyZ') return inPageField(e) ? null : toggleZenMode;
14782
14786
  if (ctrlShift && e.code === 'KeyK') return kanbotAvailable() ? () => toggleKanbot() : null;
14787
+ if (ctrlShift && e.code === 'KeyL') return toggleMessagePanel;
14788
+ if (ctrlShift && e.code === 'KeyM') return toggleLastMessage;
14783
14789
  if (ctrlShift && e.code === 'KeyD') return dismissContextSession;
14784
14790
  if (ctrlAlt && (e.code === 'KeyN' || e.code === 'KeyR') && terminalAvailable()) {
14785
14791
  return () => openNewSession(null, e.code === 'KeyR');
package/public/style.css CHANGED
@@ -1058,10 +1058,14 @@ body.booting .boot-splash {
1058
1058
  position: absolute;
1059
1059
  inset: -1px;
1060
1060
  border-left: 3px solid var(--accent);
1061
- border-radius: inherit;
1062
1061
  pointer-events: none;
1063
1062
  }
1064
1063
 
1064
+ .session-item.unread::before,
1065
+ .session-item.permission-pending::before {
1066
+ border-radius: inherit;
1067
+ }
1068
+
1065
1069
  .session-item.permission-pending::before,
1066
1070
  .sp-row.permission-pending::before {
1067
1071
  border-left-color: var(--warning);
@@ -9014,7 +9018,6 @@ select.form-input option:checked {
9014
9018
  align-items: baseline;
9015
9019
  gap: 8px;
9016
9020
  padding: 6px 8px;
9017
- border-radius: 4px;
9018
9021
  cursor: pointer;
9019
9022
  font-size: 12px;
9020
9023
  }