wisp-agents 0.15.0 → 0.16.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +28 -12
  3. package/bin/wisp.js +3 -2
  4. package/package.json +3 -3
  5. package/server/adapters/antigravity.js +2 -2
  6. package/server/adapters/claude.js +31 -50
  7. package/server/adapters/codex.js +2 -2
  8. package/server/adapters/common.js +14 -34
  9. package/server/adapters/cursor.js +2 -2
  10. package/server/adapters/grok.js +2 -2
  11. package/server/adapters/index.js +16 -1
  12. package/server/adapters/opencode.js +47 -6
  13. package/server/checkpoint.js +12 -68
  14. package/server/client.js +5 -3
  15. package/server/git.js +9 -4
  16. package/server/handoff.js +70 -0
  17. package/server/http/respond.js +59 -0
  18. package/server/http/router.js +64 -0
  19. package/server/http/static.js +36 -0
  20. package/server/index.js +138 -1216
  21. package/server/machines/bootstrap.js +56 -0
  22. package/server/machines/hosts.js +127 -0
  23. package/server/machines.js +7 -179
  24. package/server/mcp-wisp.js +17 -5
  25. package/server/openapi.js +33 -2
  26. package/server/paths.js +32 -0
  27. package/server/preferences.js +2 -6
  28. package/server/proc.js +54 -0
  29. package/server/recipes.js +2 -4
  30. package/server/remote.js +14 -14
  31. package/server/router.js +7 -3
  32. package/server/routes/auto.js +29 -0
  33. package/server/routes/automations.js +92 -0
  34. package/server/routes/internal.js +56 -0
  35. package/server/routes/machines.js +43 -0
  36. package/server/routes/meta.js +76 -0
  37. package/server/routes/proxy.js +47 -0
  38. package/server/routes/sessions.js +339 -0
  39. package/server/routes/settings.js +144 -0
  40. package/server/routes/ship.js +61 -0
  41. package/server/routes/uploads.js +19 -0
  42. package/server/runner.js +87 -36
  43. package/server/services/auto.js +67 -0
  44. package/server/services/feedback.js +53 -0
  45. package/server/services/install.js +69 -0
  46. package/server/services/launch.js +127 -0
  47. package/server/services/phone.js +65 -0
  48. package/server/services/ship.js +82 -0
  49. package/server/services/telemetry.js +34 -0
  50. package/server/services/uploads.js +59 -0
  51. package/server/services/webhooks.js +28 -0
  52. package/server/store.js +5 -3
  53. package/server/subagents.js +5 -4
  54. package/server/telemetry.js +1 -1
  55. package/server/terminal.js +2 -1
  56. package/server/triggers.js +4 -6
  57. package/server/update.js +4 -3
  58. package/server/util.js +42 -0
  59. package/server/welcome.js +1 -1
  60. package/web/css/auto.css +29 -0
  61. package/web/css/automations.css +71 -0
  62. package/web/css/base.css +83 -0
  63. package/web/css/blocks.css +461 -0
  64. package/web/css/composer.css +62 -0
  65. package/web/css/feed.css +208 -0
  66. package/web/css/launcher.css +204 -0
  67. package/web/css/layout.css +61 -0
  68. package/web/css/machines.css +125 -0
  69. package/web/css/modals.css +176 -0
  70. package/web/css/palette.css +79 -0
  71. package/web/css/responsive.css +35 -0
  72. package/web/css/ship.css +95 -0
  73. package/web/css/subagents.css +130 -0
  74. package/web/css/tokens.css +115 -0
  75. package/web/index.html +16 -1
  76. package/web/js/app.js +28 -5237
  77. package/web/js/core/api.js +25 -0
  78. package/web/js/core/bus.js +28 -0
  79. package/web/js/core/dom.js +7 -0
  80. package/web/js/core/format.js +27 -0
  81. package/web/js/core/state.js +43 -0
  82. package/web/js/features/actions.js +339 -0
  83. package/web/js/features/attachments.js +115 -0
  84. package/web/js/features/auto.js +54 -0
  85. package/web/js/features/automations.js +305 -0
  86. package/web/js/features/commands.js +123 -0
  87. package/web/js/features/compare.js +104 -0
  88. package/web/js/features/composer.js +311 -0
  89. package/web/js/features/feed.js +400 -0
  90. package/web/js/features/feedback.js +127 -0
  91. package/web/js/features/keys.js +83 -0
  92. package/web/js/features/launcher.js +559 -0
  93. package/web/js/features/live.js +91 -0
  94. package/web/js/features/machines.js +313 -0
  95. package/web/js/features/memory.js +154 -0
  96. package/web/js/features/notify.js +36 -0
  97. package/web/js/features/phone.js +124 -0
  98. package/web/js/features/places.js +38 -0
  99. package/web/js/features/projects.js +284 -0
  100. package/web/js/features/runner-picker.js +233 -0
  101. package/web/js/features/runners.js +161 -0
  102. package/web/js/features/scroll.js +75 -0
  103. package/web/js/features/settings.js +390 -0
  104. package/web/js/features/setup.js +94 -0
  105. package/web/js/features/ship.js +122 -0
  106. package/web/js/features/team.js +386 -0
  107. package/web/js/features/turns.js +401 -0
  108. package/web/js/features/update.js +48 -0
  109. package/web/js/features/usage.js +75 -0
  110. package/web/js/ui/kbd.js +46 -0
  111. package/web/js/ui/overlay.js +38 -0
  112. package/web/js/ui/popover.js +52 -0
  113. package/web/js/ui/toast.js +23 -0
  114. package/web/css/app.css +0 -1910
