claude-code-kanban 4.31.1 → 5.1.0

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/public/index.html CHANGED
@@ -32,6 +32,7 @@
32
32
  <script defer src="/app.js"></script>
33
33
  </head>
34
34
  <body class="booting">
35
+ <script src="/vendor/claude-hub-sdk.js"></script>
35
36
  <div id="offline-overlay" class="offline-overlay">
36
37
  <div class="offline-card">
37
38
  <div class="clawd-sleep" aria-hidden="true">
@@ -74,7 +75,7 @@
74
75
 
75
76
  <!-- Tasks -->
76
77
  <div class="sidebar-section flex-1">
77
- <div class="section-header" onclick="toggleSection('sessions-filters', 'sessions-chevron')" style="cursor: pointer;">
78
+ <div class="section-header">
78
79
  <span class="section-header-label">Sessions</span>
79
80
  <span id="filter-summary" class="filter-summary" title=""></span>
80
81
  <span class="section-header-actions">
@@ -116,9 +117,16 @@
116
117
  <path d="M3.51 15a9 9 0 1 0 2.13-9.36L3 8"/>
117
118
  </svg>
118
119
  </button>
119
- <svg id="sessions-chevron" class="collapse-chevron" viewBox="0 0 24 24">
120
- <path d="M6 9l6 6 6-6"/>
121
- </svg>
120
+ <button
121
+ class="icon-btn section-btn"
122
+ onclick="toggleSection('sessions-filters', 'sessions-chevron')"
123
+ title="Show or hide search and filters"
124
+ aria-label="Show or hide search and filters"
125
+ >
126
+ <svg id="sessions-chevron" class="collapse-chevron" viewBox="0 0 24 24">
127
+ <path d="M6 9l6 6 6-6"/>
128
+ </svg>
129
+ </button>
122
130
  </span>
123
131
  <div id="filter-menu" class="filter-menu" onclick="event.stopPropagation()">
124
132
  <div class="pf-field">
@@ -325,6 +333,7 @@
325
333
  <div id="terminal-boot" class="terminal-boot" aria-hidden="true">
326
334
  <svg class="clawd clawd-jump" viewBox="1 0 16 10" shape-rendering="crispEdges"><use href="#clawd-shape"/></svg>
327
335
  <p id="terminal-boot-verb"></p>
336
+ <p class="terminal-boot-slow">Slow to connect. Press <kbd>R</kbd> to reconnect.</p>
328
337
  </div>
329
338
  <div id="terminal-host" class="terminal-host"></div>
330
339
  </div>
@@ -450,12 +459,17 @@
450
459
  <div class="modal modal-help" onclick="event.stopPropagation()">
451
460
  <div class="modal-header">
452
461
  <h3 class="modal-title">Keyboard Shortcuts</h3>
462
+ <a class="help-docs" id="help-docs" href="https://nikiforovall.blog/claude-code-kanban/" target="_blank" rel="noopener">Docs ↗</a>
453
463
  <button class="modal-close" aria-label="Close dialog" onclick="closeHelpModal()">
454
464
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
455
465
  <path d="M18 6L6 18M6 6l12 12"/>
456
466
  </svg>
457
467
  </button>
458
468
  </div>
469
+ <div class="help-bar">
470
+ <div class="help-tabs" id="help-tabs" role="tablist" aria-label="Shortcut groups"></div>
471
+ <input type="search" id="help-filter" class="form-input help-filter" placeholder="Filter shortcuts" aria-label="Filter shortcuts" autocomplete="off" spellcheck="false">
472
+ </div>
459
473
  <div class="modal-body">
460
474
  <dl class="shortcuts" id="help-shortcuts"></dl>
461
475
  </div>
@@ -801,6 +815,19 @@
801
815
  </div>
802
816
  </div>
803
817
 
818
+ <div id="project-picker-modal" class="modal-overlay top-aligned" onclick="closeProjectPicker()">
819
+ <div class="modal session-picker" onclick="event.stopPropagation()">
820
+ <input type="text" id="project-picker-input" class="form-input sp-input" placeholder="Filter by project..." autocomplete="off" spellcheck="false">
821
+ <div class="pp-chips">
822
+ <button type="button" class="pf-recent" id="pp-recent-chip" aria-pressed="false" title="List only projects active in the last 24h" onclick="ppToggleRecentOnly()">Recent</button>
823
+ </div>
824
+ <div class="sp-body">
825
+ <div id="project-picker-list" class="sp-list"></div>
826
+ </div>
827
+ <div class="sp-hint"><kbd>&uarr;</kbd><kbd>&darr;</kbd> move &middot; <kbd>Enter</kbd> filter &middot; <kbd>Alt</kbd><kbd>R</kbd> recent &middot; <kbd>Alt</kbd><kbd>C</kbd> reset filter &middot; <kbd>Esc</kbd> close</div>
828
+ </div>
829
+ </div>
830
+
804
831
  <div id="toast" class="toast"></div>
