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 +1 -1
- package/lib/worktrees.js +27 -9
- package/package.json +1 -1
- package/public/app.js +25 -19
- package/public/style.css +5 -2
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
|
|
98
|
+
if (hit) return pick(hit);
|
|
74
99
|
if (misses.has(dir)) return null;
|
|
75
100
|
|
|
76
|
-
|
|
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
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
|
-
|
|
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
|
|
8623
|
-
//
|
|
8624
|
-
//
|
|
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
|
|
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
|
}
|