claude-code-kanban 4.23.2 → 4.25.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
@@ -22,6 +22,7 @@
22
22
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github.min.css" id="hljs-theme-light" disabled>
23
23
  <script defer src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
24
24
  <script defer src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
25
+ <script>window.__STORAGE_NS__ = null;</script>
25
26
  <script defer src="/app.js"></script>
26
27
  </head>
27
28
  <body>
@@ -346,7 +347,7 @@
346
347
 
347
348
  <!-- Help Modal -->
348
349
  <div id="help-modal" class="modal-overlay" onclick="closeHelpModal()">
349
- <div class="modal" onclick="event.stopPropagation()">
350
+ <div class="modal modal-help" onclick="event.stopPropagation()">
350
351
  <div class="modal-header">
351
352
  <h3 class="modal-title">Keyboard Shortcuts</h3>
352
353
  <button class="modal-close" aria-label="Close dialog" onclick="closeHelpModal()">
@@ -355,137 +356,8 @@
355
356
  </svg>
356
357
  </button>
357
358
  </div>
358
- <div class="modal-body modal-zoomable">
359
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px;">
360
- <div style="display: grid; gap: 16px; align-content: start;">
361
- <div>
362
- <h4 style="margin: 0 0 8px 0; color: var(--text-primary); font-size: 14px; font-weight: 600;">Global</h4>
363
- <table style="width: 100%; font-size: 13px;">
364
- <tr>
365
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">?</kbd></td>
366
- <td style="padding: 4px 0; color: var(--text-primary);">Show keyboard shortcuts</td>
367
- </tr>
368
- <tr>
369
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Esc</kbd></td>
370
- <td style="padding: 4px 0; color: var(--text-primary);">Close panels or cancel</td>
371
- </tr>
372
- <tr>
373
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Tab</kbd></td>
374
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle sidebar / board focus</td>
375
- </tr>
376
- <tr>
377
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">[</kbd></td>
378
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle sidebar collapse</td>
379
- </tr>
380
- <tr>
381
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">R</kbd></td>
382
- <td style="padding: 4px 0; color: var(--text-primary);">Reload page</td>
383
- </tr>
384
- </table>
385
- </div>
386
- <div>
387
- <h4 style="margin: 0 0 8px 0; color: var(--text-primary); font-size: 14px; font-weight: 600;">Navigation</h4>
388
- <table style="width: 100%; font-size: 13px;">
389
- <tr>
390
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">J</kbd> / <kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">&darr;</kbd></td>
391
- <td style="padding: 4px 0; color: var(--text-primary);">Next task in column</td>
392
- </tr>
393
- <tr>
394
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">K</kbd> / <kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">&uarr;</kbd></td>
395
- <td style="padding: 4px 0; color: var(--text-primary);">Previous task in column</td>
396
- </tr>
397
- <tr>
398
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">H</kbd> / <kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">&larr;</kbd></td>
399
- <td style="padding: 4px 0; color: var(--text-primary);">Move to left column</td>
400
- </tr>
401
- <tr>
402
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">L</kbd> / <kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">&rarr;</kbd></td>
403
- <td style="padding: 4px 0; color: var(--text-primary);">Move to right column</td>
404
- </tr>
405
- <tr>
406
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Enter</kbd></td>
407
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle task detail panel</td>
408
- </tr>
409
- </table>
410
- </div>
411
- </div>
412
- <div style="align-content: start;">
413
- <h4 style="margin: 0 0 8px 0; color: var(--text-primary); font-size: 14px; font-weight: 600;">Actions</h4>
414
- <table style="width: 100%; font-size: 13px;">
415
- <tr>
416
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">P</kbd></td>
417
- <td style="padding: 4px 0; color: var(--text-primary);">Open session plan</td>
418
- </tr>
419
- <tr>
420
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">I</kbd></td>
421
- <td style="padding: 4px 0; color: var(--text-primary);">Open session info</td>
422
- </tr>
423
- <tr>
424
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Ctrl+D</kbd></td>
425
- <td style="padding: 4px 0; color: var(--text-primary);">Dismiss selected session</td>
426
- </tr>
427
- <tr>
428
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">D</kbd></td>
429
- <td style="padding: 4px 0; color: var(--text-primary);">Delete selected task</td>
430
- </tr>
431
- <tr>
432
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">N</kbd></td>
433
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle scratchpad</td>
434
- </tr>
435
- <tr>
436
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">.</kbd></td>
437
- <td style="padding: 4px 0; color: var(--text-primary);">Pin/unpin selected session</td>
438
- </tr>
439
- <tr>
440
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">&gt;</kbd></td>
441
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle sticky on selected session</td>
442
- </tr>
443
- <tr>
444
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">T</kbd></td>
445
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle theme</td>
446
- </tr>
447
- <tr>
448
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">M</kbd></td>
449
- <td style="padding: 4px 0; color: var(--text-primary);">Jump to marketplace</td>
450
- </tr>
451
- <tr>
452
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">$</kbd></td>
453
- <td style="padding: 4px 0; color: var(--text-primary);">Jump to cost</td>
454
- </tr>
455
- <tr>
456
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Shift+L</kbd></td>
457
- <td style="padding: 4px 0; color: var(--text-primary);">Toggle session log</td>
458
- </tr>
459
- <tr>
460
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Shift+M</kbd></td>
461
- <td style="padding: 4px 0; color: var(--text-primary);">Open last message detail</td>
462
- </tr>
463
- <tr>
464
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Ctrl++</kbd> / <kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Ctrl+-</kbd></td>
465
- <td style="padding: 4px 0; color: var(--text-primary);">Modal text size</td>
466
- </tr>
467
- <tr>
468
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Ctrl+0</kbd></td>
469
- <td style="padding: 4px 0; color: var(--text-primary);">Reset text size</td>
470
- </tr>
471
- <tr>
472
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Shift+S</kbd></td>
473
- <td style="padding: 4px 0; color: var(--text-primary);">Storage manager</td>
474
- </tr>
475
- <tr>
476
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">Shift+C</kbd></td>
477
- <td style="padding: 4px 0; color: var(--text-primary);">Copy session id</td>
478
- </tr>
479
- <tr>
480
- <td style="padding: 4px 0; color: var(--text-secondary);"><kbd style="background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-family: monospace;">J/K</kbd></td>
481
- <td style="padding: 4px 0; color: var(--text-primary);">Navigate messages in detail modal</td>
482
- </tr>
483
- </table>
484
- </div>
485
- </div>
486
- </div>
487
- <div class="modal-footer">
488
- <button class="btn btn-primary" onclick="closeHelpModal()">Got it</button>
359
+ <div class="modal-body">
360
+ <dl class="shortcuts" id="help-shortcuts"></dl>
489
361
  </div>
