codebay 0.10.1 → 0.11.1
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/.mochi/manifest.json +196 -170
- package/.mochi/svelte-client/HydratableIsland-dammyce5.js +1 -0
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-v65ctd99.js +2 -0
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-htjfv8q9.js +14 -0
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-v07seqge.js +1 -0
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-hj9yvqc4.js +38 -0
- package/.mochi/svelte-client/chunk-0zkdsrth.js +2 -0
- package/.mochi/svelte-client/{chunk-bn731rda.js → chunk-566rqq4c.js} +2 -2
- package/.mochi/svelte-client/chunk-79aj59e0.js +2 -0
- package/.mochi/svelte-client/{chunk-wvtkwfsh.js → chunk-czkxd74n.js} +1 -1
- package/.mochi/svelte-client/{chunk-p20tkpde.js → chunk-hxydjtda.js} +1 -1
- package/.mochi/svelte-client/chunk-qx1defbm.js +2 -0
- package/.mochi/svelte-client/chunk-r3wxmmc0.js +2 -0
- package/.mochi/svelte-client/{chunk-j72r8pkk.js → chunk-vs6pd2b4.js} +2 -2
- package/.mochi/svelte-client/{chunk-t0ap7q89.js → chunk-wjagstre.js} +1 -1
- package/.mochi/svelte-client/{chunk-0fjx3z3r.js → chunk-y9yy1004.js} +1 -1
- package/.mochi/svelte-compile/{App-59cdj86f.server.js → App-mzn3dqjm.server.js} +182 -72
- package/.mochi/svelte-compile/{Avatars-fmagfyj2.server.js → Avatars-c0k5jx8q.server.js} +3 -6
- package/.mochi/svelte-compile/{Instance-s1gqwzmx.server.js → Instance-hj76qnha.server.js} +4 -4
- package/.mochi/svelte-compile/{Settings-k447mt0x.server.js → Settings-tvc5yp08.server.js} +39 -28
- package/.mochi/svelte-compile/chunk-ed46m58e.js +22 -0
- package/.mochi/svelte-compile/{chunk-th2dbqvc.js → chunk-gz6hv1h5.js} +5 -1
- package/.mochi/svelte-compile/chunk-zd49axkf.js +65 -0
- package/.mochi/svelte-css/{AppBar-3bxinv4pabv8l.css → AppBar-1gtu2me3fni8e.css} +1 -1
- package/.mochi/svelte-css/AvatarEditor-1mjvec6bwdkv8.css +1 -0
- package/.mochi/svelte-css/{AvatarsView-2llo48w0wt0xn.css → AvatarsView-26ya4ep5n1ze7.css} +1 -1
- package/.mochi/svelte-css/Button-208u50wkms8ya.css +1 -0
- package/.mochi/svelte-css/CoinButton-2727devjtsn10.css +1 -0
- package/.mochi/svelte-css/CredMenu-7qowd1oxujet.css +1 -0
- package/.mochi/svelte-css/DashboardView-17a0b8epjurhb.css +1 -0
- package/.mochi/svelte-css/FolderBrowser-i0uj58dph27f.css +1 -0
- package/.mochi/svelte-css/HealthBox-r0giuhjn2wj4.css +1 -0
- package/.mochi/svelte-css/IdeBar-1mtgemqfuvqzi.css +1 -0
- package/.mochi/svelte-css/InstanceCard-25z7nas7y98zk.css +1 -0
- package/.mochi/svelte-css/InstanceFilter-2vglmyy0yrbbc.css +1 -0
- package/.mochi/svelte-css/InstanceView-32j12hyg8v9u0.css +1 -0
- package/.mochi/svelte-css/SettingsCog-1jdeprd3ilczz.css +1 -0
- package/.mochi/svelte-css/{SettingsView-n3oy75uonzp2.css → SettingsView-bccw34agd2bj.css} +1 -1
- package/.mochi/svelte-css/StatusBadge-2eisdtnrsnfdc.css +1 -0
- package/.mochi/svelte-css/TerminalPane-2ywc86rhtmr9l.css +1 -0
- package/.mochi/svelte-css/ThemePicker-3fw4synubier0.css +1 -0
- package/migrations/0008_config_migrated.sql +1 -0
- package/package.json +8 -8
- package/src/components/AppBar.svelte +2 -2
- package/src/components/AppShell.svelte +71 -7
- package/src/components/AvatarEditor.svelte +10 -10
- package/src/components/AvatarsView.svelte +0 -0
- package/src/components/Button.svelte +13 -9
- package/src/components/CoinButton.svelte +2 -2
- package/src/components/CredMenu.svelte +8 -6
- package/src/components/DashboardView.svelte +2 -2
- package/src/components/FolderBrowser.svelte +39 -36
- package/src/components/HealthBox.svelte +5 -4
- package/src/components/IdeBar.svelte +286 -112
- package/src/components/InstanceCard.svelte +7 -7
- package/src/components/InstanceFilter.svelte +6 -6
- package/src/components/InstanceView.svelte +28 -22
- package/src/components/SettingsCog.svelte +4 -3
- package/src/components/SettingsView.svelte +7 -11
- package/src/components/StatusBadge.svelte +8 -6
- package/src/components/TerminalPane.svelte +19 -7
- package/src/components/ThemePicker.svelte +150 -8
- package/src/components/TopBar.svelte +1 -1
- package/src/components/UiShowcase.svelte +303 -64
- package/src/lib/db.server.ts +7 -3
- package/src/lib/devcontainer.server.ts +187 -33
- package/src/lib/docker.server.ts +10 -0
- package/src/lib/git.server.ts +28 -0
- package/src/lib/instances.server.ts +49 -6
- package/src/lib/tab-nav.ts +30 -0
- package/src/routes.ts +12 -0
- package/src/shell.html +60 -23
- package/src/theme.ts +8 -1
- package/src/toast.ts +9 -0
- package/src/types.ts +7 -0
- package/.mochi/svelte-client/HydratableIsland-aebn1fa1.js +0 -1
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-rx6arrv5.js +0 -2
- package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-jpjdmgpe.js +0 -14
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-c9y47dat.js +0 -1
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-d61feq89.js +0 -38
- package/.mochi/svelte-client/chunk-8xvr02wh.js +0 -2
- package/.mochi/svelte-client/chunk-9143wy3j.js +0 -2
- package/.mochi/svelte-client/chunk-k37wj1jm.js +0 -2
- package/.mochi/svelte-client/chunk-pc0fm37c.js +0 -2
- package/.mochi/svelte-compile/chunk-3t7v4hk0.js +0 -52
- package/.mochi/svelte-compile/chunk-gkjgybb2.js +0 -15
- package/.mochi/svelte-css/AvatarEditor-2mdq8ss346drw.css +0 -1
- package/.mochi/svelte-css/Button-oharop2xz6am.css +0 -1
- package/.mochi/svelte-css/CoinButton-1rej3sh3tkjud.css +0 -1
- package/.mochi/svelte-css/CredMenu-9hwgeptp7crb.css +0 -1
- package/.mochi/svelte-css/DashboardView-1cy4xpqhja9sx.css +0 -1
- package/.mochi/svelte-css/FolderBrowser-2pxp9z1o7zixk.css +0 -1
- package/.mochi/svelte-css/HealthBox-er2her0jukwl.css +0 -1
- package/.mochi/svelte-css/IdeBar-3bgr3m5z0lpeg.css +0 -1
- package/.mochi/svelte-css/InstanceCard-3vnltxnbxkvay.css +0 -1
- package/.mochi/svelte-css/InstanceFilter-3q6x0jt0hcgpz.css +0 -1
- package/.mochi/svelte-css/InstanceView-1pd7j16bd2b16.css +0 -1
- package/.mochi/svelte-css/SettingsCog-jftyadvo64qo.css +0 -1
- package/.mochi/svelte-css/StatusBadge-3mq0j7z6aq4j.css +0 -1
- package/.mochi/svelte-css/TerminalPane-3bwgk6ulzq6b3.css +0 -1
- package/.mochi/svelte-css/ThemePicker-1ir8jyocm8mvi.css +0 -1
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
import RotateCw from '@lucide/svelte/icons/rotate-cw';
|
|
6
6
|
import Terminal from '@lucide/svelte/icons/terminal';
|
|
7
7
|
import LayoutTemplate from '@lucide/svelte/icons/layout-template';
|
|
8
|
+
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
|
9
|
+
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
|
8
10
|
import Avatar from './Avatar.svelte';
|
|
9
11
|
import AppBar from './AppBar.svelte';
|
|
10
12
|
import { withPopupMarker } from '../lib/popup-nav.ts';
|
|
13
|
+
import { nextTabIndex } from '../lib/tab-nav.ts';
|
|
11
14
|
|
|
12
15
|
let {
|
|
13
16
|
running,
|
|
@@ -33,62 +36,164 @@
|
|
|
33
36
|
oncommitrename: (id: string) => void;
|
|
34
37
|
oncancelrename: () => void;
|
|
35
38
|
} = $props();
|
|
39
|
+
|
|
40
|
+
const activeIndex = $derived(running.findIndex((i) => i.id === active));
|
|
41
|
+
// Falls back to the first tab so the strip always offers exactly one tab stop,
|
|
42
|
+
// even when `active` names an instance that has since stopped.
|
|
43
|
+
const focusIndex = $derived(activeIndex >= 0 ? activeIndex : 0);
|
|
44
|
+
|
|
45
|
+
let viewport = $state<HTMLDivElement | null>(null);
|
|
46
|
+
let content = $state<HTMLDivElement | null>(null);
|
|
47
|
+
let atStart = $state(true);
|
|
48
|
+
let atEnd = $state(true);
|
|
49
|
+
// `$state` rather than a plain object so `bind:this` into it is a tracked write —
|
|
50
|
+
// otherwise Svelte warns, and the scroll-into-view effect below misses the first bind.
|
|
51
|
+
const tabEls = $state<Record<string, HTMLButtonElement | null>>({});
|
|
52
|
+
|
|
53
|
+
// Both true means everything fits, so the chevrons stay out of a bar that only has 44px.
|
|
54
|
+
const overflowing = $derived(!atStart || !atEnd);
|
|
55
|
+
|
|
56
|
+
// 1px of slack: fractional layout widths leave scrollLeft a hair short of the true end.
|
|
57
|
+
function measure() {
|
|
58
|
+
if (!viewport) return;
|
|
59
|
+
atStart = viewport.scrollLeft <= 1;
|
|
60
|
+
atEnd = viewport.scrollLeft + viewport.clientWidth >= viewport.scrollWidth - 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function select(index: number) {
|
|
64
|
+
const inst = running[index];
|
|
65
|
+
if (!inst) return;
|
|
66
|
+
onselect(inst.id);
|
|
67
|
+
tabEls[inst.id]?.focus();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function onTabKeydown(e: KeyboardEvent) {
|
|
71
|
+
const next = nextTabIndex(activeIndex, e.key, running.length);
|
|
72
|
+
if (next === null) return;
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
select(next);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function page(direction: -1 | 1) {
|
|
78
|
+
if (!viewport) return;
|
|
79
|
+
viewport.scrollBy({ left: direction * Math.max(160, viewport.clientWidth * 0.7) });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Observing the content too, not just the viewport — adding or removing a tab moves
|
|
83
|
+
// scrollWidth without ever resizing the scroll container.
|
|
84
|
+
$effect(() => {
|
|
85
|
+
const vp = viewport;
|
|
86
|
+
const ct = content;
|
|
87
|
+
if (!vp || !ct) return;
|
|
88
|
+
measure();
|
|
89
|
+
const ro = new ResizeObserver(measure);
|
|
90
|
+
ro.observe(vp);
|
|
91
|
+
ro.observe(ct);
|
|
92
|
+
vp.addEventListener('scroll', measure, { passive: true });
|
|
93
|
+
return () => {
|
|
94
|
+
ro.disconnect();
|
|
95
|
+
vp.removeEventListener('scroll', measure);
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Keyboard selection can land on a tab that's scrolled out of sight.
|
|
100
|
+
$effect(() => {
|
|
101
|
+
tabEls[active]?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
102
|
+
});
|
|
36
103
|
</script>
|
|
37
104
|
|
|
38
105
|
<AppBar>
|
|
39
106
|
{#if running.length > 0}
|
|
40
|
-
<
|
|
41
|
-
{#
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
-
|
|
107
|
+
<div class="strip">
|
|
108
|
+
{#if overflowing}
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
class="page"
|
|
112
|
+
disabled={atStart}
|
|
113
|
+
onclick={() => page(-1)}
|
|
114
|
+
aria-label="Scroll tabs left"><ChevronLeft size={16} /></button
|
|
47
115
|
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
{/if}
|
|
117
|
+
<div class="viewport" bind:this={viewport}>
|
|
118
|
+
<div class="tabs" role="tablist" aria-label="Instances" bind:this={content}>
|
|
119
|
+
{#each running as inst, i (inst.id)}
|
|
120
|
+
{#if editingId === inst.id}
|
|
121
|
+
<div class="tab editing">
|
|
122
|
+
<Avatar
|
|
123
|
+
name={inst.name}
|
|
124
|
+
art={findAvatar(inst.avatar ?? undefined) ?? null}
|
|
125
|
+
scale={3}
|
|
126
|
+
/>
|
|
127
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
128
|
+
<input
|
|
129
|
+
class="tab-name-edit"
|
|
130
|
+
bind:value={editingName}
|
|
131
|
+
autofocus
|
|
132
|
+
onfocus={(e) => (e.currentTarget as HTMLInputElement).select()}
|
|
133
|
+
onblur={() => oncommitrename(inst.id)}
|
|
134
|
+
onkeydown={(e) => {
|
|
135
|
+
if (e.key === 'Enter') (e.currentTarget as HTMLInputElement).blur();
|
|
136
|
+
else if (e.key === 'Escape') oncancelrename();
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
{:else}
|
|
141
|
+
<button
|
|
142
|
+
bind:this={tabEls[inst.id]}
|
|
143
|
+
type="button"
|
|
144
|
+
class="tab"
|
|
145
|
+
class:active={inst.id === active}
|
|
146
|
+
id="tab-{inst.id}"
|
|
147
|
+
role="tab"
|
|
148
|
+
aria-selected={inst.id === active}
|
|
149
|
+
tabindex={i === focusIndex ? 0 : -1}
|
|
150
|
+
onclick={() => onselect(inst.id)}
|
|
151
|
+
ondblclick={() => onstartrename(inst)}
|
|
152
|
+
onkeydown={onTabKeydown}
|
|
153
|
+
title={inst.name}
|
|
154
|
+
>
|
|
155
|
+
<Avatar
|
|
156
|
+
name={inst.name}
|
|
157
|
+
art={findAvatar(inst.avatar ?? undefined) ?? null}
|
|
158
|
+
scale={3}
|
|
159
|
+
/>
|
|
160
|
+
<span class="tab-name">{inst.name}</span>
|
|
161
|
+
<span
|
|
162
|
+
class="tab-mode"
|
|
163
|
+
aria-label={inst.mode === 'terminal'
|
|
164
|
+
? 'Terminal-only instance'
|
|
165
|
+
: 'Full IDE instance'}
|
|
166
|
+
>
|
|
167
|
+
{#if inst.mode === 'terminal'}
|
|
168
|
+
<Terminal size={11} />
|
|
169
|
+
{:else}
|
|
170
|
+
<LayoutTemplate size={11} />
|
|
171
|
+
{/if}
|
|
172
|
+
</span>
|
|
173
|
+
<!-- The focused tab never lights up, so a lit LED always means "needs your eyes". -->
|
|
174
|
+
{#if inst.id !== active && attention[inst.id]}
|
|
175
|
+
<span
|
|
176
|
+
class="attn {attention[inst.id]}"
|
|
177
|
+
aria-label={attention[inst.id] === 'waiting'
|
|
178
|
+
? 'Claude is waiting for input'
|
|
179
|
+
: 'Claude finished'}
|
|
180
|
+
></span>
|
|
181
|
+
{/if}
|
|
182
|
+
</button>
|
|
183
|
+
{/if}
|
|
184
|
+
{/each}
|
|
89
185
|
</div>
|
|
90
|
-
|
|
91
|
-
|
|
186
|
+
</div>
|
|
187
|
+
{#if overflowing}
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
class="page trailing"
|
|
191
|
+
disabled={atEnd}
|
|
192
|
+
onclick={() => page(1)}
|
|
193
|
+
aria-label="Scroll tabs right"><ChevronRight size={16} /></button
|
|
194
|
+
>
|
|
195
|
+
{/if}
|
|
196
|
+
</div>
|
|
92
197
|
{/if}
|
|
93
198
|
<div class="right">
|
|
94
199
|
{#if onreload}
|
|
@@ -139,77 +244,110 @@
|
|
|
139
244
|
}
|
|
140
245
|
.cog:hover,
|
|
141
246
|
.reload:hover {
|
|
142
|
-
background: var(--
|
|
143
|
-
color: var(--
|
|
247
|
+
background: var(--fill);
|
|
248
|
+
color: var(--fill-ink);
|
|
144
249
|
}
|
|
145
|
-
|
|
250
|
+
|
|
251
|
+
.strip {
|
|
146
252
|
display: flex;
|
|
147
253
|
align-items: stretch;
|
|
148
|
-
overflow-x: auto;
|
|
149
254
|
min-width: 0;
|
|
150
255
|
}
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
/* The focused tab never gets these, so a pulsing tab always means "needs your eyes". */
|
|
160
|
-
.tab.attn-done {
|
|
161
|
-
animation: attn-pulse-green 1.8s ease-in-out infinite;
|
|
162
|
-
}
|
|
163
|
-
.tab.attn-waiting {
|
|
164
|
-
animation: attn-pulse-amber 1.8s ease-in-out infinite;
|
|
165
|
-
}
|
|
166
|
-
@keyframes attn-pulse-green {
|
|
167
|
-
0%,
|
|
168
|
-
100% {
|
|
169
|
-
background: transparent;
|
|
170
|
-
box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--attn-done) 40%, transparent);
|
|
171
|
-
}
|
|
172
|
-
50% {
|
|
173
|
-
background: color-mix(in srgb, var(--attn-done) 32%, transparent);
|
|
174
|
-
box-shadow: inset 0 -2px 0 var(--attn-done);
|
|
175
|
-
}
|
|
256
|
+
/* The native scrollbar would eat into a 44px bar, so the chevrons replace it. */
|
|
257
|
+
.viewport {
|
|
258
|
+
display: flex;
|
|
259
|
+
min-width: 0;
|
|
260
|
+
overflow-x: auto;
|
|
261
|
+
scroll-behavior: smooth;
|
|
262
|
+
scrollbar-width: none;
|
|
176
263
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
100% {
|
|
180
|
-
background: transparent;
|
|
181
|
-
box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--attn-waiting) 40%, transparent);
|
|
182
|
-
}
|
|
183
|
-
50% {
|
|
184
|
-
background: color-mix(in srgb, var(--attn-waiting) 32%, transparent);
|
|
185
|
-
box-shadow: inset 0 -2px 0 var(--attn-waiting);
|
|
186
|
-
}
|
|
264
|
+
.viewport::-webkit-scrollbar {
|
|
265
|
+
display: none;
|
|
187
266
|
}
|
|
188
267
|
@media (prefers-reduced-motion: reduce) {
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
animation-duration: 0s;
|
|
192
|
-
background: color-mix(in srgb, var(--attn-done) 28%, transparent);
|
|
193
|
-
}
|
|
194
|
-
.tab.attn-waiting {
|
|
195
|
-
background: color-mix(in srgb, var(--attn-waiting) 28%, transparent);
|
|
268
|
+
.viewport {
|
|
269
|
+
scroll-behavior: auto;
|
|
196
270
|
}
|
|
197
271
|
}
|
|
198
|
-
.
|
|
272
|
+
.tabs {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: stretch;
|
|
275
|
+
flex: none;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.page {
|
|
279
|
+
display: inline-flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
justify-content: center;
|
|
282
|
+
width: 28px;
|
|
283
|
+
flex: none;
|
|
199
284
|
appearance: none;
|
|
200
|
-
|
|
285
|
+
margin: 0;
|
|
286
|
+
padding: 0;
|
|
201
287
|
border: 0;
|
|
288
|
+
border-right: 1px solid var(--rule);
|
|
289
|
+
background: var(--bg-card);
|
|
290
|
+
color: var(--ink);
|
|
202
291
|
cursor: pointer;
|
|
203
|
-
|
|
204
|
-
|
|
292
|
+
}
|
|
293
|
+
.page.trailing {
|
|
294
|
+
border-right: 0;
|
|
295
|
+
border-left: 1px solid var(--rule);
|
|
296
|
+
}
|
|
297
|
+
.page:hover:not(:disabled) {
|
|
298
|
+
background: var(--fill);
|
|
299
|
+
color: var(--fill-ink);
|
|
300
|
+
}
|
|
301
|
+
.page:disabled {
|
|
302
|
+
opacity: var(--dim);
|
|
303
|
+
cursor: default;
|
|
304
|
+
}
|
|
305
|
+
.page:focus-visible {
|
|
306
|
+
outline: 2px solid var(--ink);
|
|
307
|
+
outline-offset: -2px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.tab {
|
|
205
311
|
display: inline-flex;
|
|
206
312
|
align-items: center;
|
|
207
313
|
gap: 10px;
|
|
208
|
-
|
|
314
|
+
flex: none;
|
|
209
315
|
max-width: 240px;
|
|
210
|
-
|
|
316
|
+
appearance: none;
|
|
317
|
+
margin: 0;
|
|
318
|
+
padding: 0 12px;
|
|
319
|
+
border: 0;
|
|
320
|
+
border-right: 1px solid var(--rule);
|
|
321
|
+
background: transparent;
|
|
322
|
+
color: var(--ink-soft);
|
|
323
|
+
font-family: var(--font-display);
|
|
324
|
+
font-weight: 700;
|
|
325
|
+
font-size: 13px;
|
|
211
326
|
text-transform: uppercase;
|
|
212
327
|
letter-spacing: 0.06em;
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
/* Quantized like the instance cards — motion in this theme reads as digital. */
|
|
330
|
+
transition: background 0.08s steps(2);
|
|
331
|
+
}
|
|
332
|
+
/* Inversion is the house hover idiom, but it's also the active state here, so hovering
|
|
333
|
+
gets a tint instead — that keeps idle → hover → active legible as three steps. */
|
|
334
|
+
.tab:not(.active):hover {
|
|
335
|
+
background: color-mix(in srgb, var(--ink) 12%, transparent);
|
|
336
|
+
color: var(--ink);
|
|
337
|
+
}
|
|
338
|
+
.tab.active {
|
|
339
|
+
background: var(--fill);
|
|
340
|
+
color: var(--fill-ink);
|
|
341
|
+
cursor: default;
|
|
342
|
+
/* The key has sunk into the chassis; this strip is the slot it dropped out of. */
|
|
343
|
+
box-shadow: inset 0 3px 0 var(--bg);
|
|
344
|
+
}
|
|
345
|
+
.tab:focus-visible {
|
|
346
|
+
outline: 2px solid var(--ink);
|
|
347
|
+
outline-offset: -2px;
|
|
348
|
+
}
|
|
349
|
+
.tab.active:focus-visible {
|
|
350
|
+
outline-color: var(--fill-ink);
|
|
213
351
|
}
|
|
214
352
|
.tab-name {
|
|
215
353
|
white-space: nowrap;
|
|
@@ -219,29 +357,65 @@
|
|
|
219
357
|
.tab-mode {
|
|
220
358
|
display: inline-flex;
|
|
221
359
|
align-items: center;
|
|
360
|
+
justify-content: center;
|
|
222
361
|
flex: none;
|
|
223
|
-
|
|
362
|
+
width: 18px;
|
|
363
|
+
height: 18px;
|
|
364
|
+
border: 1px solid currentColor;
|
|
365
|
+
opacity: 0.7;
|
|
224
366
|
}
|
|
225
|
-
|
|
226
|
-
|
|
367
|
+
|
|
368
|
+
.attn {
|
|
369
|
+
flex: none;
|
|
370
|
+
width: 10px;
|
|
371
|
+
height: 10px;
|
|
372
|
+
border: 1px solid var(--edge);
|
|
373
|
+
animation: tab-attn-pulse 1.4s ease-in-out infinite;
|
|
227
374
|
}
|
|
228
|
-
.
|
|
229
|
-
|
|
375
|
+
.attn.done {
|
|
376
|
+
background: var(--attn-done);
|
|
377
|
+
color: var(--attn-done);
|
|
230
378
|
}
|
|
231
|
-
.
|
|
379
|
+
.attn.waiting {
|
|
380
|
+
background: var(--attn-waiting);
|
|
381
|
+
color: var(--attn-waiting);
|
|
382
|
+
}
|
|
383
|
+
/* Redeclared rather than shared with InstanceCard — Svelte scopes keyframes per component. */
|
|
384
|
+
@keyframes tab-attn-pulse {
|
|
385
|
+
0%,
|
|
386
|
+
100% {
|
|
387
|
+
box-shadow: 0 0 0 0 transparent;
|
|
388
|
+
filter: brightness(0.85);
|
|
389
|
+
}
|
|
390
|
+
50% {
|
|
391
|
+
box-shadow: 0 0 6px 2px color-mix(in srgb, currentColor 60%, transparent);
|
|
392
|
+
filter: brightness(1.15);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
@media (prefers-reduced-motion: reduce) {
|
|
396
|
+
.attn {
|
|
397
|
+
animation: none;
|
|
398
|
+
filter: none;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.tab.editing {
|
|
232
403
|
cursor: default;
|
|
233
404
|
}
|
|
234
405
|
.tab-name-edit {
|
|
235
|
-
width:
|
|
406
|
+
width: 148px;
|
|
236
407
|
font-family: var(--font-mono);
|
|
237
408
|
font-size: 12px;
|
|
238
409
|
text-transform: uppercase;
|
|
239
410
|
letter-spacing: 0.06em;
|
|
240
411
|
margin: 0;
|
|
241
|
-
padding:
|
|
242
|
-
border: 1px solid var(--ink
|
|
412
|
+
padding: 3px 6px;
|
|
413
|
+
border: 1px solid var(--ink);
|
|
243
414
|
background: var(--bg);
|
|
244
415
|
color: var(--ink);
|
|
245
|
-
|
|
416
|
+
}
|
|
417
|
+
.tab-name-edit:focus-visible {
|
|
418
|
+
outline: 2px solid var(--ink);
|
|
419
|
+
outline-offset: -2px;
|
|
246
420
|
}
|
|
247
421
|
</style>
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
}
|
|
143
143
|
.card:hover:not(.busy) {
|
|
144
144
|
transform: translate(-1px, -1px);
|
|
145
|
-
box-shadow: 6px 6px 0 var(--
|
|
145
|
+
box-shadow: 6px 6px 0 var(--shadow);
|
|
146
146
|
}
|
|
147
147
|
/* Translucent scrim dims the card body and blocks clicks on the buttons beneath. */
|
|
148
148
|
.busy-overlay {
|
|
@@ -161,15 +161,15 @@
|
|
|
161
161
|
text-transform: uppercase;
|
|
162
162
|
letter-spacing: 0.1em;
|
|
163
163
|
padding: 4px 9px;
|
|
164
|
-
border: 1px solid var(--
|
|
165
|
-
background: var(--
|
|
166
|
-
color: var(--
|
|
164
|
+
border: 1px solid var(--fill);
|
|
165
|
+
background: var(--fill);
|
|
166
|
+
color: var(--fill-ink);
|
|
167
167
|
animation: lcd-blink 1.1s steps(1) infinite;
|
|
168
168
|
}
|
|
169
169
|
@keyframes lcd-blink {
|
|
170
170
|
50% {
|
|
171
171
|
background: transparent;
|
|
172
|
-
color: var(--
|
|
172
|
+
color: var(--fill);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
flex: none;
|
|
187
187
|
width: 14px;
|
|
188
188
|
height: 14px;
|
|
189
|
-
border: 1px solid var(--
|
|
189
|
+
border: 1px solid var(--edge);
|
|
190
190
|
}
|
|
191
191
|
.attn.attn-done {
|
|
192
192
|
background: var(--attn-done);
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
font-size: 12px;
|
|
267
267
|
color: var(--ink);
|
|
268
268
|
background: var(--bg);
|
|
269
|
-
border: 1px solid var(--
|
|
269
|
+
border: 1px solid var(--edge);
|
|
270
270
|
padding: 8px 10px;
|
|
271
271
|
max-height: 80px;
|
|
272
272
|
overflow: auto;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
.picker {
|
|
31
31
|
display: inline-flex;
|
|
32
32
|
flex: none;
|
|
33
|
-
border: 1px solid var(--
|
|
33
|
+
border: 1px solid var(--edge);
|
|
34
34
|
background: var(--bg-card);
|
|
35
35
|
}
|
|
36
36
|
.segment {
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
cursor: pointer;
|
|
50
50
|
}
|
|
51
51
|
.segment + .segment {
|
|
52
|
-
border-left: 1px solid var(--
|
|
52
|
+
border-left: 1px solid var(--edge);
|
|
53
53
|
}
|
|
54
54
|
.segment:hover:not(.active) {
|
|
55
|
-
background: var(--
|
|
56
|
-
color: var(--
|
|
55
|
+
background: var(--fill);
|
|
56
|
+
color: var(--fill-ink);
|
|
57
57
|
}
|
|
58
58
|
.segment.active {
|
|
59
59
|
font-weight: 700;
|
|
60
|
-
background: var(--
|
|
61
|
-
color: var(--
|
|
60
|
+
background: var(--fill);
|
|
61
|
+
color: var(--fill-ink);
|
|
62
62
|
cursor: default;
|
|
63
63
|
}
|
|
64
64
|
.segment:focus-visible {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { enhance } from 'mochi-framework';
|
|
12
12
|
import type { MochiEnhanceOptions } from 'mochi-framework';
|
|
13
13
|
import { onBackLinkClick, installPopupBackTrap } from '../lib/popup-nav.ts';
|
|
14
|
+
import { syncTheme } from '../theme.ts';
|
|
14
15
|
import { tick } from 'svelte';
|
|
15
16
|
|
|
16
17
|
let { id, injectionChecks = 0 }: { id: string; injectionChecks?: number } = $props();
|
|
@@ -61,6 +62,8 @@
|
|
|
61
62
|
} else if (msg.type === 'health' && msg.data.id === id) {
|
|
62
63
|
health = msg.data.health;
|
|
63
64
|
lastFetchedAt = Date.now();
|
|
65
|
+
} else if (msg.type === 'theme') {
|
|
66
|
+
syncTheme(msg.data.value);
|
|
64
67
|
}
|
|
65
68
|
})
|
|
66
69
|
);
|
|
@@ -323,12 +326,13 @@
|
|
|
323
326
|
letter-spacing: 0.06em;
|
|
324
327
|
color: var(--ink);
|
|
325
328
|
text-decoration: none;
|
|
326
|
-
border: 1px solid var(--
|
|
329
|
+
border: 1px solid var(--edge);
|
|
327
330
|
padding: 7px 12px;
|
|
328
331
|
}
|
|
329
332
|
.open:hover {
|
|
330
|
-
background: var(--
|
|
331
|
-
color: var(--
|
|
333
|
+
background: var(--fill);
|
|
334
|
+
border-color: var(--fill);
|
|
335
|
+
color: var(--fill-ink);
|
|
332
336
|
}
|
|
333
337
|
.restart {
|
|
334
338
|
display: inline-flex;
|
|
@@ -341,16 +345,17 @@
|
|
|
341
345
|
letter-spacing: 0.06em;
|
|
342
346
|
color: var(--ink);
|
|
343
347
|
background: var(--bg);
|
|
344
|
-
border: 1px solid var(--
|
|
348
|
+
border: 1px solid var(--edge);
|
|
345
349
|
padding: 7px 12px;
|
|
346
350
|
cursor: pointer;
|
|
347
351
|
}
|
|
348
352
|
.restart:hover:not(:disabled) {
|
|
349
|
-
background: var(--
|
|
350
|
-
color: var(--
|
|
353
|
+
background: var(--fill);
|
|
354
|
+
border-color: var(--fill);
|
|
355
|
+
color: var(--fill-ink);
|
|
351
356
|
}
|
|
352
357
|
.restart:disabled {
|
|
353
|
-
opacity:
|
|
358
|
+
opacity: var(--dim-panel);
|
|
354
359
|
cursor: not-allowed;
|
|
355
360
|
}
|
|
356
361
|
.restart-err {
|
|
@@ -406,17 +411,17 @@
|
|
|
406
411
|
text-transform: uppercase;
|
|
407
412
|
letter-spacing: 0.08em;
|
|
408
413
|
padding: 4px 9px;
|
|
409
|
-
background: var(--
|
|
410
|
-
color: var(--
|
|
411
|
-
border: 1px solid var(--
|
|
414
|
+
background: var(--fill-ink);
|
|
415
|
+
color: var(--fill);
|
|
416
|
+
border: 1px solid var(--fill-ink);
|
|
412
417
|
cursor: pointer;
|
|
413
418
|
}
|
|
414
419
|
.rebuild:hover:not(:disabled) {
|
|
415
420
|
background: transparent;
|
|
416
|
-
color: var(--
|
|
421
|
+
color: var(--fill-ink);
|
|
417
422
|
}
|
|
418
423
|
.rebuild:disabled {
|
|
419
|
-
opacity:
|
|
424
|
+
opacity: var(--dim-panel);
|
|
420
425
|
cursor: not-allowed;
|
|
421
426
|
}
|
|
422
427
|
.ports-body {
|
|
@@ -499,14 +504,15 @@
|
|
|
499
504
|
text-transform: uppercase;
|
|
500
505
|
letter-spacing: 0.06em;
|
|
501
506
|
padding: 6px 14px;
|
|
502
|
-
border: 1px solid var(--
|
|
507
|
+
border: 1px solid var(--edge);
|
|
503
508
|
background: var(--bg-card);
|
|
504
509
|
color: var(--ink);
|
|
505
510
|
cursor: pointer;
|
|
506
511
|
}
|
|
507
512
|
.add button:hover {
|
|
508
|
-
background: var(--
|
|
509
|
-
color: var(--
|
|
513
|
+
background: var(--fill);
|
|
514
|
+
border-color: var(--fill);
|
|
515
|
+
color: var(--fill-ink);
|
|
510
516
|
}
|
|
511
517
|
.port-err {
|
|
512
518
|
margin: 10px 0 0;
|
|
@@ -549,18 +555,18 @@
|
|
|
549
555
|
text-transform: uppercase;
|
|
550
556
|
letter-spacing: 0.08em;
|
|
551
557
|
padding: 4px 9px;
|
|
552
|
-
background: var(--
|
|
553
|
-
color: var(--
|
|
554
|
-
border: 1px solid var(--
|
|
558
|
+
background: var(--fill-ink);
|
|
559
|
+
color: var(--fill);
|
|
560
|
+
border: 1px solid var(--fill-ink);
|
|
555
561
|
cursor: pointer;
|
|
556
562
|
}
|
|
557
563
|
.copy:hover:not(:disabled) {
|
|
558
564
|
background: transparent;
|
|
559
|
-
color: var(--
|
|
565
|
+
color: var(--fill-ink);
|
|
560
566
|
}
|
|
561
567
|
.copy.active {
|
|
562
568
|
background: transparent;
|
|
563
|
-
color: var(--
|
|
569
|
+
color: var(--fill-ink);
|
|
564
570
|
}
|
|
565
571
|
/* The dot, not the inverted fill, is what separates "following" from a mere hover. */
|
|
566
572
|
.dot {
|
|
@@ -568,10 +574,10 @@
|
|
|
568
574
|
width: 6px;
|
|
569
575
|
height: 6px;
|
|
570
576
|
border-radius: 50%;
|
|
571
|
-
background: var(--
|
|
577
|
+
background: var(--live-dot);
|
|
572
578
|
}
|
|
573
579
|
.copy:disabled {
|
|
574
|
-
opacity:
|
|
580
|
+
opacity: var(--dim-panel);
|
|
575
581
|
cursor: not-allowed;
|
|
576
582
|
}
|
|
577
583
|
.logs {
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
justify-content: center;
|
|
20
20
|
width: 32px;
|
|
21
21
|
height: 32px;
|
|
22
|
-
border: 1px solid var(--
|
|
22
|
+
border: 1px solid var(--edge);
|
|
23
23
|
background: var(--bg-card);
|
|
24
24
|
color: var(--ink);
|
|
25
25
|
}
|
|
26
26
|
.cog:hover {
|
|
27
|
-
background: var(--
|
|
28
|
-
color: var(--
|
|
27
|
+
background: var(--fill);
|
|
28
|
+
border-color: var(--fill);
|
|
29
|
+
color: var(--fill-ink);
|
|
29
30
|
}
|
|
30
31
|
</style>
|