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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Plus from '@lucide/svelte/icons/plus';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
3
4
|
import toast, { Toaster } from 'svelte-french-toast';
|
|
5
|
+
import { TOAST_OPTIONS } from '../toast.ts';
|
|
4
6
|
import { avatars } from '../avatars/index.ts';
|
|
5
7
|
import type { AuthProvider, InstanceHealth, PortForward, Preflight } from '../types.ts';
|
|
6
8
|
|
|
@@ -16,58 +18,75 @@
|
|
|
16
18
|
import BranchBox from './BranchBox.svelte';
|
|
17
19
|
import PortsBox from './PortsBox.svelte';
|
|
18
20
|
import StatusBadge from './StatusBadge.svelte';
|
|
21
|
+
import InstanceCard from './InstanceCard.svelte';
|
|
22
|
+
import IdeBar from './IdeBar.svelte';
|
|
19
23
|
import ComponentDemo from './ui-showcase/ComponentDemo.svelte';
|
|
20
24
|
import type { Instance } from '../types.ts';
|
|
21
25
|
|
|
22
|
-
//
|
|
23
|
-
// only a
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
26
|
+
// Both the fill and the printed value are read back off the rendered chip, so this
|
|
27
|
+
// list is the only thing to touch when shell.html gains a token — and the labels
|
|
28
|
+
// can't drift from the theme the way a hardcoded hex did.
|
|
29
|
+
const palette: { group: string; tokens: string[] }[] = [
|
|
30
|
+
{ group: 'Surfaces', tokens: ['--bg', '--bg-card', '--screen-bg', '--screen-ink'] },
|
|
31
|
+
{ group: 'Ink / text', tokens: ['--ink', '--ink-soft', '--ink-faint', '--led-gray'] },
|
|
32
|
+
{ group: 'Structure', tokens: ['--edge', '--shadow', '--rule', '--rule-soft', '--grid-line'] },
|
|
33
|
+
{ group: 'Inverted fill', tokens: ['--fill', '--fill-ink'] },
|
|
34
|
+
{
|
|
35
|
+
group: 'Semantic',
|
|
36
|
+
tokens: [
|
|
37
|
+
'--warn-bg',
|
|
38
|
+
'--warn-line',
|
|
39
|
+
'--warn-ink',
|
|
40
|
+
'--ok-bg',
|
|
41
|
+
'--ok-line',
|
|
42
|
+
'--ok-ink',
|
|
43
|
+
'--danger',
|
|
44
|
+
'--danger-soft',
|
|
45
|
+
'--info'
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
group: 'Attention',
|
|
50
|
+
tokens: ['--attn-done', '--attn-waiting', '--live-dot', '--switch-on-bg']
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const chips: Record<string, HTMLElement> = {};
|
|
55
|
+
let hexes = $state<Record<string, string>>({});
|
|
56
|
+
|
|
57
|
+
/** `rgb(r g b / a)` or `rgba(r, g, b, a)` -> `#rrggbb`, with any alpha spelled out. */
|
|
58
|
+
function toHex(value: string): string {
|
|
59
|
+
const parts = value.match(/[\d.]+/g);
|
|
60
|
+
if (!parts || parts.length < 3) return value;
|
|
61
|
+
const hex = parts
|
|
62
|
+
.slice(0, 3)
|
|
63
|
+
.map((n) => Math.round(Number(n)).toString(16).padStart(2, '0'))
|
|
64
|
+
.join('');
|
|
65
|
+
const alpha = parts.length > 3 ? Number(parts[3]) : 1;
|
|
66
|
+
return alpha < 1 ? `#${hex} ${Math.round(alpha * 100)}%` : `#${hex}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readHexes() {
|
|
70
|
+
hexes = Object.fromEntries(
|
|
71
|
+
Object.entries(chips).map(([token, node]) => [
|
|
72
|
+
token,
|
|
73
|
+
toHex(getComputedStyle(node).backgroundColor)
|
|
74
|
+
])
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onMount(() => {
|
|
79
|
+
readHexes();
|
|
80
|
+
// An explicit pick mutates data-theme; "auto" only moves when the OS does.
|
|
81
|
+
const obs = new MutationObserver(readHexes);
|
|
82
|
+
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
83
|
+
const scheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
84
|
+
scheme.addEventListener('change', readHexes);
|
|
85
|
+
return () => {
|
|
86
|
+
obs.disconnect();
|
|
87
|
+
scheme.removeEventListener('change', readHexes);
|
|
88
|
+
};
|
|
89
|
+
});
|
|
71
90
|
|
|
72
91
|
let avatarScale = $state(8);
|
|
73
92
|
let avatarName = $state('demo-instance');
|
|
@@ -137,11 +156,103 @@
|
|
|
137
156
|
|
|
138
157
|
let branchName = $state('main');
|
|
139
158
|
|
|
159
|
+
// The dashboard's headline surface — it composes Avatar, StatusBadge, PortsBox,
|
|
160
|
+
// BranchBox and Button, so it's the one place their interplay is reviewable.
|
|
161
|
+
let cardStatus = $state<Instance['status']>('running');
|
|
162
|
+
let cardAttention = $state<Instance['attention']>('waiting');
|
|
163
|
+
let cardPending = $state<'' | 'start' | 'stop' | 'delete' | 'rebuild'>('');
|
|
164
|
+
let cardMode = $state<Instance['mode']>('ide');
|
|
165
|
+
let cardBranch = $state(true);
|
|
166
|
+
let cardEditing = $state(false);
|
|
167
|
+
let cardEditingName = $state('demo-instance');
|
|
168
|
+
const cardActions = ['', 'start', 'stop', 'delete', 'rebuild'] as const;
|
|
169
|
+
const demoInstance = $derived<Instance>({
|
|
170
|
+
id: 'demo',
|
|
171
|
+
name: 'demo-instance',
|
|
172
|
+
source_path: '/home/you/code/demo-instance',
|
|
173
|
+
workspace_path: '/home/you/.codebay/instances/demo',
|
|
174
|
+
host_port: 8001,
|
|
175
|
+
container_id: 'abc123',
|
|
176
|
+
remote_workspace_folder: '/workspaces/demo-instance',
|
|
177
|
+
status: cardStatus,
|
|
178
|
+
error: cardStatus === 'error' ? 'devcontainer up failed: exit code 1' : null,
|
|
179
|
+
created_at: 0,
|
|
180
|
+
image_source: 'local',
|
|
181
|
+
avatar: avatars[0]!.name,
|
|
182
|
+
mode: cardMode,
|
|
183
|
+
terminal_split: 0,
|
|
184
|
+
git_branch: cardBranch ? branchName : null,
|
|
185
|
+
attention: cardAttention,
|
|
186
|
+
forwarded_ports: demoPorts
|
|
187
|
+
});
|
|
188
|
+
|
|
140
189
|
const statuses: Instance['status'][] = ['creating', 'running', 'stopped', 'error'];
|
|
141
190
|
let badgeStatus = $state<Instance['status']>('error');
|
|
191
|
+
|
|
192
|
+
// The IDE tab strip only ever renders against running instances, and this
|
|
193
|
+
// environment may have no Docker at all — so the demo fabricates them.
|
|
194
|
+
const TAB_NAMES = [
|
|
195
|
+
'orion',
|
|
196
|
+
'meridian',
|
|
197
|
+
'atlas',
|
|
198
|
+
'vesper',
|
|
199
|
+
'cobalt',
|
|
200
|
+
'juniper',
|
|
201
|
+
'halcyon',
|
|
202
|
+
'zenith',
|
|
203
|
+
'perigee',
|
|
204
|
+
'lumen'
|
|
205
|
+
];
|
|
206
|
+
let tabCount = $state(4);
|
|
207
|
+
let tabActive = $state(0);
|
|
208
|
+
let tabAttention = $state(true);
|
|
209
|
+
let tabMixModes = $state(true);
|
|
210
|
+
let tabLongNames = $state(false);
|
|
211
|
+
let tabEditingId = $state<string | null>(null);
|
|
212
|
+
let tabEditingName = $state('');
|
|
213
|
+
// Renames are applied for real, so the inline editor is inspectable end to end.
|
|
214
|
+
let tabRenames = $state<Record<string, string>>({});
|
|
215
|
+
|
|
216
|
+
const demoTabs = $derived(
|
|
217
|
+
Array.from({ length: tabCount }, (_, i): Instance => {
|
|
218
|
+
const id = `demo-tab-${i}`;
|
|
219
|
+
const base = TAB_NAMES[i]!;
|
|
220
|
+
return {
|
|
221
|
+
id,
|
|
222
|
+
name: tabRenames[id] ?? (tabLongNames ? `${base}-refactor-spike-2026` : base),
|
|
223
|
+
source_path: `/home/demo/${base}`,
|
|
224
|
+
workspace_path: `/data/instances/${id}`,
|
|
225
|
+
host_port: 8001 + i,
|
|
226
|
+
container_id: `container-${i}`,
|
|
227
|
+
remote_workspace_folder: null,
|
|
228
|
+
status: 'running',
|
|
229
|
+
error: null,
|
|
230
|
+
created_at: 0,
|
|
231
|
+
image_source: 'local',
|
|
232
|
+
avatar: avatars[i % avatars.length]!.name,
|
|
233
|
+
mode: tabMixModes && i % 2 === 1 ? 'terminal' : 'ide',
|
|
234
|
+
terminal_split: 0,
|
|
235
|
+
git_branch: 'main',
|
|
236
|
+
attention: null,
|
|
237
|
+
forwarded_ports: []
|
|
238
|
+
};
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
// Clamped, since dragging the count down can strand the active index past the end.
|
|
242
|
+
const demoActiveIndex = $derived(Math.min(tabActive, tabCount - 1));
|
|
243
|
+
const demoActiveId = $derived(demoTabs[demoActiveIndex]?.id ?? '');
|
|
244
|
+
// Alternating states so both LED colors are comparable side by side.
|
|
245
|
+
const demoAttention = $derived(
|
|
246
|
+
Object.fromEntries(
|
|
247
|
+
demoTabs.map((inst, i) => [
|
|
248
|
+
inst.id,
|
|
249
|
+
tabAttention && i !== demoActiveIndex ? (i % 2 === 0 ? 'done' : 'waiting') : null
|
|
250
|
+
])
|
|
251
|
+
) as Record<string, 'done' | 'waiting' | null>
|
|
252
|
+
);
|
|
142
253
|
</script>
|
|
143
254
|
|
|
144
|
-
<Toaster />
|
|
255
|
+
<Toaster toastOptions={TOAST_OPTIONS} />
|
|
145
256
|
|
|
146
257
|
<TopBar
|
|
147
258
|
auth={providers}
|
|
@@ -166,16 +277,20 @@
|
|
|
166
277
|
<span class="palette-hint">Design tokens — tap to expand</span>
|
|
167
278
|
</summary>
|
|
168
279
|
<div class="palette">
|
|
169
|
-
{#each palette as { group,
|
|
280
|
+
{#each palette as { group, tokens } (group)}
|
|
170
281
|
<div class="swatch-group">
|
|
171
282
|
<h3>{group}</h3>
|
|
172
283
|
<div class="swatches">
|
|
173
|
-
{#each
|
|
284
|
+
{#each tokens as token (token)}
|
|
174
285
|
<figure class="swatch">
|
|
175
|
-
<div
|
|
286
|
+
<div
|
|
287
|
+
class="chip"
|
|
288
|
+
bind:this={chips[token]}
|
|
289
|
+
style="background: var({token});"
|
|
290
|
+
></div>
|
|
176
291
|
<figcaption>
|
|
177
292
|
<code>{token}</code>
|
|
178
|
-
<span>{
|
|
293
|
+
<span>{hexes[token] ?? '…'}</span>
|
|
179
294
|
</figcaption>
|
|
180
295
|
</figure>
|
|
181
296
|
{/each}
|
|
@@ -185,6 +300,64 @@
|
|
|
185
300
|
</div>
|
|
186
301
|
</details>
|
|
187
302
|
|
|
303
|
+
<ComponentDemo title="InstanceCard">
|
|
304
|
+
<!-- The card renders an <li>, and .card-grid mirrors the dashboard's own
|
|
305
|
+
column so its width and hover lift match what ships. -->
|
|
306
|
+
<ul class="card-grid">
|
|
307
|
+
<InstanceCard
|
|
308
|
+
instance={demoInstance}
|
|
309
|
+
editing={cardEditing}
|
|
310
|
+
bind:editingName={cardEditingName}
|
|
311
|
+
pending={cardPending || null}
|
|
312
|
+
onact={(a) => toast(`${a} (demo)`)}
|
|
313
|
+
onstartrename={() => (cardEditing = true)}
|
|
314
|
+
oncommitrename={() => (cardEditing = false)}
|
|
315
|
+
oncancelrename={() => (cardEditing = false)}
|
|
316
|
+
/>
|
|
317
|
+
</ul>
|
|
318
|
+
{#snippet controls()}
|
|
319
|
+
<label>
|
|
320
|
+
<span>status</span>
|
|
321
|
+
<select bind:value={cardStatus}>
|
|
322
|
+
{#each statuses as s (s)}
|
|
323
|
+
<option value={s}>{s}</option>
|
|
324
|
+
{/each}
|
|
325
|
+
</select>
|
|
326
|
+
</label>
|
|
327
|
+
<label>
|
|
328
|
+
<span>attention</span>
|
|
329
|
+
<select bind:value={cardAttention}>
|
|
330
|
+
<option value={null}>none</option>
|
|
331
|
+
<option value="done">done</option>
|
|
332
|
+
<option value="waiting">waiting</option>
|
|
333
|
+
</select>
|
|
334
|
+
</label>
|
|
335
|
+
<label>
|
|
336
|
+
<span>pending (busy overlay)</span>
|
|
337
|
+
<select bind:value={cardPending}>
|
|
338
|
+
{#each cardActions as a (a)}
|
|
339
|
+
<option value={a}>{a || 'none'}</option>
|
|
340
|
+
{/each}
|
|
341
|
+
</select>
|
|
342
|
+
</label>
|
|
343
|
+
<label>
|
|
344
|
+
<span>mode</span>
|
|
345
|
+
<select bind:value={cardMode}>
|
|
346
|
+
<option value="ide">ide</option>
|
|
347
|
+
<option value="terminal">terminal</option>
|
|
348
|
+
</select>
|
|
349
|
+
</label>
|
|
350
|
+
<label class="inline">
|
|
351
|
+
<input type="checkbox" bind:checked={cardBranch} />
|
|
352
|
+
<span>git branch</span>
|
|
353
|
+
</label>
|
|
354
|
+
<label class="inline">
|
|
355
|
+
<input type="checkbox" bind:checked={cardEditing} />
|
|
356
|
+
<span>renaming</span>
|
|
357
|
+
</label>
|
|
358
|
+
{/snippet}
|
|
359
|
+
</ComponentDemo>
|
|
360
|
+
|
|
188
361
|
<ComponentDemo title="Avatar">
|
|
189
362
|
<Avatar name={avatarName} scale={avatarScale} art={avatarArt} />
|
|
190
363
|
{#snippet controls()}
|
|
@@ -370,6 +543,57 @@
|
|
|
370
543
|
{/snippet}
|
|
371
544
|
</ComponentDemo>
|
|
372
545
|
|
|
546
|
+
<ComponentDemo title="IdeBar">
|
|
547
|
+
<div class="ide-bar-demo">
|
|
548
|
+
<IdeBar
|
|
549
|
+
running={demoTabs}
|
|
550
|
+
active={demoActiveId}
|
|
551
|
+
attention={demoAttention}
|
|
552
|
+
editingId={tabEditingId}
|
|
553
|
+
bind:editingName={tabEditingName}
|
|
554
|
+
onreload={() => toast('Reload editor (demo)')}
|
|
555
|
+
onselect={(id) => (tabActive = demoTabs.findIndex((t) => t.id === id))}
|
|
556
|
+
onstartrename={(inst) => {
|
|
557
|
+
tabEditingId = inst.id;
|
|
558
|
+
tabEditingName = inst.name;
|
|
559
|
+
}}
|
|
560
|
+
oncommitrename={(id) => {
|
|
561
|
+
const name = tabEditingName.trim();
|
|
562
|
+
if (name) tabRenames[id] = name;
|
|
563
|
+
tabEditingId = null;
|
|
564
|
+
}}
|
|
565
|
+
oncancelrename={() => (tabEditingId = null)}
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
<p class="demo-hint">
|
|
569
|
+
Click to select, double-click to rename. Arrow keys / Home / End move between tabs once one
|
|
570
|
+
has focus. The Alt+1–9 jump lives in AppShell rather than here, since it also installs
|
|
571
|
+
itself into the editor iframes — so it is inert in this demo.
|
|
572
|
+
</p>
|
|
573
|
+
{#snippet controls()}
|
|
574
|
+
<label>
|
|
575
|
+
<span>tabs ({tabCount})</span>
|
|
576
|
+
<input type="range" min="1" max="10" bind:value={tabCount} />
|
|
577
|
+
</label>
|
|
578
|
+
<label>
|
|
579
|
+
<span>active ({demoActiveIndex})</span>
|
|
580
|
+
<input type="range" min="0" max={tabCount - 1} bind:value={tabActive} />
|
|
581
|
+
</label>
|
|
582
|
+
<label>
|
|
583
|
+
<span>attention</span>
|
|
584
|
+
<input type="checkbox" bind:checked={tabAttention} />
|
|
585
|
+
</label>
|
|
586
|
+
<label>
|
|
587
|
+
<span>mixed modes</span>
|
|
588
|
+
<input type="checkbox" bind:checked={tabMixModes} />
|
|
589
|
+
</label>
|
|
590
|
+
<label>
|
|
591
|
+
<span>long names</span>
|
|
592
|
+
<input type="checkbox" bind:checked={tabLongNames} />
|
|
593
|
+
</label>
|
|
594
|
+
{/snippet}
|
|
595
|
+
</ComponentDemo>
|
|
596
|
+
|
|
373
597
|
<ComponentDemo title="Brand" note="No props — static branding.">
|
|
374
598
|
<Brand />
|
|
375
599
|
</ComponentDemo>
|
|
@@ -386,7 +610,6 @@
|
|
|
386
610
|
max-width: 740px;
|
|
387
611
|
margin: 0 auto;
|
|
388
612
|
padding: 32px;
|
|
389
|
-
background: var(--bg);
|
|
390
613
|
color: var(--ink);
|
|
391
614
|
}
|
|
392
615
|
header {
|
|
@@ -414,8 +637,8 @@
|
|
|
414
637
|
border-bottom: 1px solid var(--ink);
|
|
415
638
|
}
|
|
416
639
|
.gallery-link:hover {
|
|
417
|
-
background: var(--
|
|
418
|
-
color: var(--
|
|
640
|
+
background: var(--fill);
|
|
641
|
+
color: var(--fill-ink);
|
|
419
642
|
}
|
|
420
643
|
.grid {
|
|
421
644
|
display: flex;
|
|
@@ -460,12 +683,30 @@
|
|
|
460
683
|
border: 1px solid var(--rule-soft);
|
|
461
684
|
overflow: hidden;
|
|
462
685
|
}
|
|
686
|
+
/* Matches the dashboard grid's column so the card previews at its real width. */
|
|
687
|
+
.card-grid {
|
|
688
|
+
list-style: none;
|
|
689
|
+
margin: 0;
|
|
690
|
+
padding: 0;
|
|
691
|
+
width: min(360px, 100%);
|
|
692
|
+
}
|
|
463
693
|
.badge-row {
|
|
464
694
|
display: flex;
|
|
465
695
|
flex-wrap: wrap;
|
|
466
696
|
align-items: center;
|
|
467
697
|
gap: 10px;
|
|
468
698
|
}
|
|
699
|
+
/* The bar spans the full IDE viewport in situ, so give it the whole stage here. */
|
|
700
|
+
.ide-bar-demo {
|
|
701
|
+
width: 100%;
|
|
702
|
+
border: 1px solid var(--rule-soft);
|
|
703
|
+
}
|
|
704
|
+
.demo-hint {
|
|
705
|
+
margin: 0;
|
|
706
|
+
font-family: var(--font-mono);
|
|
707
|
+
font-size: 11px;
|
|
708
|
+
color: var(--ink-faint);
|
|
709
|
+
}
|
|
469
710
|
.presets {
|
|
470
711
|
display: flex;
|
|
471
712
|
gap: 6px;
|
|
@@ -478,13 +719,14 @@
|
|
|
478
719
|
letter-spacing: 0.06em;
|
|
479
720
|
color: var(--ink);
|
|
480
721
|
background: var(--bg-card);
|
|
481
|
-
border: 1px solid var(--
|
|
722
|
+
border: 1px solid var(--edge);
|
|
482
723
|
padding: 5px 0;
|
|
483
724
|
cursor: pointer;
|
|
484
725
|
}
|
|
485
726
|
.presets button:hover {
|
|
486
|
-
background: var(--
|
|
487
|
-
color: var(--
|
|
727
|
+
background: var(--fill);
|
|
728
|
+
border-color: var(--fill);
|
|
729
|
+
color: var(--fill-ink);
|
|
488
730
|
}
|
|
489
731
|
|
|
490
732
|
.palette-card {
|
|
@@ -550,10 +792,7 @@
|
|
|
550
792
|
}
|
|
551
793
|
.swatch .chip {
|
|
552
794
|
height: 44px;
|
|
553
|
-
border: 1px solid
|
|
554
|
-
}
|
|
555
|
-
.swatch .chip.bordered {
|
|
556
|
-
border-color: var(--rule-soft);
|
|
795
|
+
border: 1px solid var(--rule-soft);
|
|
557
796
|
}
|
|
558
797
|
.swatch figcaption {
|
|
559
798
|
display: flex;
|
package/src/lib/db.server.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface InstanceRow {
|
|
|
36
36
|
mode: InstanceMode;
|
|
37
37
|
/** Terminal mode only: 1 when the scratch-shell pane was left open, so a reload restores it. */
|
|
38
38
|
terminal_split: number;
|
|
39
|
+
/** 0 until the legacy in-place config injection has been checked/undone; new rows start at 1. */
|
|
40
|
+
config_migrated: number;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export interface PortForwardRow {
|
|
@@ -66,8 +68,8 @@ export function closeDb(): void {
|
|
|
66
68
|
export function insertInstance(row: InstanceRow): void {
|
|
67
69
|
db.query(
|
|
68
70
|
`INSERT INTO instances
|
|
69
|
-
(id, name, source_path, workspace_path, host_port, container_id, remote_workspace_folder, status, error, created_at, bridge_token, remote_user, image_source, avatar, mode, terminal_split)
|
|
70
|
-
VALUES ($id, $name, $source_path, $workspace_path, $host_port, $container_id, $remote_workspace_folder, $status, $error, $created_at, $bridge_token, $remote_user, $image_source, $avatar, $mode, $terminal_split)`
|
|
71
|
+
(id, name, source_path, workspace_path, host_port, container_id, remote_workspace_folder, status, error, created_at, bridge_token, remote_user, image_source, avatar, mode, terminal_split, config_migrated)
|
|
72
|
+
VALUES ($id, $name, $source_path, $workspace_path, $host_port, $container_id, $remote_workspace_folder, $status, $error, $created_at, $bridge_token, $remote_user, $image_source, $avatar, $mode, $terminal_split, $config_migrated)`
|
|
71
73
|
).run({
|
|
72
74
|
$id: row.id,
|
|
73
75
|
$name: row.name,
|
|
@@ -84,7 +86,8 @@ export function insertInstance(row: InstanceRow): void {
|
|
|
84
86
|
$image_source: row.image_source,
|
|
85
87
|
$avatar: row.avatar,
|
|
86
88
|
$mode: row.mode,
|
|
87
|
-
$terminal_split: row.terminal_split
|
|
89
|
+
$terminal_split: row.terminal_split,
|
|
90
|
+
$config_migrated: row.config_migrated
|
|
88
91
|
});
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -147,6 +150,7 @@ const UPDATABLE_COLUMNS = [
|
|
|
147
150
|
'remote_user',
|
|
148
151
|
'image_source',
|
|
149
152
|
'terminal_split',
|
|
153
|
+
'config_migrated',
|
|
150
154
|
// Reassigned when a rebuild finds the recorded port taken over on the host.
|
|
151
155
|
'host_port'
|
|
152
156
|
] as const;
|