805
832
  </body>
806
833
  </html>
package/public/style.css CHANGED
@@ -232,6 +232,25 @@ body::before {
232
232
  animation: pulse 2.5s ease-in-out infinite;
233
233
  }
234
234
 
235
+ .activity-icon {
236
+ display: inline-flex;
237
+ margin: 0 -2px;
238
+ color: var(--chip-color, var(--accent));
239
+ }
240
+ .activity-icon svg {
241
+ width: 11px;
242
+ height: 11px;
243
+ }
244
+ .activity-chip.activity-zero .activity-icon {
245
+ color: var(--text-muted);
246
+ }
247
+ .activity-chip.activity-warn {
248
+ --chip-color: var(--warning);
249
+ background: var(--warning-dim);
250
+ border-color: color-mix(in srgb, var(--chip-color) 60%, var(--border));
251
+ color: var(--chip-color);
252
+ }
253
+
235
254
  .sidebar.collapsed .activity-chips {
236
255
  display: none;
237
256
  }
@@ -942,6 +961,36 @@ body.booting .boot-splash {
942
961
  background: var(--success);
943
962
  }
944
963
 
964
+ .session-item.warm.idle .session-time {
965
+ color: var(--text-tertiary);
966
+ }
967
+
968
+ .session-item.warm.idle .session-time::before {
969
+ background: var(--text-tertiary);
970
+ }
971
+
972
+ /* Declared on the element, not :root, so it picks up body.light's --success. */
973
+ .session-item.just-finished,
974
+ .sp-state.just-finished {
975
+ --success-faded: color-mix(in srgb, var(--success) 35%, var(--text-muted));
976
+ }
977
+
978
+ .session-item.warm.idle.just-finished .session-time {
979
+ color: var(--success-faded);
980
+ }
981
+
982
+ .session-item.warm.idle.just-finished .session-time::before {
983
+ background: var(--success-faded);
984
+ }
985
+
986
+ .session-item.warm.permission-pending .session-time {
987
+ color: var(--warning);
988
+ }
989
+
990
+ .session-item.warm.permission-pending .session-time::before {
991
+ background: var(--warning);
992
+ }
993
+
945
994
  .session-item.stale .session-time {
946
995
  color: var(--text-muted);
947
996
  }
@@ -962,6 +1011,27 @@ body.booting .boot-splash {
962
1011
  padding-right: 22px;
963
1012
  }
964
1013
 
1014
+ .session-item.unread::before,
1015
+ .session-item.permission-pending::before {
1016
+ content: '';
1017
+ position: absolute;
1018
+ inset: -1px;
1019
+ border-left: 3px solid var(--accent);
1020
+ border-radius: inherit;
1021
+ pointer-events: none;
1022
+ }
1023
+
1024
+ .session-item.permission-pending::before {
1025
+ border-left-color: var(--warning);
1026
+ animation: agent-badge-pulse 1.6s ease-in-out infinite;
1027
+ }
1028
+
1029
+ @media (prefers-reduced-motion: reduce) {
1030
+ .session-item.permission-pending::before {
1031
+ animation: none;
1032
+ }
1033
+ }
1034
+
965
1035
  .session-indicators .pulse,
966
1036
  .wf-live-name .pulse,
