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.
Files changed (101) hide show
  1. package/.mochi/manifest.json +196 -170
  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-32j12hyg8v9u0.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 +28 -22
  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 +303 -64
  65. package/src/lib/db.server.ts +7 -3
  66. package/src/lib/devcontainer.server.ts +187 -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 +49 -6
  70. package/src/lib/tab-nav.ts +30 -0
  71. package/src/routes.ts +12 -0
  72. package/src/shell.html +60 -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,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
- // 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
+ {
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, swatches } (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 swatches as { token, hex, border } (token)}
284
+ {#each tokens as token (token)}
174
285
  <figure class="swatch">
175
- <div class="chip" class:bordered={border} style="background: var({token});"></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>{hex}</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(--ink);
418
- color: var(--bg);
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(--ink);
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(--ink);
487
- color: var(--bg);
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 transparent;
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;
@@ -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;