@@ -0,0 +1,208 @@
1
+ /* ---------- feed ---------- */
2
+ .feed-head { display: flex; align-items: center; gap: 14px; min-height: 22px; margin: 26px 0 10px; padding: 0 4px; font-size: 13px; }
3
+ .feed-head[hidden] + .feed { margin-top: 26px; }
4
+ .hs { display: inline-flex; align-items: center; gap: 7px; color: var(--ink-2); }
5
+ .hs i { width: 7px; height: 7px; border-radius: 50%; }
6
+ .hs.working i { background: var(--accent); animation: pulse 1.6s infinite; }
7
+ .hs.needs i { background: var(--warn); }
8
+ .hs.calm i { background: var(--good); }
9
+
10
+ .f-group { margin-bottom: 22px; }
11
+ .f-group h4 {
12
+ display: flex; align-items: center; gap: 6px; margin: 0 0 6px; padding: 0 4px;
13
+ font-size: 12px; font-weight: 500; color: var(--muted);
14
+ }
15
+ .f-group h4 .n { color: var(--faint); }
16
+ .collapse-all {
17
+ display: inline-flex; align-items: center;
18
+ position: fixed; right: 22px; bottom: 22px; z-index: 30; height: 30px; padding: 0 12px; border-radius: 15px;
19
+ background: var(--surface); border: 1px solid var(--line-2); box-shadow: var(--shadow-2);
20
+ font-size: 12.5px; font-weight: 500; color: var(--ink-2);
21
+ opacity: 0; visibility: hidden; translate: 0 6px; transition: opacity 0.18s, translate 0.18s var(--ease), visibility 0.18s, color 0.15s;
22
+ }
23
+ .collapse-all.on { opacity: 1; visibility: visible; translate: 0 0; }
24
+ .collapse-all:hover { color: var(--ink); }
25
+ .collapse-all kbd { margin-left: 8px; }
26
+ .toggle-done { cursor: pointer; user-select: none; }
27
+ .toggle-done:hover { color: var(--ink-2); }
28
+ .toggle-done .chev { width: 12px; height: 12px; transition: transform 0.2s; }
29
+ .f-group.done:not(.collapsed) .chev { transform: rotate(90deg); }
30
+ /* Folded groups are just a row of headings: stack them tight instead of leaving a section gap each. */
31
+ .f-group.collapsed { margin-bottom: 2px; }
32
+ .f-group.collapsed h4 { margin: 0; }
33
+ .f-group h4.toggle-done { width: fit-content; min-height: 24px; margin-left: -4px; padding: 0 8px; border-radius: 6px; transition: background 0.15s, color 0.15s; }
34
+ .f-group h4.toggle-done:hover { background: var(--bg-2); }
35
+ .f-list { display: flex; flex-direction: column; gap: 6px; }
36
+ .feed-empty { padding: 48px 0; text-align: center; color: var(--faint); font-size: 13.5px; }
37
+
38
+ /* one feed item — a row that expands in place */
39
+ .item {
40
+ background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
41
+ transition: border-color 0.15s, box-shadow 0.15s;
42
+ }
43
+ .item:hover { border-color: var(--line-2); }
44
+ .item.focused { border-color: var(--line-2); box-shadow: 0 0 0 1px var(--line-2); }
45
+ .item.open { border-color: var(--line-2); box-shadow: var(--shadow-2); }
46
+ .item.st-done:not(.open) { background: transparent; }
47
+ /* a launch the server hasn't confirmed yet: shown at once, can't be opened */
48
+ .item.pending { opacity: 0.75; }
49
+ .item.pending .it-head { cursor: default; }
50
+ .item.pending .chev { visibility: hidden; }
51
+ .item.arrived { animation: arrive 1.4s var(--ease); }
52
+ @keyframes arrive { from { box-shadow: 0 0 0 3px var(--accent-soft); border-color: var(--accent); } }
53
+
54
+ .it-head {
55
+ display: grid; grid-template-columns: 10px 1fr auto; gap: 12px; align-items: center;
56
+ padding: 11px 12px 11px 14px; cursor: pointer; outline: 0; border-radius: var(--radius);
57
+ }
58
+ .item.open .it-head { border-bottom: 1px solid var(--line); border-radius: var(--radius) var(--radius) 0 0; }
59
+ .q-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pc); position: relative; }
60
+ .item.st-done .q-dot { opacity: 0.35; }
61
+ .q-dot.live::after, .q-dot.waiting::after {
62
+ content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 1.5px solid var(--pc); animation: ring 1.8s ease-out infinite;
63
+ }
64
+ .q-dot.waiting { background: var(--warn); --pc: var(--warn); }
65
+ .it-main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
66
+ .it-title { font-size: 14px; font-weight: 500; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; outline: 0; border-radius: 5px; }
67
+ .item.unread .it-title, .item.st-waiting .it-title, .item.open .it-title { color: var(--ink); font-weight: 600; }
68
+ .item.open .it-title { white-space: normal; }
69
+ .item.st-done .it-title { color: var(--muted); }
70
+ .it-title[contenteditable='true'] { white-space: normal; background: var(--surface-2); box-shadow: 0 0 0 3px var(--accent-soft); padding: 0 4px; cursor: text; }
71
+ .it-sub, .q-live { font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
72
+ .q-live.running { color: var(--ink-2); }
73
+ .q-live.waiting { color: var(--warn); font-weight: 500; }
74
+ .it-side { display: flex; align-items: center; gap: 10px; }
75
+ .it-side .chev { width: 14px; height: 14px; color: var(--faint); transition: transform 0.2s; }
76
+ .item.open .it-side .chev { transform: rotate(90deg); }
77
+ .q-proj {
78
+ font-size: 11px; font-weight: 500; padding: 0 7px; border-radius: 5px; line-height: 19px; white-space: nowrap; max-width: 120px; overflow: hidden; text-overflow: ellipsis;
79
+ color: color-mix(in srgb, var(--pc) 65%, var(--ink)); background: color-mix(in srgb, var(--pc) 10%, transparent);
80
+ }
81
+ .q-time { font-size: 11.5px; color: var(--faint); min-width: 26px; text-align: right; font-variant-numeric: tabular-nums; }
82
+ .q-queued { font-size: 11px; color: var(--accent-ink); font-weight: 600; }
83
+ /* one-click done on collapsed review rows; revealed on hover/focus */
84
+ .it-quick {
85
+ display: grid; place-items: center; width: 24px; height: 24px; margin: -4px -6px -4px 0; border-radius: 7px;
86
+ color: var(--faint); opacity: 0; transition: opacity 0.15s, background 0.15s, color 0.15s;
87
+ }
88
+ .it-quick .i { width: 14px; height: 14px; }
89
+ .item:hover .it-quick, .item.focused .it-quick, .it-quick:focus-visible { opacity: 1; }
90
+ .it-quick:hover { background: var(--bg-2); color: var(--good); }
91
+ @media (hover: none) { .it-quick { opacity: 1; } }
92
+
93
+ /* expanded body */
94
+ .it-body { animation: rise 0.3s var(--ease); }
95
+ .it-loading { display: flex; align-items: center; gap: 10px; padding: 18px; color: var(--muted); font-size: 13px; }
96
+ .it-bar { display: flex; align-items: center; gap: 12px; padding: 8px 10px 8px 14px; }
97
+ .it-meta { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; }
98
+ .it-actions { display: flex; gap: 2px; flex: none; }
99
+ .it-body .earlier { display: block; margin: 4px 0 0 18px; }
100
+ .it-dock { padding: 4px 14px 14px; }
101
+
102
+ /* ---------- pills ---------- */
103
+ .pill {
104
+ display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 8px; border-radius: 6px;
105
+ font-size: 12px; font-weight: 500; --tone: var(--good); color: color-mix(in srgb, var(--tone) 85%, var(--ink));
106
+ background: color-mix(in srgb, var(--tone) 9%, transparent);
107
+ }
108
+ .pill i { width: 6px; height: 6px; border-radius: 50%; background: var(--tone); }
109
+ .pill.st-partial, .pill.st-waiting { --tone: var(--warn); }
110
+ .pill.st-blocked { --tone: var(--bad); }
111
+ .pill.st-info { --tone: var(--accent); }
112
+
113
+ .turns { padding: 6px 18px 8px; }
114
+ .turn { padding-top: 12px; }
115
+ .turn + .turn { margin-top: 28px; border-top: 1px solid var(--line); padding-top: 28px; }
116
+ /* older exchanges fold to a single row; the latest stays open */
117
+ .turn.folded { padding-top: 0; }
118
+ .turn.folded + .turn.folded { margin-top: 2px; border-top: 0; padding-top: 0; }
119
+ .turn + .turn.folded { margin-top: 14px; padding-top: 10px; }
120
+ .turn.folded + .turn:not(.folded) { margin-top: 14px; }
121
+ .t-fold {
122
+ display: flex; align-items: center; gap: 10px; width: 100%; min-width: 0; padding: 7px 10px; border-radius: 8px;
123
+ text-align: left; font-size: 13px; color: var(--muted); transition: background 0.15s, color 0.15s;
124
+ }
125
+ .t-fold:hover { background: var(--bg-2); color: var(--ink); }
126
+ .t-fold .chev { flex: none; width: 12px; height: 12px; color: var(--faint); }
127
+ .t-prompt { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--ink); }
128
+ .t-result { flex: 0 1 auto; max-width: 40%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color: var(--faint); }
129
+ .t-fold time { flex: none; font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; }
130
+ .ask:has(.t-unfold) { padding-right: 40px; }
131
+ .t-unfold { position: absolute; top: 6px; right: 6px; }
132
+ .t-unfold .chev { width: 12px; height: 12px; transform: rotate(90deg); }
133
+
134
+ .ask { position: relative; margin: 0 0 20px; padding: 10px 14px; border-radius: var(--radius); background: var(--bg-2); }
135
+ .ask-text { white-space: pre-wrap; color: var(--ink); font-size: 14px; }
136
+ .ask-text.clamped { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
137
+ .ask-foot { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
138
+ .ask time { font-size: 11.5px; color: var(--faint); }
139
+ .model-logo { position: relative; display: inline-flex; width: 13px; height: 13px; color: var(--ink-2); cursor: default; outline: none; }
140
+ .model-logo > * { display: block; width: 100%; height: 100%; }
141
+ .model-logo .mono { background: currentColor; -webkit-mask: var(--logo) center / contain no-repeat; mask: var(--logo) center / contain no-repeat; }
142
+ .model-logo::after {
143
+ content: attr(data-tip); position: absolute; left: -6px; bottom: calc(100% + 6px); transform: translateY(2px);
144
+ padding: 3px 7px; border-radius: 6px; background: var(--ink); color: var(--bg); font-size: 11px; white-space: nowrap;
145
+ opacity: 0; pointer-events: none; transition: opacity 0.12s, transform 0.12s; z-index: 5;
146
+ }
147
+ .model-logo:hover::after, .model-logo:focus-visible::after { opacity: 1; transform: none; }
148
+ /* In the card header the logo sits at the right edge, so the tooltip opens down and leftwards. */
149
+ .it-side .model-logo::after { left: auto; right: -6px; bottom: auto; top: calc(100% + 6px); transform: translateY(-2px); }
150
+ .it-side .model-logo:hover::after, .it-side .model-logo:focus-visible::after { transform: none; }
151
+ .ask-atts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
152
+ .ask-atts img { height: 72px; border-radius: 10px; border: 1px solid var(--line); cursor: zoom-in; object-fit: cover; max-width: min(100%, 320px); }
153
+ .att-pill { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border-radius: 8px; background: var(--surface); border: 1px solid var(--line); font-size: 12.5px; }
154
+
155
+ /* ---------- working panel (not `.working` — the feed group and head badge use that) ---------- */
156
+ .w-panel {
157
+ display: flex; gap: 14px; padding: 16px 18px; border-radius: var(--radius); background: var(--surface); border: 1px solid var(--line);
158
+ animation: rise 0.4s var(--ease) both; position: relative; overflow: hidden;
159
+ }
160
+ .w-panel.waiting { border-color: color-mix(in srgb, var(--warn) 35%, var(--line)); }
161
+ .w-orb { padding-top: 2px; }
162
+ .w-body { flex: 1; min-width: 0; position: relative; }
163
+ .w-status { display: flex; align-items: baseline; gap: 12px; margin-bottom: 10px; }
164
+ .w-status b { font-weight: 600; }
165
+ .w-status span { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
166
+ .ticker, .log { list-style: none; margin: 0; padding: 0; }
167
+ .act { display: flex; align-items: center; gap: 9px; padding: 3px 0; font-size: 13px; color: var(--ink-2); min-width: 0; }
168
+ .act span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
169
+ .act .i { width: 14px; height: 14px; color: var(--faint); }
170
+ .ticker .act:nth-child(1) { color: var(--ink); }
171
+ .ticker .act:nth-child(1) .i { color: var(--accent); }
172
+ .ticker .act:nth-child(2) { opacity: 0.8; }
173
+ .ticker .act:nth-child(3) { opacity: 0.6; }
174
+ .ticker .act:nth-child(4) { opacity: 0.42; }
175
+ .ticker .act:nth-child(5) { opacity: 0.25; }
176
+ .act.new { animation: tick-in 0.45s var(--ease); }
177
+ .act.k-error { color: var(--bad); }
178
+ .act.k-ask, .act.k-wait { color: var(--warn); }
179
+ .act.k-text span { white-space: normal; color: var(--muted); }
180
+ .log { max-height: 340px; overflow-y: auto; margin-top: 12px; padding: 10px 12px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--line); }
181
+ .w-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
182
+ .w-queued { font-size: 12px; color: var(--accent-ink); font-weight: 500; }
183
+ @keyframes tick-in { from { opacity: 0; transform: translateY(-4px); } }
184
+
185
+ /* ask card (mid-run question) */
186
+ .ask-card, .b-question {
187
+ position: relative; padding: 16px 18px; border-radius: var(--radius); margin: 4px 0 14px;
188
+ background: var(--surface); border: 1px solid color-mix(in srgb, var(--warn) 60%, var(--line));
189
+ animation: rise 0.4s var(--ease) both;
190
+ }
191
+ .b-question { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); }
192
+ .b-question.answered { opacity: 0.65; background: var(--surface-2); border: 1px solid var(--line); }
193
+ .q-head { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 500; color: var(--warn); }
194
+ .b-question .q-head { color: var(--accent-ink); }
195
+ .q-text { margin: 6px 0 12px; font: 600 17px/1.4 var(--display); letter-spacing: -0.015em; color: var(--ink); }
196
+ .q-context { margin: -4px 0 12px; font-size: 13.5px; color: var(--ink-2); }
197
+ .q-opts { display: flex; flex-wrap: wrap; gap: 8px; }
198
+ .opt {
199
+ display: inline-flex; align-items: center; gap: 9px; min-height: 34px; padding: 6px 12px 6px 8px; border-radius: 8px;
200
+ border: 1px solid var(--line-2); background: var(--surface); font-weight: 500; text-align: left; transition: border-color 0.15s, background 0.15s;
201
+ }
202
+ .opt small { display: block; font-weight: 400; color: var(--muted); font-size: 12px; }
203
+ .opt .k { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 6px; background: var(--bg-2); font: 500 11px var(--mono); color: var(--muted); flex: none; }
204
+ .opt:hover:not(:disabled) { border-color: var(--accent); background: var(--accent-soft); }
205
+ .opt.own { color: var(--muted); border-style: dashed; padding-left: 12px; }
206
+ .q-own { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
207
+ .ask-card.sent { opacity: 0.5; pointer-events: none; }
208
+
@@ -0,0 +1,204 @@
1
+ /* ---------- launcher ---------- */
2
+ .launcher { container: launcher / inline-size; padding: 12px 12px 10px; border-radius: 12px; border-color: var(--line-2); box-shadow: var(--shadow-1); transition: box-shadow 0.15s, border-color 0.15s; }
3
+ /* A picker or menu opened from the launcher lives on <body> and takes focus; the launcher stays lit
4
+ while it's open so switching between the two harness pickers doesn't flash the ring off and on. */
5
+ .launcher:is(:focus-within, :has(.open)) { border-color: color-mix(in srgb, var(--accent) 55%, var(--line-2)); box-shadow: 0 0 0 3px var(--accent-soft); }
6
+ .launcher.drop, .composer.drop { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
7
+ .l-projects { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 0 2px 8px; }
8
+ /* no harness yet: one quiet line offering to install opencode */
9
+ .l-setup { display: flex; align-items: center; gap: 8px; margin: 0 0 4px; padding: 6px 6px 6px 10px; border-radius: 9px; background: var(--accent-soft); font-size: 13px; color: var(--ink-2); }
10
+ .l-setup[hidden] { display: none; }
11
+ .l-setup .model-logo { width: 15px; height: 15px; color: var(--ink); flex: none; }
12
+ .ls-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
13
+ .ls-text.bad { color: var(--bad); }
14
+ .l-setup .icon-btn { width: 28px; height: 28px; color: var(--muted); }
15
+ .l-setup .icon-btn.busy .i { animation: spin 0.8s linear infinite reverse; }
16
+ .l-setup .primary { flex: none; gap: 7px; }
17
+ .ls-spin { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid currentColor; border-right-color: transparent; animation: spin 0.7s linear infinite; }
18
+ .l-setup.nudge { animation: nudge 0.42s ease; }
19
+ @keyframes nudge { 20%, 60% { transform: translateX(-4px); } 40%, 80% { transform: translateX(4px); } }
20
+ .l-in { font-size: 12.5px; color: var(--faint); margin-right: 2px; }
21
+ .lp-chip {
22
+ display: inline-flex; align-items: center; gap: 6px; height: 27px; padding: 0 10px; border-radius: 99px;
23
+ font-size: 12.5px; color: var(--ink-2); border: 1px solid var(--line); transition: all 0.15s;
24
+ }
25
+ .lp-chip:hover { border-color: var(--line-2); background: var(--surface-2); }
26
+ .lp-chip.on { background: var(--bg-2); border-color: var(--line-2); color: var(--ink); font-weight: 500; }
27
+ .lp-chip.add { color: var(--muted); border-style: dashed; }
28
+ .lp-chip.add .i { width: 14px; height: 14px; }
29
+ /* recipes: one-click prompts under the launch bar */
30
+ .l-recipes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 8px -12px -10px; padding: 7px 14px 8px; border-top: 1px solid var(--line); }
31
+ .l-recipes .l-in { display: inline-flex; align-items: center; gap: 5px; }
32
+ .l-recipes .l-in .i { width: 13px; height: 13px; color: var(--accent); }
33
+ .l-recipes .lp-chip { height: 24px; padding: 0 9px; font-size: 12px; }
34
+ .l-recipes .lp-chip.rc { background: var(--surface-2); }
35
+ .l-recipes .lp-chip.rc:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--line-2)); color: var(--ink); }
36
+ .l-recipes .lp-chip[hidden] { display: none; }
37
+ .rc-f { font-style: normal; color: var(--faint); margin-left: -3px; }
38
+ .lr-empty { margin-right: 2px; }
39
+ .launcher textarea, .composer textarea {
40
+ display: block; width: 100%; resize: none; border: 0; outline: 0; background: transparent; padding: 8px 4px;
41
+ font-size: 15px; line-height: 1.55; color: var(--ink); min-height: 64px;
42
+ }
43
+ .launcher textarea::placeholder, .composer textarea::placeholder { color: var(--faint); }
44
+ /* docked: pinned under the top bar once scrolled past, one line until focused (or a menu of it is open) */
45
+ .launcher.docked {
46
+ position: fixed; top: 60px; left: 0; right: 0; margin-inline: auto; z-index: 15; width: min(752px, calc(100% - 48px));
47
+ max-height: calc(100vh - 76px); overflow-y: auto; box-shadow: var(--shadow-2); animation: dock-in 0.2s var(--ease);
48
+ }
49
+ .launcher.docked:is(:focus-within, :has(.open)) { box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-2); }
50
+ .launcher.docked:not(:focus-within):not(:has(.open)) { display: flex; align-items: center; gap: 6px; padding: 6px 8px; cursor: text; overflow: hidden; }
51
+ .launcher.docked:not(:focus-within):not(:has(.open)) :is(.l-in, .lp-chip:not(.on), .tray, .l-bar, .l-recipes) { display: none; }
52
+ .launcher.docked:not(:focus-within):not(:has(.open)) .l-projects { flex: none; flex-wrap: nowrap; padding: 0; }
53
+ .launcher.docked:not(:focus-within):not(:has(.open)) textarea {
54
+ flex: 1; min-width: 0; min-height: 0; height: 30px !important; padding: 4px; font-size: 14px; white-space: pre; overflow: hidden; cursor: text;
55
+ }
56
+ @keyframes dock-in { from { opacity: 0; transform: translateY(-8px); } }
57
+ body:has(.launcher.docked:is(:focus-within, :has(.open))) .to-top { display: none; }
58
+ .l-bar { display: flex; align-items: center; gap: 8px; padding-top: 6px; }
59
+ .l-bar > :not(.runners) { flex: none; }
60
+ .l-bar .grow { flex: 1; }
61
+ /* harness pill(s): "vs" rides on the end of it; each harness added joins the same pill behind a
62
+ "vs" of its own (hover: ×, removes it), and the end button turns into a + for the next one */
63
+ .runners {
64
+ display: inline-flex; align-items: stretch; min-width: 0; height: 30px; border-radius: 9px;
65
+ border: 1px solid var(--line); background: var(--surface); transition: border-color 0.15s;
66
+ }
67
+ .runners:hover, .runners:has(.open) { border-color: var(--line-2); }
68
+ .runners .runner-btn { height: 28px; min-width: 0; border: 0; border-radius: 8px; background: transparent; }
69
+ .runners .runner-btn:hover, .runners .runner-btn.open { background: var(--bg-2); }
70
+ .runners .runner-btn { overflow: hidden; }
71
+ .runners .rb-logo { flex: none; }
72
+ .runners .rb-h { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
73
+ .runners .rb-m { flex: 0 100 auto; min-width: 0; }
74
+ .runners.cmp .rb-e, .l-bar:has(.runners.cmp) #wtBtn span { display: none; }
75
+ /* three or four harnesses: logos only (each button's tooltip names it), the bar's other labels fold too */
76
+ .runners.many .runner-btn { flex: none; padding-inline: 6px; }
77
+ .runners.many :is(.rb-h, .rb-m) { display: none; }
78
+ .runners.many .rb-logo { margin-right: 3px; }
79
+ .runners.many .runner-btn .chev { margin-left: 0; }
80
+ .l-bar:has(.runners.many) :is(.mode-btn span, .team-btn .tb-label) { display: none; }
81
+ /* two harnesses on a narrow bar: drop the model names, then the harness names (logos stay) */
82
+ @container launcher (max-width: 640px) { .runners.cmp .rb-m { display: none; } }
83
+ @container launcher (max-width: 520px) {
84
+ .l-bar .mode-btn span, .l-bar #wtBtn span, .runners .rb-m { display: none; }
85
+ .runners.cmp .rb-h { display: none; }
86
+ .runners.cmp .runner-btn .chev { margin-left: 0; }
87
+ .runners.cmp .rb-logo { margin-right: 4px; }
88
+ }
89
+ @container launcher (max-width: 600px) {
90
+ .runners.many .runner-btn .chev { display: none; }
91
+ .runners.many .vs-del { width: 18px; }
92
+ }
93
+ .vs-btn {
94
+ position: relative; flex: none; display: grid; place-items: center; width: 30px;
95
+ font: 600 11px var(--mono); letter-spacing: 0.02em; color: var(--faint); transition: color 0.15s, background 0.15s;
96
+ }
97
+ .vs-btn[hidden] { display: none; }
98
+ .vs-btn:hover { color: var(--accent-ink); }
99
+ .vs-btn .i { display: none; width: 13px; height: 13px; }
100
+ .vs-btn:focus-visible { outline: 0; border-radius: 6px; box-shadow: inset 0 0 0 1px var(--accent); }
101
+ .vs-add::before { content: ''; position: absolute; left: 0; top: 7px; bottom: 7px; width: 1px; background: var(--line); }
102
+ .runners.cmp { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
103
+ .runners.cmp .vs-add { width: 26px; }
104
+ .runners.cmp .vs-add span { display: none; }
105
+ .runners.cmp .vs-add .i { display: block; width: 12px; height: 12px; }
106
+ .vs-del { width: 24px; color: var(--accent-ink); }
107
+ .runners.many .vs-del { width: 20px; }
108
+ .vs-del:hover { color: var(--bad); }
109
+ .vs-del:not(.fresh):is(:hover, :focus-visible) span { display: none; }
110
+ .vs-del:not(.fresh):is(:hover, :focus-visible) .i { display: block; }
111
+ .vs-del.fresh { cursor: default; }
112
+ /* permission mode: icon-only on the default, labelled otherwise */
113
+ .mode-btn {
114
+ display: inline-flex; align-items: center; gap: 6px; height: 32px; min-width: 32px; padding: 0 9px; border-radius: 9px;
115
+ font-size: 12.5px; font-weight: 500; color: var(--muted); transition: background 0.15s, color 0.15s;
116
+ }
117
+ .mode-btn:hover, .mode-btn.open { background: var(--bg-2); color: var(--ink); }
118
+ .mode-btn:focus-visible { outline: 0; box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 1px var(--accent); }
119
+ .mode-btn.m-plan { color: var(--accent-ink); }
120
+ .mode-btn.m-auto, .menu.mode .m-auto > .i:first-child { color: var(--warn); }
121
+ .menu.mode { width: 290px; }
122
+ .menu.mode button { align-items: flex-start; text-align: left; }
123
+ .menu.mode button > .i:first-child { margin-top: 2px; }
124
+ .mm-text { display: grid; gap: 1px; flex: 1; }
125
+ .mm-text b { font-weight: 500; color: var(--ink); }
126
+ .mm-text small { font-size: 12px; color: var(--muted); line-height: 1.35; }
127
+ .mm-check { margin-top: 2px; color: var(--accent); visibility: hidden; }
128
+ .menu.mode .on .mm-check { visibility: visible; }
129
+ .menu.mode button:focus-visible { outline: 0; background: var(--bg-2); }
130
+ .model-btn {
131
+ display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 8px 0 9px; border-radius: 8px;
132
+ font-size: 12.5px; color: var(--muted); transition: background 0.15s, color 0.15s;
133
+ }
134
+ .model-btn .i { width: 14px; height: 14px; }
135
+ .model-btn .chev { width: 12px; height: 12px; color: var(--faint); transform: rotate(90deg); transition: transform 0.2s var(--ease); }
136
+ .model-btn:hover, .model-btn.open { background: var(--bg-2); color: var(--ink); }
137
+ .model-btn.open .chev { transform: rotate(-90deg); }
138
+ .model-btn.set { color: var(--ink-2); font-weight: 500; }
139
+ .model-btn.set > .i:first-child { color: var(--accent); }
140
+ .model-btn:focus-visible { outline: 0; box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 1px var(--accent); }
141
+
142
+ /* harness · model button in the launcher */
143
+ .runner-btn {
144
+ display: inline-flex; align-items: center; height: 30px; padding: 0 8px 0 4px; border-radius: 9px; gap: 0;
145
+ border: 1px solid var(--line); background: var(--surface); font-size: 12.5px; color: var(--ink-2);
146
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
147
+ }
148
+ .runner-btn:hover, .runner-btn.open { background: var(--bg-2); border-color: var(--line-2); }
149
+ .runner-btn:focus-visible { outline: 0; box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 1px var(--accent); }
150
+ .rb-logo { display: grid; place-items: center; width: 22px; height: 22px; margin-right: 6px; border-radius: 6px; background: var(--bg-2); color: var(--ink); }
151
+ .runner-btn:hover .rb-logo, .runner-btn.open .rb-logo { background: var(--surface); }
152
+ .rb-logo > * { display: block; width: 13px; height: 13px; }
153
+ .rb-logo .mono, .pk-logo .mono { background: currentColor; -webkit-mask: var(--logo) center / contain no-repeat; mask: var(--logo) center / contain no-repeat; }
154
+ .rb-h { font-weight: 500; color: var(--ink); }
155
+ .rb-m { position: relative; margin-left: 13px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
156
+ .rb-m::before { content: ''; position: absolute; left: -7px; top: 50%; width: 1px; height: 12px; margin-top: -6px; background: var(--line-2); }
157
+ .rb-e { flex: none; margin-left: 6px; padding: 1px 6px; border-radius: 6px; font-size: 11.5px; background: var(--accent-soft); color: var(--accent-ink); }
158
+ .runner-btn .chev { width: 12px; height: 12px; margin-left: 6px; color: var(--faint); transform: rotate(90deg); transition: transform 0.2s var(--ease); }
159
+ .runner-btn.open .chev { transform: rotate(-90deg); }
160
+
161
+ /* harness + model picker */
162
+ .picker {
163
+ position: fixed; z-index: 40; display: grid; grid-template-columns: 200px 1fr; height: min(420px, 70vh); overflow: hidden;
164
+ border-radius: 14px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-2); animation: pop 0.2s var(--ease);
165
+ }
166
+ .pk-side { display: flex; flex-direction: column; gap: 2px; padding: 6px; overflow-y: auto; background: var(--surface-2); border-right: 1px solid var(--line); }
167
+ .pk-head, .pk-ghead { padding: 8px 10px 5px; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
168
+ .pk-h, .pk-m { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 9px; border-radius: 9px; text-align: left; outline: 0; color: var(--ink-2); transition: background 0.12s; }
169
+ .pk-h:hover:not(:disabled), .pk-m:hover, .pk-m:focus-visible { background: var(--bg-2); }
170
+ .pk-h:focus-visible { box-shadow: inset 0 0 0 1px var(--line-2); }
171
+ .pk-h.on { background: var(--surface); box-shadow: 0 0 0 1px var(--line-2), var(--shadow-1); }
172
+ .pk-h.off { opacity: 0.45; cursor: not-allowed; }
173
+ .pk-logo { display: grid; place-items: center; flex: none; width: 28px; height: 28px; border-radius: 8px; background: var(--surface); border: 1px solid var(--line); color: var(--ink); }
174
+ .pk-h.on .pk-logo { background: var(--bg-2); border-color: transparent; }
175
+ .pk-logo > * { display: block; width: 15px; height: 15px; }
176
+ .pk-text { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
177
+ .pk-text b { font-weight: 500; font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
178
+ .pk-text small { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
179
+ .pk-text small:empty { display: none; }
180
+ .pk-text small.warn { color: var(--warn); }
181
+ .pk-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
182
+ .pk-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
183
+ .pk-search { flex: none; width: auto; margin: 8px 8px 2px; color: var(--faint); }
184
+ .pk-list { flex: 1; overflow-y: auto; padding: 6px; }
185
+ .pk-group + .pk-group { margin-top: 4px; }
186
+ .pk-check { width: 15px; height: 15px; flex: none; color: var(--accent); visibility: hidden; }
187
+ .pk-m.on .pk-check { visibility: visible; }
188
+ .pk-m.on b { color: var(--accent-ink); }
189
+ .pk-effort { flex: none; display: flex; flex-direction: column; gap: 6px; padding: 8px 8px 10px; border-top: 1px solid var(--line); background: var(--surface-2); }
190
+ .pk-effort[hidden] { display: none; }
191
+ .pk-ehead { padding: 0 2px; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
192
+ .pk-seg { display: flex; min-width: 0; gap: 2px; padding: 2px; overflow-x: auto; border-radius: 9px; background: var(--bg-2); border: 1px solid var(--line); }
193
+ .pk-seg button { flex: 1 0 auto; padding: 4px 7px; border-radius: 7px; font-size: 12px; color: var(--muted); white-space: nowrap; outline: 0; transition: background 0.12s, color 0.12s; }
194
+ .pk-seg button:hover { color: var(--ink); }
195
+ .pk-seg button:focus-visible { box-shadow: inset 0 0 0 1px var(--accent); }
196
+ .pk-seg button.on { background: var(--surface); color: var(--accent-ink); font-weight: 500; box-shadow: var(--shadow-1); }
197
+ .pk-empty { padding: 30px 10px; text-align: center; font-size: 13px; color: var(--muted); }
198
+ @media (max-width: 560px) {
199
+ .picker { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
200
+ .pk-side { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line); }
201
+ .pk-side .pk-head, .pk-h .pk-text small { display: none; }
202
+ .pk-h { width: auto; flex: none; }
203
+ }
204
+
@@ -0,0 +1,61 @@
1
+ /* ---------- layout: one column — top bar, launcher, feed ---------- */
2
+ .stage {
3
+ position: relative; height: 100vh; overflow-y: auto; overflow-x: hidden; scroll-padding-top: 116px; /* top bar + docked launcher */
4
+ /* reserve the gutter on both sides so the scrollbar appearing never shifts or off-centres the layout */
5
+ scrollbar-gutter: stable both-edges;
6
+ }
7
+
8
+ /* scrollbars: a hairline pill on a clear track — faint at rest, firmer and wider when grabbed, gone when idle */
9
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
10
+ ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
11
+ ::-webkit-scrollbar-thumb {
12
+ border-radius: 99px; border: 3px solid transparent; min-height: 40px;
13
+ background: color-mix(in srgb, var(--ink) 16%, transparent) padding-box;
14
+ }
15
+ ::-webkit-scrollbar-thumb:hover { border-width: 2px; background-color: color-mix(in srgb, var(--ink) 32%, transparent); }
16
+ ::-webkit-scrollbar-thumb:active { border-width: 2px; background-color: color-mix(in srgb, var(--ink) 45%, transparent); }
17
+ :not(:hover)::-webkit-scrollbar-thumb { background-color: transparent; }
18
+ ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
19
+ @supports not selector(::-webkit-scrollbar) {
20
+ * { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--ink) 20%, transparent) transparent; }
21
+ }
22
+ .feed-wrap { max-width: 800px; margin: 0 auto; padding: 20px 24px 120px; }
23
+
24
+ .topbar {
25
+ position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: 14px; height: 52px; padding: 0 20px;
26
+ background: color-mix(in srgb, var(--bg) 94%, transparent); backdrop-filter: blur(10px);
27
+ border-bottom: 1px solid transparent; transition: border-color 0.2s;
28
+ }
29
+ .topbar.scrolled { border-bottom-color: var(--line); }
30
+
31
+ .to-top {
32
+ position: fixed; top: 116px; left: 0; right: 0; margin-inline: auto; z-index: 20; display: flex; align-items: center; gap: 7px;
33
+ width: max-content; height: 38px; padding: 0 9px 0 12px; border-radius: 19px; color: var(--ink-2);
34
+ background: var(--surface); border: 1px solid var(--line-2); box-shadow: var(--shadow-2);
35
+ transition: color 0.15s, border-color 0.15s, translate 0.2s var(--ease); animation: to-top-in 0.25s var(--ease);
36
+ }
37
+ .to-top kbd { line-height: 1.3; }
38
+ .to-top:hover { color: var(--ink); border-color: var(--faint); translate: 0 -2px; }
39
+ @keyframes to-top-in { from { opacity: 0; transform: translateY(-10px) scale(0.97); } }
40
+ .to-top:focus-visible { outline: 0; box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 1px var(--accent); }
41
+ .brand { display: flex; align-items: center; gap: 9px; flex: none; }
42
+ .brand .word { font: 600 15px/1 var(--display); letter-spacing: -0.02em; }
43
+ .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: none; }
44
+ .dot.ok { background: var(--good); }
45
+ .dot.bad { background: var(--bad); }
46
+
47
+ /* project filter chips in the top bar */
48
+ .projects { flex: 1; min-width: 0; display: flex; align-items: center; gap: 4px; overflow-x: auto; scrollbar-width: none; }
49
+ .projects::-webkit-scrollbar { display: none; }
50
+ .p-chip {
51
+ display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: 99px; flex: none;
52
+ font-size: 12.5px; color: var(--muted); border: 1px solid transparent; transition: background 0.15s, border-color 0.15s, color 0.15s;
53
+ }
54
+ .p-chip:hover { background: var(--bg-2); color: var(--ink-2); }
55
+ .p-chip.on { background: var(--surface); border-color: var(--line-2); color: var(--ink); font-weight: 500; }
56
+ .p-chip.add { padding: 0 6px; }
57
+ .p-chip.add .i { width: 14px; height: 14px; }
58
+ .p-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pc); display: inline-block; flex: none; }
59
+ .p-run { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); animation: pulse 1.6s ease-in-out infinite; }
60
+ .p-need { font-size: 10.5px; font-weight: 600; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 99px; background: var(--ink); color: var(--bg); display: grid; place-items: center; }
61
+
@@ -0,0 +1,125 @@
1
+ /* ---------- machines: other computers over SSH ---------- */
2
+ .machines-btn.trouble .i { color: var(--warn); }
3
+ .q-machine {
4
+ display: inline-flex; align-items: center; gap: 4px; max-width: 120px; padding: 0 6px; border-radius: 5px; border: 1px solid var(--line);
5
+ font-size: 11px; font-weight: 500; line-height: 17px; color: var(--ink-2); white-space: nowrap;
6
+ }
7
+ .q-machine span { overflow: hidden; text-overflow: ellipsis; }
8
+ .q-machine .i { flex: none; width: 11px; height: 11px; color: var(--accent); }
9
+ .q-machine.off { color: var(--faint); border-style: dashed; }
10
+ .q-machine.off .i { color: var(--faint); }
11
+ /* the launcher: one group of project chips per place it runs on */
12
+ .l-projects { row-gap: 6px; }
13
+ .lp-group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
14
+ .lp-group + .lp-group { margin-left: 4px; padding-left: 10px; border-left: 1px solid var(--line); }
15
+ .lp-gl { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 500; color: var(--ink-2); }
16
+ .lp-gl .i { width: 13px; height: 13px; color: var(--accent); }
17
+ .lp-gl.off { color: var(--faint); }
18
+ .lp-gl.off .i { color: var(--faint); }
19
+ .lp-gl .link { margin-left: 2px; }
20
+ .where-btn .chev { width: 11px; height: 11px; opacity: 0.6; transform: rotate(90deg); }
21
+ .where-btn.bare span, .where-btn.bare .chev { display: none; }
22
+ .where-menu { min-width: 300px; }
23
+ .wm-h { display: flex; align-items: baseline; gap: 8px; padding: 6px 10px 6px; font-size: 12px; font-weight: 600; color: var(--ink); }
24
+ .wm-h small { font-weight: 400; color: var(--faint); }
25
+ .where-menu .wm-row { gap: 10px; }
26
+ .wm-check { display: grid; place-items: center; width: 16px; height: 16px; flex: none; border-radius: 5px; border: 1.5px solid var(--line-2); color: transparent; }
27
+ .wm-check .i { width: 11px; height: 11px; }
28
+ .wm-row.on .wm-check { background: var(--accent); border-color: var(--accent); color: #fff; }
29
+ .wm-icon { position: relative; display: inline-grid; place-items: center; flex: none; color: var(--muted); }
30
+ .wm-icon .i { width: 16px; height: 16px; }
31
+ .wm-icon .dot { position: absolute; right: -3px; bottom: -2px; width: 7px; height: 7px; border: 1.5px solid var(--surface); box-sizing: content-box; }
32
+ .wm-row.on .wm-icon { color: var(--accent); }
33
+ .wm-name { flex: 1; min-width: 0; display: flex; flex-direction: column; text-align: left; }
34
+ .wm-name b { font-weight: 500; color: var(--ink); }
35
+ .wm-name small { font: 11px var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
36
+ .wm-agents { display: inline-flex; gap: 5px; }
37
+ .wm-only { padding: 1px 6px; border-radius: 6px; font-size: 11px; color: var(--muted); opacity: 0; transition: opacity 0.12s; }
38
+ .wm-row:hover .wm-only { opacity: 1; }
39
+ .wm-only:hover { background: var(--bg-2); color: var(--ink); }
40
+ .wm-sep { height: 1px; margin: 5px 4px; background: var(--line); }
41
+ .p-chip .p-where { width: 11px; height: 11px; color: var(--muted); }
42
+ .projects-modal .where { margin-bottom: 12px; }
43
+ .projects-modal .where .i { width: 13px; height: 13px; color: var(--muted); }
44
+ .projects-modal .where button.on .i { color: var(--accent); }
45
+ .projects-modal .where .dot { width: 6px; height: 6px; }
46
+ .machines-modal { width: min(640px, 100%); }
47
+ .mc-list { display: flex; flex-direction: column; gap: 10px; }
48
+ .mc { padding: 12px 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); }
49
+ .mc-head { display: flex; align-items: center; gap: 10px; }
50
+ .mc-icon { position: relative; flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: 9px; background: var(--bg-2); color: var(--muted); }
51
+ .mc-icon .i { width: 17px; height: 17px; }
52
+ .mc-dot { position: absolute; right: -2px; bottom: -2px; width: 10px; height: 10px; border-radius: 50%; background: var(--faint); border: 2px solid var(--surface); }
53
+ .mc.st-online .mc-icon { color: var(--accent); }
54
+ .mc.st-online .mc-dot { background: var(--good); }
55
+ .mc.st-error .mc-dot { background: var(--bad); }
56
+ .mc:is(.st-connecting, .st-reconnecting) .mc-dot { background: var(--warn); animation: blink 1.2s infinite; }
57
+ .mc-title { flex: 1; min-width: 0; display: flex; flex-direction: column; }
58
+ .mc-title b { font-size: 14px; font-weight: 600; }
59
+ .mc-title span { font: 11.5px var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
60
+ .mc-state { font-size: 12px; color: var(--muted); white-space: nowrap; }
61
+ .mc.st-online .mc-state { color: var(--good); }
62
+ .mc.st-error .mc-state { color: var(--bad); }
63
+ .mc-meta, .mc-steps, .mc-hint, .mc-projects, .mc-edit { margin-left: 42px; }
64
+ .mc-meta { display: flex; align-items: center; gap: 12px; margin-top: 8px; font-size: 12px; color: var(--muted); }
65
+ .mc-meta .bad { color: var(--bad); }
66
+ .mc-meta .mc-update { margin-left: -6px; color: var(--accent); }
67
+ .mc-agents { display: inline-flex; gap: 7px; }
68
+ .mc-steps { list-style: none; display: flex; flex-direction: column; gap: 5px; margin-top: 10px; margin-bottom: 0; padding: 0; }
69
+ .mc-steps li { display: grid; grid-template-columns: 16px auto 1fr; align-items: center; column-gap: 8px; font-size: 12.5px; color: var(--faint); }
70
+ .mc-steps li b { font-weight: 500; }
71
+ .mc-steps li small { font: 11.5px var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
72
+ .mc-si { display: grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--line-2); }
73
+ .mc-si .i { width: 10px; height: 10px; }
74
+ .mc-steps .s-done { color: var(--ink-2); }
75
+ .mc-steps .s-done .mc-si { background: var(--good); border-color: var(--good); color: #fff; animation: check-in 0.25s var(--ease); }
76
+ .mc-steps .s-run { color: var(--ink); }
77
+ .mc-steps .s-run .mc-si { border-color: var(--accent); border-top-color: transparent; animation: spin 0.8s linear infinite; }
78
+ .mc-steps .s-fail { color: var(--bad); }
79
+ .mc-steps .s-fail .mc-si { background: var(--bad); border-color: var(--bad); color: #fff; }
80
+ .mc-steps .s-fail small { color: var(--bad); white-space: normal; }
81
+ .mc-steps .s-warn { color: var(--warn); }
82
+ .mc-steps .s-warn .mc-si { border-color: var(--warn); color: var(--warn); }
83
+ .mc-steps .s-warn small { white-space: normal; }
84
+ .mc-hint { margin-top: 10px; padding: 10px 12px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--bad) 25%, var(--line)); background: var(--del-bg); font-size: 12.5px; color: var(--ink-2); }
85
+ .mc-hint.soft { border-color: var(--line); background: var(--surface-2); }
86
+ .mc-hint p { margin: 0 0 6px; }
87
+ .mc-hint > :last-child { margin-bottom: 0; }
88
+ .mc-cmd { display: flex; align-items: center; gap: 6px; margin: 4px 0 8px; padding: 3px 3px 3px 10px; border-radius: 8px; background: var(--term-bg); color: var(--term-ink); }
89
+ .mc-cmd code { flex: 1; font: 12px var(--mono); }
90
+ .mc-cmd .ghost { color: var(--term-ink); }
91
+ .mc-cmd .ghost:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
92
+ .mc-log summary { cursor: pointer; font-size: 12px; color: var(--muted); width: max-content; }
93
+ .mc-log pre { max-height: 180px; overflow: auto; margin: 6px 0 0; padding: 8px 10px; border-radius: 8px; background: var(--surface); border: 1px solid var(--line); font: 11px/1.5 var(--mono); white-space: pre-wrap; }
94
+ .mc-projects { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
95
+ .mc-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
96
+ .mc-add { margin-top: 12px; }
97
+ .mc-add h4 { margin-top: 18px; }
98
+ .mc-new { height: 32px; }
99
+ .mc-found { margin-bottom: 14px; }
100
+ .mc-found h4 { margin: 0 0 8px; }
101
+ .mc-found-list { display: flex; flex-wrap: wrap; gap: 6px; }
102
+ .mc-found .lp-chip .i { width: 14px; height: 14px; color: var(--muted); }
103
+ .mc-found .lp-chip.off { color: var(--muted); }
104
+ .mc-fdot { width: 7px; height: 7px; border-radius: 50%; background: var(--good); }
105
+ .lp-chip.off .mc-fdot { background: var(--faint); }
106
+ .mc-form { display: flex; flex-direction: column; gap: 10px; }
107
+ .mc-row { display: flex; gap: 10px; }
108
+ .mc-field { display: flex; flex-direction: column; gap: 5px; width: 150px; min-width: 0; }
109
+ .mc-field.grow { flex: 1; width: auto; }
110
+ .mc-field span { font-size: 12px; font-weight: 500; color: var(--ink-2); }
111
+ .mc-adv summary { cursor: pointer; width: max-content; font-size: 12.5px; color: var(--muted); }
112
+ .mc-adv[open] summary { margin-bottom: 8px; }
113
+ .mc-check { margin-top: 8px; }
114
+ /* Hosted inside Settings: undo its row styling for labels (centred, padded, bordered, input flexed to 0 tall). */
115
+ .mc-form label { padding: 0; border-bottom: 0; }
116
+ .mc-form .mc-field { align-items: stretch; justify-content: flex-start; gap: 5px; }
117
+ .mc-form .mc-field > span { text-align: left; }
118
+ .mc-form .mc-field > .input { flex: none; max-width: none; }
119
+ .mc-form .mc-check { display: inline-flex; justify-content: flex-start; gap: 8px; width: max-content; }
120
+ .mc-foot { display: flex; align-items: center; gap: 10px; }
121
+ .mc-foot .hint { flex: 1; margin: 0 !important; font-size: 12px !important; }
122
+ .mc-foot .primary { flex: none; }
123
+ .mc-foot .primary .i { width: 14px; height: 14px; }
124
+ .menu button.danger:hover { color: var(--bad); background: var(--del-bg); }
125
+