490
362
  </div>
491
363
  </div>
@@ -737,6 +609,17 @@
737
609
  </div>
738
610
  </div>
739
611
 
612
+ <div id="session-picker-modal" class="modal-overlay top-aligned" onclick="closeSessionPicker()">
613
+ <div class="modal session-picker" onclick="event.stopPropagation()">
614
+ <input type="text" id="session-picker-input" class="form-input sp-input" placeholder="Jump to session..." autocomplete="off" spellcheck="false">
615
+ <div class="sp-body">
616
+ <div id="session-picker-list" class="sp-list"></div>
617
+ <div id="session-picker-peek" class="sp-peek"></div>
618
+ </div>
619
+ <div class="sp-hint"><kbd>&uarr;</kbd><kbd>&darr;</kbd> move &middot; <kbd>Enter</kbd> open &middot; <kbd>Ctrl</kbd><kbd>Space</kbd> peek &middot; <kbd>Esc</kbd> close</div>
620
+ </div>
621
+ </div>
622
+
740
623
  <div id="toast" class="toast"></div>
741
624
  </body>
742
625
  </html>
package/public/style.css CHANGED
@@ -2264,7 +2264,8 @@ body::before {
2264
2264
  flex-direction: column;
2265
2265
  }
2266
2266
  #team-modal .modal-body,
