claude-code-kanban 7.16.0 → 7.18.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/style.css CHANGED
@@ -834,6 +834,111 @@ body::before {
834
834
  padding: 0 14px 12px;
835
835
  }
836
836
 
837
+ .sessions-list.zen {
838
+ --zen-dot: var(--border);
839
+ display: flex;
840
+ flex-direction: column;
841
+ }
842
+
843
+ .sessions-list.zen[data-zen-state='working'] {
844
+ --zen-dot: color-mix(in srgb, var(--success) 45%, var(--border));
845
+ }
846
+
847
+ .sessions-list.zen[data-zen-state='waiting'] {
848
+ --zen-dot: color-mix(in srgb, var(--warning) 55%, var(--border));
849
+ }
850
+
851
+ /* The spacers fill the free space. Centered, they split it 1:2, so a growing panel moves the card up by a third of the growth, not a half. With no free space they are 0 and the list scrolls from the top. */
852
+ .sessions-list.zen::after,
853
+ .sessions-list.zen.zen-centered::before {
854
+ content: '';
855
+ flex: 1 0 0;
856
+ }
857
+
858
+ /* Dots unless the hub's zenPattern setting names another pattern. Line patterns use a lighter color, because a line has more ink than a dot. */
859
+ .sessions-list.zen {
860
+ --zen-line: color-mix(in srgb, var(--zen-dot) 50%, transparent);
861
+ --zen-fill: radial-gradient(circle, var(--zen-dot) 1px, transparent 1.5px) 0 0 / 20px 20px;
862
+ --zen-glow-y: 0;
863
+ }
864
+
865
+ .sessions-list.zen[data-zen-pattern='diagonal'] {
866
+ --zen-fill: repeating-linear-gradient(-45deg, var(--zen-line) 0 1px, transparent 1px 14px);
867
+ }
868
+
869
+ .sessions-list.zen[data-zen-pattern='rings'] {
870
+ --zen-fill: radial-gradient(circle, transparent 2.5px, var(--zen-line) 3px 3.5px, transparent 4px) 0 0 / 28px 28px;
871
+ }
872
+
873
+ .sessions-list.zen[data-zen-pattern='grid'] {
874
+ --zen-fill:
875
+ linear-gradient(var(--zen-line) 1px, transparent 1px) 0 0 / 24px 24px,
876
+ linear-gradient(90deg, var(--zen-line) 1px, transparent 1px) 0 0 / 24px 24px;
877
+ }
878
+
879
+ /* The pattern glows out from the card. The glow has a fixed height, so it looks the same in a short space and a tall one. */
880
+ .sessions-list.zen:not([data-zen-pattern='none'])::after,
881
+ .sessions-list.zen.zen-centered:not([data-zen-pattern='none'])::before {
882
+ background: var(--zen-fill);
883
+ mask-image: radial-gradient(110% 260px at 50% var(--zen-glow-y), rgb(0 0 0 / 0.9), transparent);
884
+ }
885
+
886
+ .sessions-list.zen.zen-centered::before {
887
+ --zen-glow-y: 100%;
888
+ }
889
+
890
+ .sessions-list.zen.zen-centered::after {
891
+ flex-grow: 2;
892
+ }
893
+
894
+ .sessions-list.zen > * {
895
+ flex-shrink: 0;
896
+ }
897
+
898
+ /* After the ::after spacer, so the agents sit at the bottom of the free space. */
899
+ .zen-agents {
900
+ order: 1;
901
+ display: flex;
902
+ flex-direction: column;
903
+ gap: 2px;
904
+ margin-top: 6px;
905
+ }
906
+ .zen-agents:empty {
907
+ display: none;
908
+ }
909
+ .zen-agent {
910
+ display: grid;
911
+ grid-template-columns: 5px 1fr auto;
912
+ align-items: center;
913
+ gap: 8px;
914
+ padding: 5px 8px;
915
+ background: none;
916
+ border: none;
917
+ border-radius: 6px;
918
+ font: inherit;
919
+ font-size: 12px;
920
+ text-align: left;
921
+ cursor: pointer;
922
+ }
923
+ .zen-agent:hover {
924
+ background: var(--bg-hover);
925
+ }
926
+ .zen-agent-text {
927
+ min-width: 0;
928
+ }
929
+ .zen-agent-task {
930
+ display: block;
931
+ overflow: hidden;
932
+ text-overflow: ellipsis;
933
+ white-space: nowrap;
934
+ color: var(--text-primary);
935
+ }
936
+ .zen-agent-time {
937
+ font-size: 10.5px;
938
+ font-variant-numeric: tabular-nums;
939
+ color: var(--text-tertiary);
940
+ }
941
+
837
942
  .session-item {
838
943
  position: relative;
839
944
  display: block;
@@ -7030,13 +7135,15 @@ body.show-resizing iframe {
7030
7135
  }
7031
7136
 
7032
7137
  /* The × is a glyph, not an svg, so .row-actions button svg does not size it. */
7033
- .linked-doc-remove {
7138
+ .linked-doc-remove,
7139
+ .artifact-remove {
7034
7140
  font-family: var(--mono);
7035
7141
  font-size: 15px;
7036
7142
  line-height: 1;
7037
7143
  }
7038
7144
 
7039
- .linked-doc-actions .linked-doc-remove:hover {
7145
+ .linked-doc-actions .linked-doc-remove:hover,
7146
+ .artifact-actions .artifact-remove:hover {
7040
7147
  background: var(--bg-elevated);
7041
7148
  border-color: var(--border);
7042
7149
  color: var(--text-primary);
@@ -8134,6 +8241,13 @@ body.light .msg-assistant .msg-text {
8134
8241
  padding: 8px 16px;
8135
8242
  white-space: nowrap;
8136
8243
  }
8244
+
8245
+ /* The modal focuses a button itself, and after a mouse click Chrome does not match
8246
+ :focus-visible for that, so Enter would act on a button with no ring. */
8247
+ .confirm-buttons .btn:focus {
8248
+ outline: 2px solid var(--accent);
8249
+ outline-offset: 2px;
8250
+ }
8137
8251
  /* #endregion */
8138
8252
 
8139
8253
  /* Reference card, not reading material: it stays out of the modal zoom, uses the app's
@@ -48,6 +48,39 @@
48
48
  return lines.join('\n');
49
49
  }
50
50
 
51
+ const PATH_ROOT = String.raw`(?:[A-Za-z]:[\\/]|\/(?=[\w.@+-]+\/)|\\\\(?=[\w.$-]+\\))`;
52
+ // A quoted path may hold spaces; a bare one ends at the first.
53
+ const PATH_RE = new RegExp(
54
+ [
55
+ String.raw`"(${PATH_ROOT}[^"<>|*?]*[^\s"<>|*?])"`,
56
+ String.raw`'(${PATH_ROOT}[^'<>|*?]*[^\s'<>|*?])'`,
57
+ String.raw`(?<![\w/\\:.~-])${PATH_ROOT}[^\s"'<>|*?${'`'}]*[^\s"'<>|*?${'`'}.,;:!)\]}]`,
58
+ ].join('|'),
59
+ 'g',
60
+ );
61
+
62
+ function pathsIn(text) {
63
+ return Array.from(text.matchAll(PATH_RE), (m) => {
64
+ const quoted = m[1] ?? m[2];
65
+ const found = quoted ?? m[0];
66
+ return { index: m.index + (quoted ? 1 : 0), text: found, path: found.replace(/(?::\d+){1,2}$/, '') };
67
+ });
68
+ }
69
+
70
+ // Cell x of each character, because a wide character spans two cells and translateToString counts it once.
71
+ function lineCells(line, cols, reuse) {
72
+ let text = '';
73
+ const xs = [];
74
+ for (let x = 0; x < cols; x++) {
75
+ const cell = line.getCell(x, reuse);
76
+ if (!cell || cell.getWidth() === 0) continue;
77
+ const chars = cell.getChars() || ' ';
78
+ for (let i = 0; i < chars.length; i++) xs.push(x);
79
+ text += chars;
80
+ }
81
+ return { text, xs };
82
+ }
83
+
51
84
  function oscColor(hex) {
52
85
  const m = /^#([0-9a-f]{6})$/i.exec(hex || '');
53
86
  if (!m) return null;
@@ -147,6 +180,8 @@
147
180
  scrollback: o.scrollback,
148
181
  cursorBlink: true,
149
182
  allowProposedApi: true,
183
+ // xterm's default asks with confirm() and opens a window, which the installed hub window blocks.
184
+ linkHandler: { activate: (_e, uri) => up('link', { href: uri }), allowNonHttpProtocols: true },
150
185
  ...o.themeOptions,
151
186
  });
152
187
  state.term = term;
@@ -154,6 +189,22 @@
154
189
  term.loadAddon(state.fit);
155
190
  term.loadAddon(new win.Unicode11Addon.Unicode11Addon());
156
191
  term.unicode.activeVersion = '11';
192
+ // Link providers scan a line only when the pointer moves onto it, never on output.
193
+ term.loadAddon(new win.WebLinksAddon.WebLinksAddon((_e, uri) => up('link', { href: uri })));
194
+ const cellReuse = term.buffer.active.getNullCell();
195
+ term.registerLinkProvider({
196
+ provideLinks(y, done) {
197
+ const line = term.buffer.active.getLine(y - 1);
198
+ if (!line || !/[\\/]/.test(line.translateToString(true))) return done(undefined);
199
+ const { text, xs } = lineCells(line, term.cols, cellReuse);
200
+ const links = pathsIn(text).map((p) => ({
201
+ text: p.text,
202
+ range: { start: { x: xs[p.index] + 1, y }, end: { x: xs[p.index + p.text.length - 1] + 1, y } },
203
+ activate: () => up('link', { href: p.path }),
204
+ }));
205
+ done(links.length ? links : undefined);
206
+ },
207
+ });
157
208
  term.open(host);
158
209
  try {
159
210
  const gl = new win.WebglAddon.WebglAddon();
@@ -346,7 +397,7 @@
346
397
  }
347
398
 
348
399
  if (typeof module === 'object' && module.exports) {
349
- module.exports = { createTerminalFrame, claimSig, isClaimed, handsBack, parentOriginOf, tailOf };
400
+ module.exports = { createTerminalFrame, claimSig, isClaimed, handsBack, parentOriginOf, tailOf, pathsIn };
350
401
  } else {
351
402
  createTerminalFrame(root);
352
403
  }
@@ -22,6 +22,7 @@
22
22
  <script src="/vendor/xterm/addon-fit.js"></script>
23
23
  <script src="/vendor/xterm/addon-webgl.js"></script>
24
24
  <script src="/vendor/xterm/addon-unicode11.js"></script>
25
+ <script src="/vendor/xterm/addon-web-links.js"></script>
25
26
  <script src="/terminal-frame.js"></script>
26
27
  </body>
27
28
  </html>
@@ -1,4 +1,4 @@
1
- // claude-hub-sdk 1.5.0 (sha256 1505d3d5f8ba). Copied by npm run sdk:sync in claude-code-hub. Do not edit.
1
+ // claude-hub-sdk 1.7.0 (sha256 904e53d87ec4). Copied by npm run sdk:sync in claude-code-hub. Do not edit.
2
2
  // Claude Code Hub SDK: the one rule that names a key press, shared by the hub page, client.js and
3
3
  // stub.js. The hub serves it to its page as /sdk/keys.js; mount() and sdk:sync put it in front of
4
4
  // client.js and stub.js, so an app still loads one file.
@@ -60,6 +60,8 @@ if (typeof module === 'object' && module.exports) module.exports = ClaudeHubKeys
60
60
  status: 'standalone',
61
61
  themes: [],
62
62
  inHub: false,
63
+ settings: null,
64
+ onSettings: () => off,
63
65
  onStatus: () => off,
64
66
  onActive: () => off,
65
67
  onThemes: () => off,
package/server.js CHANGED
@@ -3468,6 +3468,7 @@ const XTERM_FILES = {
3468
3468
  'addon-fit.js': '@xterm/addon-fit/lib/addon-fit.js',
3469
3469
  'addon-webgl.js': '@xterm/addon-webgl/lib/addon-webgl.js',
3470
3470
  'addon-unicode11.js': '@xterm/addon-unicode11/lib/addon-unicode11.js',
3471
+ 'addon-web-links.js': '@xterm/addon-web-links/lib/addon-web-links.js',
3471
3472
  };
3472
3473
  app.get('/vendor/xterm/:file', (req, res) => {
3473
3474
  const rel = Object.hasOwn(XTERM_FILES, req.params.file) ? XTERM_FILES[req.params.file] : null;