967
1037
  .session-waiting .pulse {
@@ -3132,6 +3202,14 @@ body.booting .boot-splash {
3132
3202
  animation: boot-fade 0.3s ease-out 0.2s both;
3133
3203
  }
3134
3204
 
3205
+ .terminal-boot-slow {
3206
+ display: none;
3207
+ }
3208
+
3209
+ .terminal-boot.slow .terminal-boot-slow {
3210
+ display: block;
3211
+ }
3212
+
3135
3213
  /* Opacity, not visibility: a hidden textarea drops focus, and the terminal must keep it. */
3136
3214
  .terminal-pane:has(.terminal-boot.visible) .xterm {
3137
3215
  opacity: 0;
@@ -5587,6 +5665,7 @@ body.light .msg-assistant .msg-text {
5587
5665
  the viewport cap. */
5588
5666
  .modal-help {
5589
5667
  max-width: 900px;
5668
+ height: min(700px, 90vh);
5590
5669
  padding: 16px 20px 20px;
5591
5670
  display: flex;
5592
5671
  flex-direction: column;
@@ -5599,11 +5678,30 @@ body.light .msg-assistant .msg-text {
5599
5678
  font-size: 14px;
5600
5679
  }
5601
5680
 
5681
+ .help-docs {
5682
+ margin: 0 12px 0 auto;
5683
+ padding: 4px 10px;
5684
+ border: 1px solid var(--border);
5685
+ border-radius: 6px;
5686
+ color: var(--text-secondary);
5687
+ font-size: 12px;
5688
+ text-decoration: none;
5689
+ white-space: nowrap;
5690
+ transition:
5691
+ color 0.15s ease,
5692
+ border-color 0.15s ease;
5693
+ }
5694
+
5695
+ .help-docs:hover {
5696
+ color: var(--accent);
5697
+ border-color: var(--accent);
5698
+ }
5699
+
5602
5700
  /* One shared grid for both halves (key | label | spacer | key | label): each key
5603
5701
  gutter is sized by its column's widest combo, and left/right rows share row
5604
5702
  tracks, so paired groups line up — a shorter group just leaves empty rows.
5605
5703
  Cells opt into a half via sc-l / sc-r; the markup is generated from
5606
- SHORTCUT_PAIRS in app.js. */
5704
+ SHORTCUT_TABS in app.js. */
5607
5705
  .shortcuts {
5608
5706
  margin: 0;
5609
5707
  display: grid;
@@ -5666,7 +5764,8 @@ body.light .msg-assistant .msg-text {
5666
5764
 
5667
5765
  /* keycap chips: elevated fill + thicker bottom border reads as a key */
5668
5766
  .sp-hint kbd,
5669
- .shortcuts kbd {
5767
+ .shortcuts kbd,
5768
+ .terminal-boot-slow kbd {
5670
5769
  display: inline-flex;
5671
5770
  align-items: center;
5672
5771
  justify-content: center;
@@ -5689,10 +5788,31 @@ body.light .msg-assistant .msg-text {
5689
5788
  font-size: 11px;
5690
5789
  }
5691
5790
 
5692
- /* Hub jumps are no-ops outside Claude Code Hub, so the standalone board hides
5693
- that whole column rather than listing keys that do nothing. */
5694
- .shortcuts.sc-standalone .sc-hub {
5695
- display: none;
5791
+ .shortcuts .sc-empty {
5792
+ grid-column: 1 / -1;
5793
+ padding: 24px 0;
5794
+ color: var(--text-muted);
5795
+ text-align: center;
5796
+ }
5797
+
5798
+ .help-bar {
5799
+ display: flex;
5800
+ align-items: flex-end;
5801
+ gap: 12px;
5802
+ margin-bottom: 12px;
5803
+ border-bottom: 1px solid var(--border);
5804
+ flex-shrink: 0;
5805
+ }
5806
+
5807
+ .help-tabs {
5808
+ display: flex;
5809
+ }
5810
+
5811
+ .help-bar .help-filter {
5812
+ margin: 0 0 6px auto;
5813
+ width: 220px;
5814
+ padding: 4px 10px;
5815
+ font-size: 12px;
5696
5816
  }
5697
5817
 
5698
5818
  @keyframes live-border-pulse {
@@ -5998,16 +6118,46 @@ select.form-input option:checked {
5998
6118
  border-color: var(--accent);
5999
6119
  }
6000
6120
 
6001
- .sp-row .activity-dot.live {
6002
- background: var(--success);
6121
+ .sp-state {
6122
+ flex: none;
6123
+ width: 14px;
6124
+ display: flex;
6125
+ align-items: center;
6126
+ justify-content: center;
6127
+ line-height: 0;
6003
6128
  }
6004
6129
 
6005
- .sp-row .activity-dot.active {
6006
- background: var(--accent);
6130
+ .sp-state.live {
6131
+ color: var(--success);
6007
6132
  }
6008
6133
 
6009
- .sp-row .activity-dot.waiting {
6010
- background: var(--warning);
6134
+ .sp-state.active {
6135
+ color: var(--accent);
6136
+ }
6137
+
6138
+ .sp-state.waiting {
6139
+ color: var(--warning);
6140
+ }
6141
+
6142
+ .sp-state.just-finished {
6143
+ color: var(--success-faded);
6144
+ }
6145
+
6146
+ .sp-state.recent {
6147
+ color: var(--text-tertiary);
6148
+ }
6149
+
6150
+ .sp-state.idle {
6151
+ color: var(--text-muted);
6152
+ }
6153
+
6154
+ .sp-state .activity-dot {
6155
+ background: currentColor;
6156
+ }
6157
+
6158
+ .sp-state.idle .activity-dot {
6159
+ background: transparent;
6160
+ border: 1px solid currentColor;
6011
6161
  }
6012
6162
 
6013
6163
  .sp-name,
@@ -6024,6 +6174,24 @@ select.form-input option:checked {
6024
6174
  color: var(--text-primary);
6025
6175
  }
6026
6176
 
6177
+ .sp-pin {
6178
+ flex: none;
6179
+ width: 14px;
6180
+ display: flex;
6181
+ justify-content: center;
6182
+ line-height: 0;
6183
+ }
6184
+
6185
+ .sp-pin.pinned svg {
6186
+ fill: var(--accent);
6187
+ stroke: var(--accent);
6188
+ }
6189
+
6190
+ .sp-pin.sticky svg {
6191
+ fill: var(--warning);
6192
+ stroke: var(--warning);
6193
+ }
6194
+
6027
6195
  .sp-row:not(.outside) + .sp-row.outside {
6028
6196
  margin-top: 8px;
6029
6197
  }
@@ -6076,6 +6244,17 @@ select.form-input option:checked {
6076
6244
  font-size: 11px;
6077
6245
  }
6078
6246
 
6247
+ .pp-chips {
6248
+ display: flex;
6249
+ gap: 6px;
6250
+ }
6251
+
6252
+ .sp-row.recent .sp-name::before {
6253
+ content: '•';
6254
+ margin-right: 5px;
6255
+ color: var(--accent-text);
6256
+ }
6257
+
6079
6258
  /* #endregion */
6080
6259
 
6081
6260
  /* #region NEW_SESSION */
@@ -6556,7 +6735,8 @@ select.form-input option:checked {
6556
6735
  flex-shrink: 0;
6557
6736
  }
6558
6737
 
6559
- .storage-tab {
6738
+ .storage-tab,
6739
+ .help-tab {
6560
6740
  background: none;
6561
6741
  border: none;
6562
6742
  color: var(--text-secondary);
@@ -6570,11 +6750,13 @@ select.form-input option:checked {
6570
6750
  border-color 0.15s;
6571
6751
  }
6572
6752
 
6573
- .storage-tab:hover {
6753
+ .storage-tab:hover,
6754
+ .help-tab:hover {
6574
6755
  color: var(--text-primary);
6575
6756
  }
6576
6757
 
6577
- .storage-tab.active {
6758
+ .storage-tab.active,
6759
+ .help-tab.active {
6578
6760
  color: var(--accent);
6579
6761
  border-bottom-color: var(--accent);
6580
6762
  }
@@ -7258,6 +7440,11 @@ pre.mermaid svg {
7258
7440
  background: var(--accent-dim);
7259
7441
  }
7260
7442
 
7443
+ /* An empty section shows only while a drag needs its ungroup drop zone. */
7444
+ .sessions-list:not(.sg-dragging-active) .sg-section-empty {
7445
+ display: none;
7446
+ }
7447
+
7261
7448
  .sessions-list.sg-dragging-active .sg-ungroup-zone {
7262
7449
  border: 1px dashed var(--accent);
7263
7450
  border-radius: 4px;
@@ -0,0 +1,50 @@
1
+ // claude-hub-sdk 1.1.0 (sha256 0fed14faa670). Copied by npm run sdk:sync in claude-code-hub. Do not edit.
2
+ // Claude Code Hub SDK stub: what an app serves when no hub runs it. Under a hub, the hub hands the
3
+ // app its real SDK (client.js) in place of this file. The API must match client.js; a test checks it.
4
+ ((root) => {
5
+ 'use strict';
6
+
7
+ function createClaudeHub(win) {
8
+ let hub = null;
9
+ const off = () => {};
10
+
11
+ function connect({ standalone = {} } = {}) {
12
+ if (hub) return hub;
13
+ const standaloneFn = (action) => {
14
+ const fn = (typeof standalone === 'function' ? standalone() : standalone)[action];
15
+ return typeof fn === 'function' ? fn : null;
16
+ };
17
+ hub = {
18
+ status: 'standalone',
19
+ themes: [],
20
+ inHub: false,
21
+ onStatus: () => off,
22
+ onActive: () => off,
23
+ onThemes: () => off,
24
+ subscribe: () => off,
25
+ bindTheme: () => off,
26
+ handle() {},
27
+ publish() {},
28
+ invoke(action, params = {}) {
29
+ const target = standaloneFn(action)?.(params);
30
+ if (!target) return Promise.resolve({ ok: false, reason: 'unhandled' });
31
+ win.open(target, '_blank', 'noopener');
32
+ return Promise.resolve({ ok: true, handledBy: 'standalone' });
33
+ },
34
+ can: (action) => !!standaloneFn(action),
35
+ forwards: () => false,
36
+ closeGuard() {},
37
+ openExternal(url) {
38
+ win.open(url, '_blank', 'noopener');
39
+ },
40
+ terminalToken: () => Promise.resolve(null),
41
+ };
42
+ return hub;
43
+ }
44
+
45
+ return { connect };
46
+ }
47
+
48
+ if (typeof module === 'object' && module.exports) module.exports = { createClaudeHub };
49
+ else root.ClaudeHub = createClaudeHub(root);
50
+ })(typeof window !== 'undefined' ? window : globalThis);