2267
- #agent-modal .modal-body {
2267
+ #agent-modal .modal-body,
2268
+ .modal-help .modal-body {
2268
2269
  flex: 1 1 auto;
2269
2270
  overflow-y: auto;
2270
2271
  min-height: 0;
@@ -2349,7 +2350,6 @@ body::before {
2349
2350
  padding: 10px 14px;
2350
2351
  background: var(--bg-elevated);
2351
2352
  border: 1px solid var(--border);
2352
- border-left: 3px solid var(--warning);
2353
2353
  border-radius: 8px;
2354
2354
  }
2355
2355
  .info-goal-head {
@@ -4264,6 +4264,13 @@ body.light .msg-assistant .msg-text {
4264
4264
  display: flex;
4265
4265
  }
4266
4266
 
4267
+ /* For pickers: the dialog sits above the vertical centre so its list grows
4268
+ downward instead of shifting rows under the cursor as it filters. */
4269
+ .modal-overlay.top-aligned {
4270
+ align-items: flex-start;
4271
+ padding-top: 12vh;
4272
+ }
4273
+
4267
4274
  .plan-modal-overlay {
4268
4275
  z-index: 10001;
4269
4276
  background: rgba(0, 0, 0, 0.6);
@@ -4512,6 +4519,119 @@ body.light .msg-assistant .msg-text {
4512
4519
  max-width: 440px;
4513
4520
  }
4514
4521
 
4522
+ /* Reference card, not reading material: it stays out of the modal zoom, uses the app's
4523
+ small type, and scrolls its body (rule shared with #team-modal) instead of growing past
4524
+ the viewport cap. */
4525
+ .modal-help {
4526
+ max-width: 900px;
4527
+ padding: 16px 20px 20px;
4528
+ display: flex;
4529
+ flex-direction: column;
4530
+ font-size: 13px;
4531
+ }
4532
+ .modal-help .modal-header {
4533
+ margin-bottom: 8px;
4534
+ }
4535
+ .modal-help .modal-title {
4536
+ font-size: 14px;
4537
+ }
4538
+
4539
+ /* One shared grid for both halves (key | label | spacer | key | label): each key
4540
+ gutter is sized by its column's widest combo, and left/right rows share row
4541
+ tracks, so paired groups line up — a shorter group just leaves empty rows.
4542
+ Cells opt into a half via sc-l / sc-r; the markup is generated from
4543
+ SHORTCUT_PAIRS in app.js. */
4544
+ .shortcuts {
4545
+ margin: 0;
4546
+ display: grid;
4547
+ grid-template-columns: max-content 1fr 28px max-content 1fr;
4548
+ column-gap: 14px;
4549
+ }
4550
+
4551
+ .shortcuts dt.sc-l {
4552
+ grid-column: 1;
4553
+ }
4554
+ .shortcuts dd.sc-l {
4555
+ grid-column: 2;
4556
+ }
4557
+ .shortcuts dt.sc-r {
4558
+ grid-column: 4;
4559
+ }
4560
+ .shortcuts dd.sc-r {
4561
+ grid-column: 5;
4562
+ }
4563
+ .shortcuts .sc-group.sc-l {
4564
+ grid-column: 1 / 3;
4565
+ }
4566
+ .shortcuts .sc-group.sc-r {
4567
+ grid-column: 4 / 6;
4568
+ }
4569
+
4570
+ .shortcuts dt {
4571
+ margin: 0;
4572
+ display: flex;
4573
+ align-items: center;
4574
+ align-self: center;
4575
+ gap: 4px;
4576
+ padding: 3px 0;
4577
+ }
4578
+
4579
+ .shortcuts dd {
4580
+ margin: 0;
4581
+ font-size: 13px;
4582
+ color: var(--text-secondary);
4583
+ align-self: center;
4584
+ padding: 3px 0;
4585
+ }
4586
+
4587
+ /* Section headings carry the column: full-width rule + primary ink, so the eye
4588
+ finds the group before it reads any single row. */
4589
+ .shortcuts .sc-group {
4590
+ padding: 18px 0 6px;
4591
+ margin-bottom: 6px;
4592
+ font-size: 11px;
4593
+ font-weight: 700;
4594
+ letter-spacing: 0.16em;
4595
+ text-transform: uppercase;
4596
+ color: var(--text-primary);
4597
+ border-bottom: 1px solid var(--border);
4598
+ }
4599
+
4600
+ .shortcuts .sc-group.sc-first {
4601
+ padding-top: 2px;
4602
+ }
4603
+
4604
+ /* keycap chips: elevated fill + thicker bottom border reads as a key */
4605
+ .sp-hint kbd,
4606
+ .shortcuts kbd {
4607
+ display: inline-flex;
4608
+ align-items: center;
4609
+ justify-content: center;
4610
+ min-width: 22px;
4611
+ height: 22px;
4612
+ padding: 0 6px;
4613
+ box-sizing: border-box;
4614
+ font-family: var(--font-mono, monospace);
4615
+ font-size: 11px;
4616
+ color: var(--text-primary);
4617
+ background: var(--bg-hover);
4618
+ border: 1px solid var(--border);
4619
+ border-bottom-width: 2px;
4620
+ border-radius: 5px;
4621
+ }
4622
+
4623
+ .shortcuts .sc-plus,
4624
+ .shortcuts .sc-or {
4625
+ color: var(--text-muted);
4626
+ font-size: 11px;
4627
+ }
4628
+
4629
+ /* Hub jumps are no-ops outside Claude Code Hub, so the standalone board hides
4630
+ that whole column rather than listing keys that do nothing. */
4631
+ .shortcuts.sc-standalone .sc-hub {
4632
+ display: none;
4633
+ }
4634
+
4515
4635
  @keyframes live-border-pulse {
4516
4636
  0%,
4517
4637
  100% {
@@ -4721,6 +4841,176 @@ select.form-input option:checked {
4721
4841
 
4722
4842
  /* #endregion */
4723
4843
 
4844
+ /* #region SESSION_PICKER */
4845
+ .modal.session-picker {
4846
+ max-height: 70vh;
4847
+ padding: 12px;
4848
+ display: flex;
4849
+ flex-direction: column;
4850
+ gap: 8px;
4851
+ overflow: hidden;
4852
+ }
4853
+
4854
+ .sp-input {
4855
+ padding: 9px 12px;
4856
+ background: var(--bg-deep);
4857
+ font-size: 13px;
4858
+ }
4859
+
4860
+ .modal.session-picker.peeking {
4861
+ max-width: 1240px;
4862
+ }
4863
+
4864
+ .sp-body {
4865
+ display: flex;
4866
+ gap: 10px;
4867
+ min-height: 0;
4868
+ }
4869
+
4870
+ .sp-body > * {
4871
+ min-width: 0;
4872
+ }
4873
+
4874
+ .sp-list {
4875
+ flex: 1;
4876
+ overflow-y: auto;
4877
+ display: flex;
4878
+ flex-direction: column;
4879
+ gap: 2px;
4880
+ }
4881
+
4882
+ .sp-peek {
4883
+ flex: 0 0 42%;
4884
+ overflow-y: auto;
4885
+ border-left: 1px solid var(--border);
4886
+ padding-left: 10px;
4887
+ }
4888
+
4889
+ .modal.session-picker:not(.peeking) .sp-peek {
4890
+ display: none;
4891
+ }
4892
+
4893
+ .sp-peek-title {
4894
+ display: flex;
4895
+ align-items: center;
4896
+ justify-content: space-between;
4897
+ gap: 8px;
4898
+ margin-bottom: 6px;
4899
+ font-family: var(--mono);
4900
+ font-size: 11px;
4901
+ color: var(--text-muted);
4902
+ white-space: nowrap;
4903
+ overflow: hidden;
4904
+ text-overflow: ellipsis;
4905
+ }
4906
+
4907
+ /* Row clicks open the live panel's detail modal by index, which means nothing
4908
+ for a peeked session. The renderer's readOnly mode already drops the buttons. */
4909
+ .sp-peek-items {
4910
+ pointer-events: none;
4911
+ }
4912
+
4913
+ .sp-row {
4914
+ display: flex;
4915
+ align-items: center;
4916
+ gap: 8px;
4917
+ width: 100%;
4918
+ padding: 6px 8px;
4919
+ background: none;
4920
+ border: 1px solid transparent;
4921
+ border-radius: 5px;
4922
+ color: var(--text-secondary);
4923
+ font-family: var(--mono);
4924
+ font-size: 12px;
4925
+ text-align: left;
4926
+ cursor: pointer;
4927
+ }
4928
+
4929
+ .sp-row:hover {
4930
+ background: var(--bg-hover);
4931
+ }
4932
+
4933
+ .sp-row.selected {
4934
+ background: var(--bg-elevated);
4935
+ border-color: var(--accent);
4936
+ }
4937
+
4938
+ .sp-row .activity-dot.live {
4939
+ background: var(--success);
4940
+ }
4941
+
4942
+ .sp-row .activity-dot.active {
4943
+ background: var(--accent);
4944
+ }
4945
+
4946
+ .sp-row .activity-dot.waiting {
4947
+ background: var(--warning);
4948
+ }
4949
+
4950
+ .sp-name,
4951
+ .sp-project,
4952
+ .sp-branch {
4953
+ overflow: hidden;
4954
+ text-overflow: ellipsis;
4955
+ white-space: nowrap;
4956
+ }
4957
+
4958
+ .sp-name {
4959
+ flex: none;
4960
+ max-width: 40%;
4961
+ color: var(--text-primary);
4962
+ }
4963
+
4964
+ .sp-row.current .sp-name {
4965
+ color: var(--accent-text);
4966
+ }
4967
+
4968
+ .sp-project {
4969
+ flex: 1;
4970
+ min-width: 0;
4971
+ color: var(--text-muted);
4972
+ }
4973
+
4974
+ .sp-branch {
4975
+ flex: none;
4976
+ max-width: 25%;
4977
+ padding: 1px 5px;
4978
+ border-radius: 3px;
4979
+ background: var(--bg-hover);
4980
+ color: var(--text-tertiary);
4981
+ font-size: 11px;
4982
+ }
4983
+
4984
+ .sp-count,
4985
+ .sp-time {
4986
+ flex: none;
4987
+ color: var(--text-muted);
4988
+ font-size: 11px;
4989
+ white-space: nowrap;
4990
+ }
4991
+
4992
+ .sp-time {
4993
+ min-width: 62px;
4994
+ text-align: right;
4995
+ }
4996
+
4997
+ .sp-empty {
4998
+ padding: 20px 8px;
4999
+ color: var(--text-muted);
5000
+ font-size: 12px;
5001
+ text-align: center;
5002
+ }
5003
+
5004
+ .sp-hint {
5005
+ display: flex;
5006
+ align-items: center;
5007
+ gap: 5px;
5008
+ color: var(--text-muted);
5009
+ font-size: 11px;
5010
+ }
5011
+
5012
+ /* #endregion */
5013
+
4724
5014
  /* #region TOOL_STATS */
4725
5015
  .modal.tool-stats-modal {
4726
5016
  width: 90%;
@@ -5314,21 +5604,24 @@ pre.mermaid svg {
5314
5604
  }
5315
5605
 
5316
5606
  .project-group-header.kb-selected,
5317
- .pinned-sub-header.kb-selected {
5607
+ .pinned-sub-header.kb-selected,
5608
+ .sg-section-toggle.kb-selected {
5318
5609
  color: var(--text-primary);
5319
5610
  background: var(--bg-hover);
5320
5611
  border-radius: 4px;
5321
5612
  }
5322
5613
 
5323
5614
  .project-group-header .group-chevron,
5324
- .session-group-header .group-chevron {
5615
+ .session-group-header .group-chevron,
5616
+ .sg-section-toggle .group-chevron {
5325
5617
  transition: transform 0.15s ease;
5326
5618
  flex-shrink: 0;
5327
5619
  display: block;
5328
5620
  }
5329
5621
 
5330
5622
  .project-group-header.collapsed .group-chevron,
5331
- .session-group-header.collapsed .group-chevron {
5623
+ .session-group-header.collapsed .group-chevron,
5624
+ .sg-section-toggle.collapsed .group-chevron {
5332
5625
  transform: rotate(-90deg);
5333
5626
  }
5334
5627
 
@@ -5522,6 +5815,18 @@ pre.mermaid svg {
5522
5815
  margin-top: 0;
5523
5816
  }
5524
5817
 
5818
+ .sg-section-toggle {
5819
+ display: flex;
5820
+ align-items: center;
5821
+ gap: 4px;
5822
+ padding-bottom: 4px;
5823
+ cursor: pointer;
5824
+ }
5825
+
5826
+ .sg-section-toggle:hover {
5827
+ color: var(--text-primary);
5828
+ }
5829
+
5525
5830
  .session-group-header {
5526
5831
  display: flex;
5527
5832
  align-items: center;
@@ -5584,7 +5889,8 @@ pre.mermaid svg {
5584
5889
  margin-left: 6px;
5585
5890
  }
5586
5891
 
5587
- .session-group-sessions.collapsed {
5892
+ .session-group-sessions.collapsed,
5893
+ .sg-section-body.collapsed {
5588
5894
  display: none;
5589
5895
  }
5590
5896
 
package/server.js CHANGED
@@ -34,6 +34,7 @@ const {
34
34
  } = require('./lib/parsers');
35
35
  const { inlineHtmlAssets } = require('./lib/inline-assets');
36
36
  const { buildDecision, decisionFileName, isDecisionFile, waitSecondsFrom, isLapsed } = require('./lib/approvals');
37
+ const { getClaudeDir, getArgValue, storageNamespace } = require('./lib/claude-dir');
37
38
 
38
39
  if (process.argv.includes("--install") || process.argv.includes("--uninstall")) {
39
40
  const { runInstall, runUninstall } = require("./install");
@@ -56,30 +57,8 @@ app.use(net.hostGuard);
56
57
  app.use(net.frameGuard);
57
58
  app.use(net.originGuard);
58
59
 
59
- // Parse --dir flag for custom Claude directory
60
- function getClaudeDir() {
61
- const dirIndex = process.argv.findIndex(arg => arg.startsWith('--dir'));
62
- if (dirIndex !== -1) {
63
- const arg = process.argv[dirIndex];
64
- if (arg.includes('=')) {
65
- const dir = arg.split('=')[1];
66
- return dir.startsWith('~') ? dir.replace('~', os.homedir()) : dir;
67
- } else if (process.argv[dirIndex + 1]) {
68
- const dir = process.argv[dirIndex + 1];
69
- return dir.startsWith('~') ? dir.replace('~', os.homedir()) : dir;
70
- }
71
- }
72
- return process.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_DIR || path.join(os.homedir(), '.claude');
73
- }
74
-
75
60
  function getArgUrl(argName, envName) {
76
- const idx = process.argv.findIndex(arg => arg.startsWith(`--${argName}`));
77
- if (idx !== -1) {
78
- const arg = process.argv[idx];
79
- if (arg.includes('=')) return arg.split('=').slice(1).join('=');
80
- if (process.argv[idx + 1]) return process.argv[idx + 1];
81
- }
82
- return process.env[envName] || null;
61
+ return getArgValue(argName) || process.env[envName] || null;
83
62
  }
84
63
 
85
64
  const MARKETPLACE_URL = getArgUrl('marketplace-url', 'MARKETPLACE_URL');
@@ -516,6 +495,18 @@ app.get('/hub-config', (_req, res) => {
516
495
  res.json({ enabled: !!process.env.CLAUDE_HUB, url: process.env.HUB_URL || null });
517
496
  });
518
497
 
498
+ // app.js reads localStorage synchronously at startup, before any fetch could deliver the
499
+ // namespace, so it is injected into the page instead of served from /hub-config.
500
+ const STORAGE_NS = storageNamespace(CLAUDE_DIR);
501
+ const NS_DECL = 'window.__STORAGE_NS__ = null;';
502
+ const RAW_INDEX = readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
503
+ if (!RAW_INDEX.includes(NS_DECL)) throw new Error(`public/index.html is missing "${NS_DECL}"`);
504
+ const INDEX_HTML = RAW_INDEX.replace(NS_DECL, `window.__STORAGE_NS__ = ${JSON.stringify(STORAGE_NS)};`);
505
+ app.get(['/', '/index.html'], (_req, res) => {
506
+ res.setHeader('Cache-Control', 'no-cache');
507
+ res.type('html').send(INDEX_HTML);
508
+ });
509
+
519
510
  // Serve static files
520
511
  app.get('/sw.js', (_req, res) => {
521
512
  res.setHeader('Cache-Control', 'no-cache');