codebay 0.10.1 → 0.11.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 (101) hide show
  1. package/.mochi/manifest.json +199 -173
  2. package/.mochi/svelte-client/HydratableIsland-dammyce5.js +1 -0
  3. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-v65ctd99.js +2 -0
  4. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-htjfv8q9.js +14 -0
  5. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-v07seqge.js +1 -0
  6. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-hj9yvqc4.js +38 -0
  7. package/.mochi/svelte-client/chunk-0zkdsrth.js +2 -0
  8. package/.mochi/svelte-client/{chunk-bn731rda.js → chunk-566rqq4c.js} +2 -2
  9. package/.mochi/svelte-client/chunk-79aj59e0.js +2 -0
  10. package/.mochi/svelte-client/{chunk-wvtkwfsh.js → chunk-czkxd74n.js} +1 -1
  11. package/.mochi/svelte-client/{chunk-p20tkpde.js → chunk-hxydjtda.js} +1 -1
  12. package/.mochi/svelte-client/chunk-qx1defbm.js +2 -0
  13. package/.mochi/svelte-client/chunk-r3wxmmc0.js +2 -0
  14. package/.mochi/svelte-client/{chunk-j72r8pkk.js → chunk-vs6pd2b4.js} +2 -2
  15. package/.mochi/svelte-client/{chunk-t0ap7q89.js → chunk-wjagstre.js} +1 -1
  16. package/.mochi/svelte-client/{chunk-0fjx3z3r.js → chunk-y9yy1004.js} +1 -1
  17. package/.mochi/svelte-compile/{App-59cdj86f.server.js → App-mzn3dqjm.server.js} +182 -72
  18. package/.mochi/svelte-compile/{Avatars-fmagfyj2.server.js → Avatars-c0k5jx8q.server.js} +3 -6
  19. package/.mochi/svelte-compile/{Instance-s1gqwzmx.server.js → Instance-hj76qnha.server.js} +4 -4
  20. package/.mochi/svelte-compile/{Settings-k447mt0x.server.js → Settings-tvc5yp08.server.js} +39 -28
  21. package/.mochi/svelte-compile/chunk-ed46m58e.js +22 -0
  22. package/.mochi/svelte-compile/{chunk-th2dbqvc.js → chunk-gz6hv1h5.js} +5 -1
  23. package/.mochi/svelte-compile/chunk-zd49axkf.js +65 -0
  24. package/.mochi/svelte-css/{AppBar-3bxinv4pabv8l.css → AppBar-1gtu2me3fni8e.css} +1 -1
  25. package/.mochi/svelte-css/AvatarEditor-1mjvec6bwdkv8.css +1 -0
  26. package/.mochi/svelte-css/{AvatarsView-2llo48w0wt0xn.css → AvatarsView-26ya4ep5n1ze7.css} +1 -1
  27. package/.mochi/svelte-css/Button-208u50wkms8ya.css +1 -0
  28. package/.mochi/svelte-css/CoinButton-2727devjtsn10.css +1 -0
  29. package/.mochi/svelte-css/CredMenu-7qowd1oxujet.css +1 -0
  30. package/.mochi/svelte-css/DashboardView-17a0b8epjurhb.css +1 -0
  31. package/.mochi/svelte-css/FolderBrowser-i0uj58dph27f.css +1 -0
  32. package/.mochi/svelte-css/HealthBox-r0giuhjn2wj4.css +1 -0
  33. package/.mochi/svelte-css/IdeBar-1mtgemqfuvqzi.css +1 -0
  34. package/.mochi/svelte-css/InstanceCard-25z7nas7y98zk.css +1 -0
  35. package/.mochi/svelte-css/InstanceFilter-2vglmyy0yrbbc.css +1 -0
  36. package/.mochi/svelte-css/InstanceView-oozmmctp8m3p.css +1 -0
  37. package/.mochi/svelte-css/SettingsCog-1jdeprd3ilczz.css +1 -0
  38. package/.mochi/svelte-css/{SettingsView-n3oy75uonzp2.css → SettingsView-bccw34agd2bj.css} +1 -1
  39. package/.mochi/svelte-css/StatusBadge-2eisdtnrsnfdc.css +1 -0
  40. package/.mochi/svelte-css/TerminalPane-2ywc86rhtmr9l.css +1 -0
  41. package/.mochi/svelte-css/ThemePicker-3fw4synubier0.css +1 -0
  42. package/migrations/0008_config_migrated.sql +1 -0
  43. package/package.json +8 -8
  44. package/src/components/AppBar.svelte +2 -2
  45. package/src/components/AppShell.svelte +71 -7
  46. package/src/components/AvatarEditor.svelte +10 -10
  47. package/src/components/AvatarsView.svelte +0 -0
  48. package/src/components/Button.svelte +13 -9
  49. package/src/components/CoinButton.svelte +2 -2
  50. package/src/components/CredMenu.svelte +8 -6
  51. package/src/components/DashboardView.svelte +2 -2
  52. package/src/components/FolderBrowser.svelte +39 -36
  53. package/src/components/HealthBox.svelte +5 -4
  54. package/src/components/IdeBar.svelte +286 -112
  55. package/src/components/InstanceCard.svelte +7 -7
  56. package/src/components/InstanceFilter.svelte +6 -6
  57. package/src/components/InstanceView.svelte +27 -21
  58. package/src/components/SettingsCog.svelte +4 -3
  59. package/src/components/SettingsView.svelte +7 -11
  60. package/src/components/StatusBadge.svelte +8 -6
  61. package/src/components/TerminalPane.svelte +19 -7
  62. package/src/components/ThemePicker.svelte +150 -8
  63. package/src/components/TopBar.svelte +1 -1
  64. package/src/components/UiShowcase.svelte +300 -64
  65. package/src/lib/db.server.ts +7 -3
  66. package/src/lib/devcontainer.server.ts +181 -33
  67. package/src/lib/docker.server.ts +10 -0
  68. package/src/lib/git.server.ts +28 -0
  69. package/src/lib/instances.server.ts +47 -6
  70. package/src/lib/tab-nav.ts +30 -0
  71. package/src/routes.ts +12 -0
  72. package/src/shell.html +56 -23
  73. package/src/theme.ts +8 -1
  74. package/src/toast.ts +9 -0
  75. package/src/types.ts +7 -0
  76. package/.mochi/svelte-client/HydratableIsland-aebn1fa1.js +0 -1
  77. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-rx6arrv5.js +0 -2
  78. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-jpjdmgpe.js +0 -14
  79. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-c9y47dat.js +0 -1
  80. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-d61feq89.js +0 -38
  81. package/.mochi/svelte-client/chunk-8xvr02wh.js +0 -2
  82. package/.mochi/svelte-client/chunk-9143wy3j.js +0 -2
  83. package/.mochi/svelte-client/chunk-k37wj1jm.js +0 -2
  84. package/.mochi/svelte-client/chunk-pc0fm37c.js +0 -2
  85. package/.mochi/svelte-compile/chunk-3t7v4hk0.js +0 -52
  86. package/.mochi/svelte-compile/chunk-gkjgybb2.js +0 -15
  87. package/.mochi/svelte-css/AvatarEditor-2mdq8ss346drw.css +0 -1
  88. package/.mochi/svelte-css/Button-oharop2xz6am.css +0 -1
  89. package/.mochi/svelte-css/CoinButton-1rej3sh3tkjud.css +0 -1
  90. package/.mochi/svelte-css/CredMenu-9hwgeptp7crb.css +0 -1
  91. package/.mochi/svelte-css/DashboardView-1cy4xpqhja9sx.css +0 -1
  92. package/.mochi/svelte-css/FolderBrowser-2pxp9z1o7zixk.css +0 -1
  93. package/.mochi/svelte-css/HealthBox-er2her0jukwl.css +0 -1
  94. package/.mochi/svelte-css/IdeBar-3bgr3m5z0lpeg.css +0 -1
  95. package/.mochi/svelte-css/InstanceCard-3vnltxnbxkvay.css +0 -1
  96. package/.mochi/svelte-css/InstanceFilter-3q6x0jt0hcgpz.css +0 -1
  97. package/.mochi/svelte-css/InstanceView-1pd7j16bd2b16.css +0 -1
  98. package/.mochi/svelte-css/SettingsCog-jftyadvo64qo.css +0 -1
  99. package/.mochi/svelte-css/StatusBadge-3mq0j7z6aq4j.css +0 -1
  100. package/.mochi/svelte-css/TerminalPane-3bwgk6ulzq6b3.css +0 -1
  101. 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,72 @@
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
- // The swatch fill reads var(--token) so it auto-syncs with shell.html; `hex` is
23
- // only a label and always shows the light-theme value.
24
- const palette: { group: string; swatches: { token: string; hex: string; border?: boolean }[] }[] =
25
- [
26
- {
27
- group: 'Surfaces',
28
- swatches: [
29
- { token: '--bg', hex: '#d7d8d2', border: true },
30
- { token: '--bg-card', hex: '#e7e8e2', border: true },
31
- { token: '--screen-bg', hex: '#0d0e0a' }
32
- ]
33
- },
34
- {
35
- group: 'Ink / text',
36
- swatches: [
37
- { token: '--ink', hex: '#14150f' },
38
- { token: '--ink-soft', hex: '#45463e' },
39
- { token: '--ink-faint', hex: '#7d7e74' }
40
- ]
41
- },
42
- {
43
- group: 'Rules',
44
- swatches: [
45
- { token: '--rule', hex: '#20211a' },
46
- { token: '--rule-soft', hex: '#b9bab2', border: true }
47
- ]
48
- },
49
- {
50
- group: 'Semantic',
51
- swatches: [
52
- { token: '--warn-bg', hex: '#e4d98f', border: true },
53
- { token: '--warn-line', hex: '#9c8729' },
54
- { token: '--warn-ink', hex: '#463b08' },
55
- { token: '--ok-bg', hex: '#8fbf6e' },
56
- { token: '--ok-line', hex: '#4c7a2e' },
57
- { token: '--ok-ink', hex: '#1c3110' },
58
- { token: '--danger', hex: '#b23a2e' },
59
- { token: '--danger-soft', hex: '#e3c6c1', border: true },
60
- { token: '--info', hex: '#388bfd' }
61
- ]
62
- },
63
- {
64
- group: 'Attention',
65
- swatches: [
66
- { token: '--attn-done', hex: '#22c55e' },
67
- { token: '--attn-waiting', hex: '#f59e0b' }
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
+ { group: 'Attention', tokens: ['--attn-done', '--attn-waiting', '--switch-on-bg'] }
49
+ ];
50
+
51
+ const chips: Record<string, HTMLElement> = {};
52
+ let hexes = $state<Record<string, string>>({});
53
+
54
+ /** `rgb(r g b / a)` or `rgba(r, g, b, a)` -> `#rrggbb`, with any alpha spelled out. */
55
+ function toHex(value: string): string {
56
+ const parts = value.match(/[\d.]+/g);
57
+ if (!parts || parts.length < 3) return value;
58
+ const hex = parts
59
+ .slice(0, 3)
60
+ .map((n) => Math.round(Number(n)).toString(16).padStart(2, '0'))
61
+ .join('');
62
+ const alpha = parts.length > 3 ? Number(parts[3]) : 1;
63
+ return alpha < 1 ? `#${hex} ${Math.round(alpha * 100)}%` : `#${hex}`;
64
+ }
65
+
66
+ function readHexes() {
67
+ hexes = Object.fromEntries(
68
+ Object.entries(chips).map(([token, node]) => [
69
+ token,
70
+ toHex(getComputedStyle(node).backgroundColor)
71
+ ])
72
+ );
73
+ }
74
+
75
+ onMount(() => {
76
+ readHexes();
77
+ // An explicit pick mutates data-theme; "auto" only moves when the OS does.
78
+ const obs = new MutationObserver(readHexes);
79
+ obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
80
+ const scheme = window.matchMedia('(prefers-color-scheme: dark)');
81
+ scheme.addEventListener('change', readHexes);
82
+ return () => {
83
+ obs.disconnect();
84
+ scheme.removeEventListener('change', readHexes);
85
+ };
86
+ });
71
87
 
72
88
  let avatarScale = $state(8);
73
89
  let avatarName = $state('demo-instance');
@@ -137,11 +153,103 @@
137
153
 
138
154
  let branchName = $state('main');
139
155
 
156
+ // The dashboard's headline surface — it composes Avatar, StatusBadge, PortsBox,
157
+ // BranchBox and Button, so it's the one place their interplay is reviewable.
158
+ let cardStatus = $state<Instance['status']>('running');
159
+ let cardAttention = $state<Instance['attention']>('waiting');
160
+ let cardPending = $state<'' | 'start' | 'stop' | 'delete' | 'rebuild'>('');
161
+ let cardMode = $state<Instance['mode']>('ide');
162
+ let cardBranch = $state(true);
163
+ let cardEditing = $state(false);
164
+ let cardEditingName = $state('demo-instance');
165
+ const cardActions = ['', 'start', 'stop', 'delete', 'rebuild'] as const;
166
+ const demoInstance = $derived<Instance>({
167
+ id: 'demo',
168
+ name: 'demo-instance',
169
+ source_path: '/home/you/code/demo-instance',
170
+ workspace_path: '/home/you/.codebay/instances/demo',
171
+ host_port: 8001,
172
+ container_id: 'abc123',
173
+ remote_workspace_folder: '/workspaces/demo-instance',
174
+ status: cardStatus,
175
+ error: cardStatus === 'error' ? 'devcontainer up failed: exit code 1' : null,
176
+ created_at: 0,
177
+ image_source: 'local',
178
+ avatar: avatars[0]!.name,
179
+ mode: cardMode,
180
+ terminal_split: 0,
181
+ git_branch: cardBranch ? branchName : null,
182
+ attention: cardAttention,
183
+ forwarded_ports: demoPorts
184
+ });
185
+
140
186
  const statuses: Instance['status'][] = ['creating', 'running', 'stopped', 'error'];
141
187
  let badgeStatus = $state<Instance['status']>('error');
188
+
189
+ // The IDE tab strip only ever renders against running instances, and this
190
+ // environment may have no Docker at all — so the demo fabricates them.
191
+ const TAB_NAMES = [
192
+ 'orion',
193
+ 'meridian',
194
+ 'atlas',
195
+ 'vesper',
196
+ 'cobalt',
197
+ 'juniper',
198
+ 'halcyon',
199
+ 'zenith',
200
+ 'perigee',
201
+ 'lumen'
202
+ ];
203
+ let tabCount = $state(4);
204
+ let tabActive = $state(0);
205
+ let tabAttention = $state(true);
206
+ let tabMixModes = $state(true);
207
+ let tabLongNames = $state(false);
208
+ let tabEditingId = $state<string | null>(null);
209
+ let tabEditingName = $state('');
210
+ // Renames are applied for real, so the inline editor is inspectable end to end.
211
+ let tabRenames = $state<Record<string, string>>({});
212
+
213
+ const demoTabs = $derived(
214
+ Array.from({ length: tabCount }, (_, i): Instance => {
215
+ const id = `demo-tab-${i}`;
216
+ const base = TAB_NAMES[i]!;
217
+ return {
218
+ id,
219
+ name: tabRenames[id] ?? (tabLongNames ? `${base}-refactor-spike-2026` : base),
220
+ source_path: `/home/demo/${base}`,
221
+ workspace_path: `/data/instances/${id}`,
222
+ host_port: 8001 + i,
223
+ container_id: `container-${i}`,
224
+ remote_workspace_folder: null,
225
+ status: 'running',
226
+ error: null,
227
+ created_at: 0,
228
+ image_source: 'local',
229
+ avatar: avatars[i % avatars.length]!.name,
230
+ mode: tabMixModes && i % 2 === 1 ? 'terminal' : 'ide',
231
+ terminal_split: 0,
232
+ git_branch: 'main',
233
+ attention: null,
234
+ forwarded_ports: []
235
+ };
236
+ })
237
+ );
238
+ // Clamped, since dragging the count down can strand the active index past the end.
239
+ const demoActiveIndex = $derived(Math.min(tabActive, tabCount - 1));
240
+ const demoActiveId = $derived(demoTabs[demoActiveIndex]?.id ?? '');
241
+ // Alternating states so both LED colors are comparable side by side.
242
+ const demoAttention = $derived(
243
+ Object.fromEntries(
244
+ demoTabs.map((inst, i) => [
245
+ inst.id,
246
+ tabAttention && i !== demoActiveIndex ? (i % 2 === 0 ? 'done' : 'waiting') : null
247
+ ])
248
+ ) as Record<string, 'done' | 'waiting' | null>
249
+ );
142
250
  </script>
143
251
 
144
- <Toaster />
252
+ <Toaster toastOptions={TOAST_OPTIONS} />
145
253
 
146
254
  <TopBar
147
255
  auth={providers}
@@ -166,16 +274,20 @@
166
274
  <span class="palette-hint">Design tokens — tap to expand</span>
167
275
  </summary>
168
276
  <div class="palette">
169
- {#each palette as { group, swatches } (group)}
277
+ {#each palette as { group, tokens } (group)}
170
278
  <div class="swatch-group">
171
279
  <h3>{group}</h3>
172
280
  <div class="swatches">
173
- {#each swatches as { token, hex, border } (token)}
281
+ {#each tokens as token (token)}
174
282
  <figure class="swatch">
175
- <div class="chip" class:bordered={border} style="background: var({token});"></div>
283
+ <div
284
+ class="chip"
285
+ bind:this={chips[token]}
286
+ style="background: var({token});"
287
+ ></div>
176
288
  <figcaption>
177
289
  <code>{token}</code>
178
- <span>{hex}</span>
290
+ <span>{hexes[token] ?? '…'}</span>
179
291
  </figcaption>
180
292
  </figure>
181
293
  {/each}
@@ -185,6 +297,64 @@
185
297
  </div>
186
298
  </details>
187
299
 
300
+ <ComponentDemo title="InstanceCard">
301
+ <!-- The card renders an <li>, and .card-grid mirrors the dashboard's own
302
+ column so its width and hover lift match what ships. -->
303
+ <ul class="card-grid">
304
+ <InstanceCard
305
+ instance={demoInstance}
306
+ editing={cardEditing}
307
+ bind:editingName={cardEditingName}
308
+ pending={cardPending || null}
309
+ onact={(a) => toast(`${a} (demo)`)}
310
+ onstartrename={() => (cardEditing = true)}
311
+ oncommitrename={() => (cardEditing = false)}
312
+ oncancelrename={() => (cardEditing = false)}
313
+ />
314
+ </ul>
315
+ {#snippet controls()}
316
+ <label>
317
+ <span>status</span>
318
+ <select bind:value={cardStatus}>
319
+ {#each statuses as s (s)}
320
+ <option value={s}>{s}</option>
321
+ {/each}
322
+ </select>
323
+ </label>
324
+ <label>
325
+ <span>attention</span>
326
+ <select bind:value={cardAttention}>
327
+ <option value={null}>none</option>
328
+ <option value="done">done</option>
329
+ <option value="waiting">waiting</option>
330
+ </select>
331
+ </label>
332
+ <label>
333
+ <span>pending (busy overlay)</span>
334
+ <select bind:value={cardPending}>
335
+ {#each cardActions as a (a)}
336
+ <option value={a}>{a || 'none'}</option>
337
+ {/each}
338
+ </select>
339
+ </label>
340
+ <label>
341
+ <span>mode</span>
342
+ <select bind:value={cardMode}>
343
+ <option value="ide">ide</option>
344
+ <option value="terminal">terminal</option>
345
+ </select>
346
+ </label>
347
+ <label class="inline">
348
+ <input type="checkbox" bind:checked={cardBranch} />
349
+ <span>git branch</span>
350
+ </label>
351
+ <label class="inline">
352
+ <input type="checkbox" bind:checked={cardEditing} />
353
+ <span>renaming</span>
354
+ </label>
355
+ {/snippet}
356
+ </ComponentDemo>
357
+
188
358
  <ComponentDemo title="Avatar">
189
359
  <Avatar name={avatarName} scale={avatarScale} art={avatarArt} />
190
360
  {#snippet controls()}
@@ -370,6 +540,57 @@
370
540
  {/snippet}
371
541
  </ComponentDemo>
372
542
 
543
+ <ComponentDemo title="IdeBar">
544
+ <div class="ide-bar-demo">
545
+ <IdeBar
546
+ running={demoTabs}
547
+ active={demoActiveId}
548
+ attention={demoAttention}
549
+ editingId={tabEditingId}
550
+ bind:editingName={tabEditingName}
551
+ onreload={() => toast('Reload editor (demo)')}
552
+ onselect={(id) => (tabActive = demoTabs.findIndex((t) => t.id === id))}
553
+ onstartrename={(inst) => {
554
+ tabEditingId = inst.id;
555
+ tabEditingName = inst.name;
556
+ }}
557
+ oncommitrename={(id) => {
558
+ const name = tabEditingName.trim();
559
+ if (name) tabRenames[id] = name;
560
+ tabEditingId = null;
561
+ }}
562
+ oncancelrename={() => (tabEditingId = null)}
563
+ />
564
+ </div>
565
+ <p class="demo-hint">
566
+ Click to select, double-click to rename. Arrow keys / Home / End move between tabs once one
567
+ has focus. The Alt+1–9 jump lives in AppShell rather than here, since it also installs
568
+ itself into the editor iframes — so it is inert in this demo.
569
+ </p>
570
+ {#snippet controls()}
571
+ <label>
572
+ <span>tabs ({tabCount})</span>
573
+ <input type="range" min="1" max="10" bind:value={tabCount} />
574
+ </label>
575
+ <label>
576
+ <span>active ({demoActiveIndex})</span>
577
+ <input type="range" min="0" max={tabCount - 1} bind:value={tabActive} />
578
+ </label>
579
+ <label>
580
+ <span>attention</span>
581
+ <input type="checkbox" bind:checked={tabAttention} />
582
+ </label>
583
+ <label>
584
+ <span>mixed modes</span>
585
+ <input type="checkbox" bind:checked={tabMixModes} />
586
+ </label>
587
+ <label>
588
+ <span>long names</span>
589
+ <input type="checkbox" bind:checked={tabLongNames} />
590
+ </label>
591
+ {/snippet}
592
+ </ComponentDemo>
593
+
373
594
  <ComponentDemo title="Brand" note="No props — static branding.">
374
595
  <Brand />
375
596
  </ComponentDemo>
@@ -386,7 +607,6 @@
386
607
  max-width: 740px;
387
608
  margin: 0 auto;
388
609
  padding: 32px;
389
- background: var(--bg);
390
610
  color: var(--ink);
391
611
  }
392
612
  header {
@@ -414,8 +634,8 @@
414
634
  border-bottom: 1px solid var(--ink);
415
635
  }
416
636
  .gallery-link:hover {
417
- background: var(--ink);
418
- color: var(--bg);
637
+ background: var(--fill);
638
+ color: var(--fill-ink);
419
639
  }
420
640
  .grid {
421
641
  display: flex;
@@ -460,12 +680,30 @@
460
680
  border: 1px solid var(--rule-soft);
461
681
  overflow: hidden;
462
682
  }
683
+ /* Matches the dashboard grid's column so the card previews at its real width. */
684
+ .card-grid {
685
+ list-style: none;
686
+ margin: 0;
687
+ padding: 0;
688
+ width: min(360px, 100%);
689
+ }
463
690
  .badge-row {
464
691
  display: flex;
465
692
  flex-wrap: wrap;
466
693
  align-items: center;
467
694
  gap: 10px;
468
695
  }
696
+ /* The bar spans the full IDE viewport in situ, so give it the whole stage here. */
697
+ .ide-bar-demo {
698
+ width: 100%;
699
+ border: 1px solid var(--rule-soft);
700
+ }
701
+ .demo-hint {
702
+ margin: 0;
703
+ font-family: var(--font-mono);
704
+ font-size: 11px;
705
+ color: var(--ink-faint);
706
+ }
469
707
  .presets {
470
708
  display: flex;
471
709
  gap: 6px;
@@ -478,13 +716,14 @@
478
716
  letter-spacing: 0.06em;
479
717
  color: var(--ink);
480
718
  background: var(--bg-card);
481
- border: 1px solid var(--ink);
719
+ border: 1px solid var(--edge);
482
720
  padding: 5px 0;
483
721
  cursor: pointer;
484
722
  }
485
723
  .presets button:hover {
486
- background: var(--ink);
487
- color: var(--bg);
724
+ background: var(--fill);
725
+ border-color: var(--fill);
726
+ color: var(--fill-ink);
488
727
  }
489
728
 
490
729
  .palette-card {
@@ -550,10 +789,7 @@
550
789
  }
551
790
  .swatch .chip {
552
791
  height: 44px;
553
- border: 1px solid transparent;
554
- }
555
- .swatch .chip.bordered {
556
- border-color: var(--rule-soft);
792
+ border: 1px solid var(--rule-soft);
557
793
  }
558
794
  .swatch figcaption {
559
795
  display: flex;
@@ -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;