shadok-ai 0.15.34 → 0.15.36
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/README.md +9 -6
- package/package.json +1 -1
- package/public/index.html +148 -93
package/README.md
CHANGED
|
@@ -76,9 +76,11 @@ Enter to skip; you can add it later from the web UI).
|
|
|
76
76
|
- **Channels** — one `claude` process each, running in parallel. Renaming,
|
|
77
77
|
grouping, and closing all sync live between the browser and Telegram: it's
|
|
78
78
|
one list, server-owned, not two copies.
|
|
79
|
-
- **Groups column** — on a desktop the groups get a
|
|
80
|
-
of the agents
|
|
81
|
-
|
|
79
|
+
- **Groups column** — once you create a group, on a desktop the groups get a
|
|
80
|
+
column of their own, left of the agents (with no group there is no column, just
|
|
81
|
+
the single agents list): pick a group and the agents column shows only its
|
|
82
|
+
agents (*Ungrouped* holds the rest, the home agent included unless you file
|
|
83
|
+
it). Each
|
|
82
84
|
group has an icon — drawn at random, double-click it to pick another: choose
|
|
83
85
|
from the grid, type any emoji, or **type a word** (`rocket`, `bug`, `money`…)
|
|
84
86
|
to search for a matching one — and a **+** that creates the new agent straight
|
|
@@ -86,9 +88,10 @@ Enter to skip; you can add it later from the web UI).
|
|
|
86
88
|
A single click on a group (its icon or its row) selects it; this matters once
|
|
87
89
|
**«** folds the column to its icons, where the icon is the whole row.
|
|
88
90
|
Dragging an agent onto a group still files it there, and dragging groups
|
|
89
|
-
reorders them. Double-click a name to rename it. A
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
reorders them. Double-click a name to rename it. A group you are not looking at
|
|
92
|
+
carries the same signal its agents do — the row lights up (and breathes) when
|
|
93
|
+
one is working, waiting for you, or has an unread answer, the way a channel
|
|
94
|
+
does. On a phone nothing changes: the agents sheet still lists every group.
|
|
92
95
|
- **Worktree isolation** — spawned agents get their own git worktree and branch
|
|
93
96
|
by default, so parallel agents never collide. Work is never auto-discarded:
|
|
94
97
|
an empty worktree is reclaimed on close, anything with changes or commits
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
<link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2314161d%22%2F%3E%3Cline%20x1%3D%2210.8%22%20y1%3D%2223%22%20x2%3D%2210.4%22%20y2%3D%2230.6%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2216.400000000000002%22%20y1%3D%2223%22%20x2%3D%2216.8%22%20y2%3D%2230.6%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%228.8%22%20y1%3D%2230.7%22%20x2%3D%2211.9%22%20y2%3D%2230.7%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2215.4%22%20y1%3D%2230.7%22%20x2%3D%2218.5%22%20y2%3D%2230.7%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2219.6%22%20y1%3D%2217.2%22%20x2%3D%2224.000000000000004%22%20y2%3D%2220.4%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%221.3%22%20stroke-linecap%3D%22round%22%2F%3E%3Cg%3E%3Cline%20x1%3D%2211.600000000000001%22%20y1%3D%225.399999999999999%22%20x2%3D%229.600000000000001%22%20y2%3D%222.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%220.9%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%229.600000000000001%22%20cy%3D%222.8%22%20r%3D%221%22%20fill%3D%22%23f0a848%22%2F%3E%3Cline%20x1%3D%2216%22%20y1%3D%225.399999999999999%22%20x2%3D%2218%22%20y2%3D%222.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%220.9%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%222.8%22%20r%3D%221%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2213.8%22%20cy%3D%2216.2%22%20r%3D%228.4%22%20fill%3D%22%23f0a848%22%2F%3E%3Cpath%20d%3D%22M12.200000000000001%2018.2%20L15.4%2018.2%20L13.8%2021.6%20Z%22%20fill%3D%22%23f0a848%22%2F%3E%3Ccircle%20cx%3D%2210.9%22%20cy%3D%227.799999999999999%22%20r%3D%222.6%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2216.7%22%20cy%3D%227.799999999999999%22%20r%3D%222.6%22%20fill%3D%22%23fff%22%2F%3E%3Ccircle%20cx%3D%2211.200000000000001%22%20cy%3D%228.1%22%20r%3D%221.3%22%20fill%3D%22%2314161d%22%2F%3E%3Ccircle%20cx%3D%2217%22%20cy%3D%228.1%22%20r%3D%221.3%22%20fill%3D%22%2314161d%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
|
15
15
|
<style>
|
|
16
16
|
:root {
|
|
17
|
+
/* The page the panels sit on: a shade darker than --bg, so the nav and the
|
|
18
|
+
conversation read as cards (the "sober" pass, 2026-10-28). */
|
|
19
|
+
--page: #0d0f14;
|
|
20
|
+
--radius: 12px;
|
|
17
21
|
--bg: #14161d;
|
|
18
22
|
--bg-raised: #1b1e27;
|
|
19
23
|
--bg-inset: #0e1015;
|
|
@@ -32,7 +36,9 @@
|
|
|
32
36
|
--menu-bg: #14140f;
|
|
33
37
|
--menu-hover: #26261c;
|
|
34
38
|
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
35
|
-
|
|
39
|
+
/* One sans for the whole interface; the mono is kept for what IS code —
|
|
40
|
+
code blocks, session ids, the terminal. */
|
|
41
|
+
--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
36
42
|
}
|
|
37
43
|
/* Light mode. An ORTHOGONAL axis to the accent palette: only the base surfaces
|
|
38
44
|
and text flip; the instance's accent (--amber/--phosphor) stays its identity.
|
|
@@ -42,7 +48,8 @@
|
|
|
42
48
|
a pale amber on white. Accent-tinted surfaces (--amber-soft) derive from
|
|
43
49
|
--amber and follow on their own. */
|
|
44
50
|
:root[data-mode="light"] {
|
|
45
|
-
--
|
|
51
|
+
--page: #eef0f4;
|
|
52
|
+
--bg: #f7f8fa;
|
|
46
53
|
--bg-raised: #ffffff;
|
|
47
54
|
--bg-inset: #e5e8ee;
|
|
48
55
|
--line: #d2d7e0;
|
|
@@ -69,10 +76,11 @@
|
|
|
69
76
|
html, body { height: 100%; }
|
|
70
77
|
html { overflow: hidden; }
|
|
71
78
|
body {
|
|
72
|
-
background: var(--
|
|
79
|
+
background: var(--page);
|
|
73
80
|
color: var(--text);
|
|
74
81
|
font-family: var(--sans);
|
|
75
|
-
font-size:
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
-webkit-font-smoothing: antialiased;
|
|
76
84
|
display: flex;
|
|
77
85
|
flex-direction: column;
|
|
78
86
|
/* Fixed chassis: the page never scrolls — only the inner panels (channel
|
|
@@ -101,17 +109,16 @@
|
|
|
101
109
|
reports. Set from `visualViewport` (see `syncViewport`), touch only. */
|
|
102
110
|
body { height: 100dvh; }
|
|
103
111
|
body.vv-sized { height: var(--app-h); }
|
|
112
|
+
/* Labels read as quiet sentence-case captions, not stamped capitals. */
|
|
104
113
|
.label {
|
|
105
|
-
font-
|
|
106
|
-
font-
|
|
107
|
-
letter-spacing: 0.18em;
|
|
108
|
-
text-transform: uppercase;
|
|
114
|
+
font-size: 12px;
|
|
115
|
+
font-weight: 500;
|
|
109
116
|
color: var(--text-dim);
|
|
110
117
|
}
|
|
111
118
|
/* Message time, hooked onto the speaker's label. Paler and less spaced out
|
|
112
119
|
than the label: there for whoever looks for it, quiet otherwise. */
|
|
113
120
|
.label .time {
|
|
114
|
-
|
|
121
|
+
font-weight: 400;
|
|
115
122
|
opacity: 0.6;
|
|
116
123
|
}
|
|
117
124
|
.label .time::before {
|
|
@@ -126,8 +133,7 @@
|
|
|
126
133
|
align-items: center;
|
|
127
134
|
gap: 12px;
|
|
128
135
|
padding: 10px 16px;
|
|
129
|
-
|
|
130
|
-
background: var(--bg-raised);
|
|
136
|
+
background: var(--page);
|
|
131
137
|
flex-wrap: wrap;
|
|
132
138
|
}
|
|
133
139
|
/* Keep the toolbar buttons together so they never split across lines. */
|
|
@@ -156,11 +162,11 @@
|
|
|
156
162
|
#starBtn:hover { border-color: var(--amber); color: var(--amber); }
|
|
157
163
|
#starBtn:hover .star-count { color: var(--amber); }
|
|
158
164
|
header .brand {
|
|
159
|
-
font-
|
|
160
|
-
font-
|
|
161
|
-
color: var(--
|
|
162
|
-
letter-spacing: 0.06em;
|
|
165
|
+
font-size: 15px;
|
|
166
|
+
font-weight: 700;
|
|
167
|
+
color: var(--text);
|
|
163
168
|
}
|
|
169
|
+
header .brand::before { color: var(--amber); }
|
|
164
170
|
header .brand::before { content: "◆ "; }
|
|
165
171
|
/* The brand name doubles as the cockpit's editable title: click to rename,
|
|
166
172
|
Enter/blur commits, Escape cancels. A dashed outline + pencil on hover make
|
|
@@ -225,7 +231,7 @@
|
|
|
225
231
|
#verMenu {
|
|
226
232
|
position: fixed; top: 42px; left: 12px; z-index: 1001;
|
|
227
233
|
background: var(--menu-bg); border: 1px solid var(--line); border-radius: 6px;
|
|
228
|
-
padding: 10px 12px; font-family: var(--
|
|
234
|
+
padding: 10px 12px; font-family: var(--sans); font-size: 12px;
|
|
229
235
|
min-width: 210px; box-shadow: 0 6px 20px rgba(0,0,0,0.5);
|
|
230
236
|
}
|
|
231
237
|
#verMenu .vm-row { display: flex; justify-content: space-between; gap: 16px; margin-bottom: 4px; }
|
|
@@ -241,7 +247,7 @@
|
|
|
241
247
|
#tabMenu {
|
|
242
248
|
position: fixed; z-index: 1002;
|
|
243
249
|
background: var(--menu-bg); border: 1px solid var(--line); border-radius: 6px;
|
|
244
|
-
padding: 4px; font-family: var(--
|
|
250
|
+
padding: 4px; font-family: var(--sans); font-size: 12px;
|
|
245
251
|
min-width: 170px; box-shadow: 0 6px 20px rgba(0,0,0,0.5);
|
|
246
252
|
}
|
|
247
253
|
#tabMenu .tm-item {
|
|
@@ -298,7 +304,7 @@
|
|
|
298
304
|
#updateBanner {
|
|
299
305
|
position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
|
|
300
306
|
background: var(--amber); color: #1a1200;
|
|
301
|
-
font-family: var(--
|
|
307
|
+
font-family: var(--sans); font-size: 12px; text-align: center;
|
|
302
308
|
padding: 6px 12px;
|
|
303
309
|
}
|
|
304
310
|
/* The re-login reminder (sign-in within 48h / expired). Same top strip as the
|
|
@@ -306,7 +312,7 @@
|
|
|
306
312
|
#expiryBanner {
|
|
307
313
|
position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
|
|
308
314
|
background: var(--amber); color: #1a1200;
|
|
309
|
-
font-family: var(--
|
|
315
|
+
font-family: var(--sans); font-size: 12px;
|
|
310
316
|
padding: 6px 12px;
|
|
311
317
|
display: flex; gap: 10px; align-items: center; justify-content: center; flex-wrap: wrap;
|
|
312
318
|
}
|
|
@@ -317,7 +323,7 @@
|
|
|
317
323
|
}
|
|
318
324
|
.gauge { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
319
325
|
.gauge .value {
|
|
320
|
-
font-family: var(--
|
|
326
|
+
font-family: var(--sans);
|
|
321
327
|
font-size: 12px;
|
|
322
328
|
color: var(--text);
|
|
323
329
|
white-space: nowrap;
|
|
@@ -385,23 +391,25 @@
|
|
|
385
391
|
.dial .needle, .dial .pct { transition: none; }
|
|
386
392
|
}
|
|
387
393
|
button {
|
|
388
|
-
font-family: var(--
|
|
389
|
-
font-size:
|
|
390
|
-
|
|
394
|
+
font-family: var(--sans);
|
|
395
|
+
font-size: 13px;
|
|
396
|
+
font-weight: 500;
|
|
391
397
|
background: transparent;
|
|
392
398
|
color: var(--text);
|
|
393
399
|
border: 1px solid var(--line);
|
|
394
|
-
border-radius:
|
|
400
|
+
border-radius: 8px;
|
|
395
401
|
padding: 6px 12px;
|
|
396
402
|
cursor: pointer;
|
|
403
|
+
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
397
404
|
}
|
|
398
|
-
/*
|
|
405
|
+
/* Icon buttons carry no frame: a soft fill on hover is the whole affordance. */
|
|
399
406
|
header button.icon {
|
|
400
|
-
padding:
|
|
407
|
+
padding: 7px 9px;
|
|
401
408
|
font-size: 14px;
|
|
402
409
|
line-height: 1;
|
|
403
|
-
|
|
410
|
+
border-color: transparent;
|
|
404
411
|
}
|
|
412
|
+
header button.icon:hover { background: var(--bg-raised); border-color: transparent; color: var(--text); }
|
|
405
413
|
header button.icon.stop:hover { border-color: var(--err); color: var(--err); }
|
|
406
414
|
/* The bell's diagonal slash shows only when notifications are muted. */
|
|
407
415
|
#muteNotif:not(.muted) .mute-slash { display: none; }
|
|
@@ -431,7 +439,7 @@
|
|
|
431
439
|
.mm-tools:empty { display: none; }
|
|
432
440
|
#moreMenu .mm-tools button { width: auto; } /* override #moreMenu button width:100% */
|
|
433
441
|
#moreMenu .mm-sep { height: 1px; background: var(--line); margin: 4px 2px; }
|
|
434
|
-
#moreMenu .mm-label { padding: 2px 4px; font-size:
|
|
442
|
+
#moreMenu .mm-label { padding: 2px 4px; font-size: 12px; font-weight: 500; color: var(--text-dim); }
|
|
435
443
|
.mm-themes { display: flex; gap: 6px; padding: 2px 4px 4px; }
|
|
436
444
|
/* Swatch buttons must override `#moreMenu button { width:100% }` — hence the
|
|
437
445
|
id-scoped selector so specificity wins. */
|
|
@@ -450,9 +458,10 @@
|
|
|
450
458
|
}
|
|
451
459
|
#moreMenu .mm-mode button:hover { border-color: var(--text-dim); color: var(--text); }
|
|
452
460
|
#moreMenu .mm-mode button.active { border-color: var(--amber); color: var(--amber); background: color-mix(in srgb, var(--amber) 12%, transparent); }
|
|
453
|
-
button:hover { border-color: var(--
|
|
461
|
+
button:hover { background: var(--bg-inset); border-color: var(--line); color: var(--text); }
|
|
454
462
|
button:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
|
|
455
463
|
button.primary { background: var(--amber); border-color: var(--amber); color: #1a1205; font-weight: 600; }
|
|
464
|
+
button.primary:hover { background: var(--amber); border-color: var(--amber); color: #1a1205; }
|
|
456
465
|
button.primary:hover { filter: brightness(1.1); }
|
|
457
466
|
button:disabled { opacity: 0.4; cursor: default; }
|
|
458
467
|
|
|
@@ -464,22 +473,22 @@
|
|
|
464
473
|
flex-direction: column;
|
|
465
474
|
gap: 2px;
|
|
466
475
|
background: var(--bg-raised);
|
|
467
|
-
border
|
|
476
|
+
border: 1px solid var(--line);
|
|
477
|
+
border-radius: var(--radius);
|
|
468
478
|
padding: 12px 8px;
|
|
469
479
|
overflow-y: auto;
|
|
470
480
|
}
|
|
471
|
-
#tabbar .side-label { padding: 0 8px 8px; }
|
|
481
|
+
#tabbar .side-label { padding: 0 8px 8px; font-size: 13px; font-weight: 600; color: var(--text); }
|
|
472
482
|
.tab {
|
|
473
483
|
position: relative;
|
|
474
484
|
display: flex;
|
|
475
485
|
flex-direction: column; /* row 1 (led+name+icons) then row 2 (info) */
|
|
476
486
|
gap: 1px;
|
|
477
|
-
font-
|
|
478
|
-
font-size: 12px;
|
|
487
|
+
font-size: 14px;
|
|
479
488
|
color: var(--text-dim);
|
|
480
489
|
border-left: 2px solid transparent;
|
|
481
|
-
border-radius:
|
|
482
|
-
padding:
|
|
490
|
+
border-radius: 8px;
|
|
491
|
+
padding: 8px 10px 10px;
|
|
483
492
|
cursor: pointer;
|
|
484
493
|
user-select: none;
|
|
485
494
|
}
|
|
@@ -497,7 +506,7 @@
|
|
|
497
506
|
right: 10px;
|
|
498
507
|
bottom: 3px;
|
|
499
508
|
height: 2px;
|
|
500
|
-
background:
|
|
509
|
+
background: transparent;
|
|
501
510
|
border-radius: 1px;
|
|
502
511
|
overflow: hidden;
|
|
503
512
|
}
|
|
@@ -521,7 +530,7 @@
|
|
|
521
530
|
color: var(--text);
|
|
522
531
|
font-weight: 600;
|
|
523
532
|
border-left-color: var(--phosphor);
|
|
524
|
-
border-radius: 0
|
|
533
|
+
border-radius: 0 8px 8px 0;
|
|
525
534
|
box-shadow: inset 3px 0 0 var(--phosphor);
|
|
526
535
|
}
|
|
527
536
|
/* Full-width row 2: "xxx ago" on the left, context % hard right. */
|
|
@@ -541,8 +550,7 @@
|
|
|
541
550
|
.tab input.rename {
|
|
542
551
|
flex: 1;
|
|
543
552
|
min-width: 0;
|
|
544
|
-
font-
|
|
545
|
-
font-size: 12px;
|
|
553
|
+
font-size: 13px;
|
|
546
554
|
background: var(--bg-inset);
|
|
547
555
|
border: 1px solid var(--amber);
|
|
548
556
|
border-radius: 3px;
|
|
@@ -590,15 +598,20 @@
|
|
|
590
598
|
animation: breathe 1.8s ease-in-out infinite;
|
|
591
599
|
}
|
|
592
600
|
@media (prefers-reduced-motion: reduce) { .tab.working, .tab.has-bg { animation: none; } }
|
|
593
|
-
#newTab, #newGroup {
|
|
594
|
-
margin-top:
|
|
595
|
-
border:
|
|
596
|
-
font-size:
|
|
601
|
+
#newTab, #newGroup, #newGroupBare {
|
|
602
|
+
margin-top: 6px;
|
|
603
|
+
border-color: transparent;
|
|
604
|
+
font-size: 13px;
|
|
597
605
|
color: var(--text-dim);
|
|
598
|
-
padding: 8px;
|
|
606
|
+
padding: 8px 10px;
|
|
607
|
+
text-align: left;
|
|
599
608
|
}
|
|
600
|
-
#newTab:hover, #newGroup:hover { color: var(--amber);
|
|
609
|
+
#newTab:hover, #newGroup:hover, #newGroupBare:hover { color: var(--amber); background: var(--amber-soft); border-color: transparent; }
|
|
601
610
|
#newGroup { margin-top: 4px; }
|
|
611
|
+
/* The fallback "new group" entry: only on desktop, only while the rail (which
|
|
612
|
+
has its own) is hidden for want of a group. */
|
|
613
|
+
#newGroupBare { display: none; margin-top: 4px; }
|
|
614
|
+
@media (min-width: 641px) { body:not(.has-groups) #newGroupBare { display: block; } }
|
|
602
615
|
/* The column's only coloured element: tabs are borderless rows and the + are
|
|
603
616
|
ghost buttons, so a solid card reads as an action rather than as a session.
|
|
604
617
|
Pinned to the bottom (margin-top:auto + sticky) so it stays reachable while
|
|
@@ -614,7 +627,7 @@
|
|
|
614
627
|
padding-top: 8px;
|
|
615
628
|
background: var(--bg-raised);
|
|
616
629
|
/* Separates it from the list scrolling underneath, with no extra rule. */
|
|
617
|
-
box-shadow: 0 -8px 10px -
|
|
630
|
+
box-shadow: 0 -8px 10px -10px rgba(0, 0, 0, 0.35);
|
|
618
631
|
}
|
|
619
632
|
/* The tweak channel's tab keeps its slot's tint, so it is recognisable in the
|
|
620
633
|
same place as before it was started. */
|
|
@@ -629,18 +642,18 @@
|
|
|
629
642
|
gap: 2px;
|
|
630
643
|
text-align: left;
|
|
631
644
|
padding: 8px;
|
|
632
|
-
border: 1px solid var(--amber);
|
|
633
|
-
border-radius:
|
|
645
|
+
border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
|
|
646
|
+
border-radius: 10px;
|
|
634
647
|
width: 100%;
|
|
635
648
|
background: linear-gradient(var(--amber-soft), var(--amber-soft)), var(--bg-raised);
|
|
636
649
|
}
|
|
637
650
|
#tweakCta .tweak-title {
|
|
638
|
-
font-family: var(--
|
|
639
|
-
font-size:
|
|
651
|
+
font-family: var(--sans);
|
|
652
|
+
font-size: 13px;
|
|
640
653
|
font-weight: 600;
|
|
641
654
|
color: var(--amber);
|
|
642
655
|
}
|
|
643
|
-
#tweakCta .tweak-sub { font-size:
|
|
656
|
+
#tweakCta .tweak-sub { font-size: 11px; color: var(--text-dim); }
|
|
644
657
|
#tweakCta .tweak-arrow { display: inline-block; transition: transform 0.15s ease; }
|
|
645
658
|
/* No permanent animation: an element blinking here would compete with the
|
|
646
659
|
real signals (a waiting tab, the gauges) and would turn into noise. */
|
|
@@ -658,12 +671,11 @@
|
|
|
658
671
|
display: flex;
|
|
659
672
|
align-items: center;
|
|
660
673
|
gap: 6px;
|
|
661
|
-
font-family: var(--
|
|
662
|
-
font-size:
|
|
663
|
-
|
|
664
|
-
text-transform: uppercase;
|
|
674
|
+
font-family: var(--sans);
|
|
675
|
+
font-size: 12px;
|
|
676
|
+
font-weight: 600;
|
|
665
677
|
color: var(--text-dim);
|
|
666
|
-
border-radius:
|
|
678
|
+
border-radius: 6px;
|
|
667
679
|
padding: 5px 8px;
|
|
668
680
|
cursor: pointer;
|
|
669
681
|
user-select: none;
|
|
@@ -679,7 +691,7 @@
|
|
|
679
691
|
.group-head input.rename {
|
|
680
692
|
flex: 1;
|
|
681
693
|
min-width: 0;
|
|
682
|
-
font-family: var(--
|
|
694
|
+
font-family: var(--sans);
|
|
683
695
|
font-size: 11px;
|
|
684
696
|
text-transform: none;
|
|
685
697
|
letter-spacing: normal;
|
|
@@ -712,13 +724,22 @@
|
|
|
712
724
|
#groupRail { display: none; }
|
|
713
725
|
.icon-picker[hidden] { display: none; }
|
|
714
726
|
@media (min-width: 641px) {
|
|
715
|
-
|
|
727
|
+
/* The column only earns its space once a group exists: with none, the
|
|
728
|
+
cockpit is the single agents list it always was (body.has-groups is set
|
|
729
|
+
from groups.length in applyView). */
|
|
730
|
+
/* The rail and the agents column are ONE card: the rail is its tinted
|
|
731
|
+
left half, the agents column its right half. */
|
|
732
|
+
body.has-groups #groupRail {
|
|
716
733
|
width: 190px; flex: none;
|
|
717
734
|
display: flex; flex-direction: column; gap: 2px;
|
|
718
735
|
background: var(--bg-inset);
|
|
719
|
-
border
|
|
736
|
+
border: 1px solid var(--line); border-right: none;
|
|
737
|
+
border-radius: var(--radius) 0 0 var(--radius);
|
|
720
738
|
padding: 12px 8px; overflow-y: auto;
|
|
721
739
|
}
|
|
740
|
+
body.has-groups #tabbar { border-radius: 0 var(--radius) var(--radius) 0; }
|
|
741
|
+
/* Cancel <main>'s gap between the two halves of the card. */
|
|
742
|
+
body.has-groups #groupRail { margin-right: -12px; }
|
|
722
743
|
.rail-head { display: flex; align-items: center; justify-content: space-between; padding: 0 4px 8px 8px; }
|
|
723
744
|
#railMini { border: none; background: none; padding: 2px 6px; color: var(--text-dim); font-size: 13px; }
|
|
724
745
|
#railMini:hover { color: var(--amber); }
|
|
@@ -745,13 +766,22 @@
|
|
|
745
766
|
background: var(--bg-inset); border: 1px solid var(--amber); border-radius: 3px;
|
|
746
767
|
color: var(--text); padding: 2px 5px;
|
|
747
768
|
}
|
|
748
|
-
.rail-count { flex: none; font-family: var(--
|
|
769
|
+
.rail-count { flex: none; font-family: var(--sans); font-size: 10px; color: var(--text-dim); }
|
|
749
770
|
/* What a group that is not on screen asks of you — the single place it shows. */
|
|
750
771
|
.rail-dot { flex: none; width: 7px; height: 7px; border-radius: 50%; display: none; }
|
|
751
772
|
.rail-item.needs-answer .rail-dot { display: block; background: var(--amber); animation: breathe 1.2s ease-in-out infinite; }
|
|
752
773
|
.rail-item.unread .rail-dot { display: block; background: var(--phosphor); }
|
|
753
774
|
.rail-item.working .rail-dot { display: block; background: var(--text-dim); animation: breathe 1.8s ease-in-out infinite; }
|
|
754
775
|
@media (prefers-reduced-motion: reduce) { .rail-dot { animation: none !important; } }
|
|
776
|
+
/* A group carries the SAME effect its channels do — a working or finished
|
|
777
|
+
agent makes the whole group row read like that agent, not just a dot.
|
|
778
|
+
Mirrors .tab.working / .needs-answer / .unread. Shown for a group you are
|
|
779
|
+
NOT viewing (the active one shows its state on each agent in the column).
|
|
780
|
+
Placed after :hover so the state colour wins over the hover background. */
|
|
781
|
+
.rail-item.working { background: var(--amber-soft); animation: breathe 1.6s ease-in-out infinite; }
|
|
782
|
+
.rail-item.needs-answer { background: color-mix(in srgb, var(--amber) 28%, transparent); color: var(--text); }
|
|
783
|
+
.rail-item.unread { background: rgba(127, 201, 127, 0.14); border-left-color: var(--ok); color: var(--text); }
|
|
784
|
+
@media (prefers-reduced-motion: reduce) { .rail-item.working { animation: none !important; } }
|
|
755
785
|
.rail-add, .rail-del {
|
|
756
786
|
flex: none; display: none; border: none; background: none;
|
|
757
787
|
padding: 0 3px; font-size: 13px; line-height: 1; color: var(--text-dim);
|
|
@@ -761,8 +791,8 @@
|
|
|
761
791
|
.rail-add:hover { color: var(--amber); }
|
|
762
792
|
.rail-del:hover { color: var(--err); }
|
|
763
793
|
#groupRail #newGroup {
|
|
764
|
-
margin-top:
|
|
765
|
-
font-size:
|
|
794
|
+
margin-top: 6px; border-color: transparent; text-align: left;
|
|
795
|
+
font-size: 13px; color: var(--text-dim); padding: 8px 10px;
|
|
766
796
|
}
|
|
767
797
|
#groupRail #newGroup:hover { color: var(--amber); border-color: var(--amber); }
|
|
768
798
|
/* Collapsed to icons, like a dock: the names live in the tooltips. */
|
|
@@ -780,7 +810,11 @@
|
|
|
780
810
|
body.rail-mini .rail-dot { position: absolute; top: 4px; right: 4px; }
|
|
781
811
|
|
|
782
812
|
/* The agents column, narrowed to the selected group. */
|
|
783
|
-
|
|
813
|
+
/* With a column the list header is the selected group's name; with no
|
|
814
|
+
column it is just "Agents" again (the "Ungrouped" view label would read
|
|
815
|
+
oddly when there is nothing to be ungrouped from). */
|
|
816
|
+
body.has-groups #tabbar .side-label .sl-all { display: none; }
|
|
817
|
+
body:not(.has-groups) #tabbar .side-label .sl-view { display: none; }
|
|
784
818
|
#tabbar .group-head { display: none; }
|
|
785
819
|
#tabbar .group { margin-top: 0; }
|
|
786
820
|
#tabbar .group-body { padding-left: 0; }
|
|
@@ -807,11 +841,16 @@
|
|
|
807
841
|
.icon-picker input { width: 100%; font-size: 13px; box-sizing: border-box; }
|
|
808
842
|
|
|
809
843
|
/* ── Body ────────────────────────────────────────────── */
|
|
810
|
-
|
|
844
|
+
/* The panels are cards on the page: a gap around them, rounded corners.
|
|
845
|
+
A phone keeps edge-to-edge panels (see the mobile query). */
|
|
846
|
+
main { flex: 1; display: flex; min-height: 0; padding: 0 12px 12px; gap: 12px; }
|
|
811
847
|
/* min-height:0 so the transcript (flex:1) bounds the height instead of pushing
|
|
812
848
|
the chassis; overflow-y:auto keeps the setup screen reachable when it
|
|
813
849
|
overflows (with a session running the transcript fits exactly → inert). */
|
|
814
|
-
.chat-pane {
|
|
850
|
+
.chat-pane {
|
|
851
|
+
flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; position: relative;
|
|
852
|
+
background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
|
|
853
|
+
}
|
|
815
854
|
/* File drag-and-drop target: a dashed accent overlay while a file hovers.
|
|
816
855
|
pointer-events:none so the drop still lands on the pane underneath. */
|
|
817
856
|
.chat-pane.dropping::after {
|
|
@@ -826,7 +865,7 @@
|
|
|
826
865
|
|
|
827
866
|
.field { display: flex; flex-direction: column; gap: 6px; }
|
|
828
867
|
input[type="text"] {
|
|
829
|
-
font-family: var(--
|
|
868
|
+
font-family: var(--sans);
|
|
830
869
|
font-size: 13px;
|
|
831
870
|
background: var(--bg-inset);
|
|
832
871
|
border: 1px solid var(--line);
|
|
@@ -889,7 +928,7 @@
|
|
|
889
928
|
.profile-card.selected { border-color: var(--amber); box-shadow: inset 0 0 0 1px var(--amber); }
|
|
890
929
|
.profile-card.selected .pc-name { color: var(--amber); }
|
|
891
930
|
.profile-card:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
|
|
892
|
-
.pc-name { font-
|
|
931
|
+
.pc-name { font-size: 14px; font-weight: 600; }
|
|
893
932
|
.pc-blurb {
|
|
894
933
|
color: var(--text-dim); font-size: 11.5px; line-height: 1.35;
|
|
895
934
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
@@ -1061,8 +1100,8 @@
|
|
|
1061
1100
|
.turn.user { align-self: flex-end; align-items: flex-end; }
|
|
1062
1101
|
.turn.claude { align-self: flex-start; }
|
|
1063
1102
|
.bubble {
|
|
1064
|
-
padding:
|
|
1065
|
-
border-radius:
|
|
1103
|
+
padding: 9px 14px;
|
|
1104
|
+
border-radius: 14px;
|
|
1066
1105
|
font-size: 14px;
|
|
1067
1106
|
line-height: 1.4;
|
|
1068
1107
|
white-space: pre-wrap;
|
|
@@ -1114,10 +1153,11 @@
|
|
|
1114
1153
|
color: #fff; border-radius: 50%; width: 16px; height: 16px; font-size: 9px; line-height: 1; cursor: pointer; padding: 0; }
|
|
1115
1154
|
.cmp-att .cmp-x:hover { background: var(--err); }
|
|
1116
1155
|
.turn.claude .bubble {
|
|
1117
|
-
background: var(--bg-
|
|
1156
|
+
background: var(--bg-raised);
|
|
1118
1157
|
border: 1px solid var(--line);
|
|
1119
|
-
font-family: var(--
|
|
1120
|
-
font-size:
|
|
1158
|
+
font-family: var(--sans);
|
|
1159
|
+
font-size: 14px;
|
|
1160
|
+
line-height: 1.55;
|
|
1121
1161
|
/* Wrap long prose lines so nothing is clipped; still preserves the
|
|
1122
1162
|
TUI's own newlines. (Wide tables come through history as real HTML.) */
|
|
1123
1163
|
white-space: pre-wrap;
|
|
@@ -1132,7 +1172,7 @@
|
|
|
1132
1172
|
.turn.claude.live-preview .bubble {
|
|
1133
1173
|
opacity: 0.55;
|
|
1134
1174
|
white-space: pre-wrap;
|
|
1135
|
-
font-family: var(--
|
|
1175
|
+
font-family: var(--sans);
|
|
1136
1176
|
}
|
|
1137
1177
|
.turn.claude.live-preview .bubble::after {
|
|
1138
1178
|
content: "▍";
|
|
@@ -1413,7 +1453,7 @@
|
|
|
1413
1453
|
.validate { align-self: flex-end; margin-top: 2px; }
|
|
1414
1454
|
.freetext-row { display: flex; gap: 8px; width: 100%; }
|
|
1415
1455
|
.freetext-row input { flex: 1; }
|
|
1416
|
-
#thinking { display: none; align-self: flex-start; color: var(--text-dim); font-family: var(--
|
|
1456
|
+
#thinking { display: none; align-self: flex-start; color: var(--text-dim); font-family: var(--sans); font-size: 12.5px; padding: 0 22px 6px; }
|
|
1417
1457
|
#thinking.visible { display: flex; align-items: center; gap: 6px; }
|
|
1418
1458
|
/* Shimmering text (an amber sweep) + a little Shadok pumping alongside. */
|
|
1419
1459
|
#thinking .tw-text {
|
|
@@ -1440,9 +1480,8 @@
|
|
|
1440
1480
|
gap: 10px;
|
|
1441
1481
|
/* The bottom padding absorbs the home indicator (viewport-fit=cover): without
|
|
1442
1482
|
it the field slides under it and half the text becomes unreadable. */
|
|
1443
|
-
padding:
|
|
1444
|
-
|
|
1445
|
-
background: var(--bg-raised);
|
|
1483
|
+
padding: 12px 16px calc(14px + env(safe-area-inset-bottom, 0px));
|
|
1484
|
+
background: var(--bg);
|
|
1446
1485
|
}
|
|
1447
1486
|
#composer textarea {
|
|
1448
1487
|
flex: 1;
|
|
@@ -1451,17 +1490,17 @@
|
|
|
1451
1490
|
min-width: 0;
|
|
1452
1491
|
font-family: var(--sans);
|
|
1453
1492
|
font-size: 14px;
|
|
1454
|
-
background: var(--bg-
|
|
1493
|
+
background: var(--bg-raised);
|
|
1455
1494
|
color: var(--text);
|
|
1456
1495
|
border: 1px solid var(--line);
|
|
1457
|
-
border-radius:
|
|
1458
|
-
padding:
|
|
1496
|
+
border-radius: var(--radius);
|
|
1497
|
+
padding: 11px 14px;
|
|
1459
1498
|
resize: none;
|
|
1460
1499
|
min-height: 42px;
|
|
1461
1500
|
max-height: 160px;
|
|
1462
1501
|
line-height: 1.45;
|
|
1463
1502
|
}
|
|
1464
|
-
#composer textarea:focus-visible { outline:
|
|
1503
|
+
#composer textarea:focus-visible { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }
|
|
1465
1504
|
|
|
1466
1505
|
/* ── Engine room (live terminal) ─────────────────────── */
|
|
1467
1506
|
#machine {
|
|
@@ -1503,13 +1542,13 @@
|
|
|
1503
1542
|
channel's actions only existed in terminal mode; it now sits above both
|
|
1504
1543
|
views, and `hidden` only removes it while the "New agent" form is up (no
|
|
1505
1544
|
channel to act on yet). */
|
|
1506
|
-
#chanBar { display: flex; align-items: center; gap:
|
|
1545
|
+
#chanBar { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); background: var(--bg-raised); font-size: 13px; }
|
|
1507
1546
|
#chanBar[hidden] { display: none; }
|
|
1508
1547
|
#chanBar .label { color: var(--text-dim); }
|
|
1509
|
-
#chanBar #chanBarName { color: var(--
|
|
1548
|
+
#chanBar #chanBarName { color: var(--text); font-size: 14px; font-weight: 600; }
|
|
1510
1549
|
/* Channel actions on the right of the bar (icons like the header). */
|
|
1511
1550
|
.chan-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; }
|
|
1512
|
-
#chanBar button.icon { padding:
|
|
1551
|
+
#chanBar button.icon { padding: 6px 8px; font-size: 14px; line-height: 1; border-color: transparent; }
|
|
1513
1552
|
#chanBar button.icon.stop:hover { border-color: var(--err); color: var(--err); }
|
|
1514
1553
|
#chanBar button.icon.on { border-color: var(--amber); color: var(--amber); background: color-mix(in srgb, var(--amber) 15%, transparent); }
|
|
1515
1554
|
/* Reload-to-apply: a green, pulsing text pill (not a bare icon) so an available
|
|
@@ -1529,7 +1568,7 @@
|
|
|
1529
1568
|
shown as a compact "● name ▾" pill on a phone. */
|
|
1530
1569
|
#chanPicker {
|
|
1531
1570
|
display: none; align-items: center; gap: 6px;
|
|
1532
|
-
font-family: var(--
|
|
1571
|
+
font-family: var(--sans); font-size: 13px; cursor: pointer;
|
|
1533
1572
|
background: var(--bg-inset); color: var(--text);
|
|
1534
1573
|
border: 1px solid var(--line); border-radius: 6px; padding: 5px 8px;
|
|
1535
1574
|
max-width: 55vw;
|
|
@@ -1539,6 +1578,12 @@
|
|
|
1539
1578
|
body.picker-open #chanPicker { border-color: var(--amber); }
|
|
1540
1579
|
body.picker-open #chanPicker .pk-caret { transform: rotate(180deg); }
|
|
1541
1580
|
/* ── Mobile: sidebar hidden, navigation via the channel header's selector ── */
|
|
1581
|
+
/* A phone has no room for cards: panels go edge to edge, as before. */
|
|
1582
|
+
@media (max-width: 640px) {
|
|
1583
|
+
main { padding: 0; gap: 0; }
|
|
1584
|
+
.chat-pane { border: none; border-radius: 0; }
|
|
1585
|
+
#tabbar { border-radius: 0; }
|
|
1586
|
+
}
|
|
1542
1587
|
@media (max-width: 640px) {
|
|
1543
1588
|
#tabbar { display: none; }
|
|
1544
1589
|
#chanPicker { display: inline-flex; min-width: 0; }
|
|
@@ -1629,8 +1674,7 @@
|
|
|
1629
1674
|
}
|
|
1630
1675
|
#emptyState.visible, #startingState.visible { display: flex; }
|
|
1631
1676
|
#emptyState .es-title, #startingState .es-title {
|
|
1632
|
-
font-family: var(--
|
|
1633
|
-
letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim);
|
|
1677
|
+
font-family: var(--sans); font-size: 15px; font-weight: 600; color: var(--text);
|
|
1634
1678
|
}
|
|
1635
1679
|
#emptyState .es-hint, #startingState .es-hint { color: var(--text-dim); font-size: 13px; line-height: 1.5; max-width: 44ch; }
|
|
1636
1680
|
.keys {
|
|
@@ -1763,7 +1807,7 @@
|
|
|
1763
1807
|
.preprompt { margin: 0 0 14px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-raised); }
|
|
1764
1808
|
.preprompt > summary {
|
|
1765
1809
|
cursor: pointer; padding: 8px 12px; color: var(--text-dim);
|
|
1766
|
-
font-size: 12px; font-family: var(--
|
|
1810
|
+
font-size: 12px; font-family: var(--sans); list-style: none;
|
|
1767
1811
|
}
|
|
1768
1812
|
.preprompt > summary::-webkit-details-marker { display: none; }
|
|
1769
1813
|
.preprompt > summary::before { content: "▸ "; }
|
|
@@ -1858,10 +1902,8 @@
|
|
|
1858
1902
|
/* Same treatment as the cockpit's labels (.label, channel bar): the popin
|
|
1859
1903
|
visibly belongs to the same family. */
|
|
1860
1904
|
.modal > .sec-head strong {
|
|
1861
|
-
font-family: var(--
|
|
1862
|
-
letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-dim);
|
|
1905
|
+
font-family: var(--sans); font-size: 16px; font-weight: 600; color: var(--text);
|
|
1863
1906
|
}
|
|
1864
|
-
.modal > .sec-head strong .obj { color: var(--amber); }
|
|
1865
1907
|
.modal-body { flex: 1 1 auto; overflow: auto; padding: 14px 16px; }
|
|
1866
1908
|
.modal-foot {
|
|
1867
1909
|
flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end;
|
|
@@ -2568,6 +2610,11 @@
|
|
|
2568
2610
|
<div id="groups"></div>
|
|
2569
2611
|
<p id="viewEmpty" class="check-hint" hidden>No agent in this group yet — create one, or drag one onto the group.</p>
|
|
2570
2612
|
<button id="newTab" title="New agent">+ new agent</button>
|
|
2613
|
+
<!-- The rail carries "+ new group" at its foot, but the rail is gone until a
|
|
2614
|
+
group exists — so this is the way to create the FIRST one. Desktop-only
|
|
2615
|
+
and only while there are no groups (CSS); it hands off to the rail's
|
|
2616
|
+
button once the column appears. -->
|
|
2617
|
+
<button id="newGroupBare" type="button" title="New agent group">+ new group</button>
|
|
2571
2618
|
<div id="tweakSlot">
|
|
2572
2619
|
<button id="tweakCta" type="button"
|
|
2573
2620
|
title="Change shadok-ai itself — the agent works on a branch and opens a pull request">
|
|
@@ -4188,6 +4235,8 @@
|
|
|
4188
4235
|
|
|
4189
4236
|
function applyView() {
|
|
4190
4237
|
const v = currentView();
|
|
4238
|
+
// No group → no column (invariant: the rail earns its space, see the CSS).
|
|
4239
|
+
document.body.classList.toggle("has-groups", groups.length > 0);
|
|
4191
4240
|
tabbar.dataset.view = v;
|
|
4192
4241
|
for (const g of groups) g.el.classList.toggle("in-view", String(g.id) === v);
|
|
4193
4242
|
const g = groups.find((x) => String(x.id) === v);
|
|
@@ -9786,12 +9835,18 @@
|
|
|
9786
9835
|
markTweakTab(t);
|
|
9787
9836
|
launchTab(t, { cwd: data.cwd, worktree: true, profile: "Shadok-Tweak" });
|
|
9788
9837
|
});
|
|
9789
|
-
|
|
9838
|
+
function addGroup() {
|
|
9790
9839
|
const g = createGroup();
|
|
9791
9840
|
persistGroups();
|
|
9841
|
+
// applyView first, so has-groups is set and the column is shown SYNCHRONOUSLY
|
|
9842
|
+
// — otherwise railShown() below is still false on the first group (created
|
|
9843
|
+
// from the bare button, while the rail was hidden) and it would not select.
|
|
9844
|
+
applyView();
|
|
9792
9845
|
// A new group is where you are going next: select it (it starts empty).
|
|
9793
9846
|
if (railShown()) setView(g.id, { follow: false });
|
|
9794
|
-
}
|
|
9847
|
+
}
|
|
9848
|
+
newGroupBtn.addEventListener("click", addGroup);
|
|
9849
|
+
$("newGroupBare").addEventListener("click", addGroup);
|
|
9795
9850
|
|
|
9796
9851
|
// ── Quota gauges (5h / 7d) ──────────────────────────────
|
|
9797
9852
|
function fmtReset(sec) {
|