codebay 0.10.0 → 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 +193 -167
  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 +211 -38
  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 +117 -18
  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
@@ -23,6 +23,7 @@
23
23
  import Boxes from '@lucide/svelte/icons/boxes';
24
24
  import Puzzle from '@lucide/svelte/icons/puzzle';
25
25
  import { Toaster } from 'svelte-french-toast';
26
+ import { TOAST_OPTIONS } from '../toast.ts';
26
27
  import { flushSync } from 'svelte';
27
28
  import { enhance } from 'mochi-framework';
28
29
  import type { MochiEnhanceOptions } from 'mochi-framework';
@@ -2189,12 +2190,7 @@
2189
2190
  </div>
2190
2191
 
2191
2192
  <!-- Settings renders outside AppShell, which hosts the app-wide Toaster. -->
2192
- <Toaster
2193
- toastOptions={{
2194
- style:
2195
- 'border:1px solid var(--ink); background:var(--bg-card); color:var(--ink); box-shadow:4px 4px 0 var(--ink); font-family:var(--font-mono); font-size:13px;'
2196
- }}
2197
- />
2193
+ <Toaster toastOptions={TOAST_OPTIONS} />
2198
2194
 
2199
2195
  <style>
2200
2196
  .page {
@@ -2437,7 +2433,7 @@
2437
2433
  .mode-toggle {
2438
2434
  display: flex;
2439
2435
  flex: none;
2440
- border: 1px solid var(--ink);
2436
+ border: 1px solid var(--edge);
2441
2437
  }
2442
2438
  .mode-btn {
2443
2439
  display: inline-flex;
@@ -2455,11 +2451,11 @@
2455
2451
  cursor: pointer;
2456
2452
  }
2457
2453
  .mode-btn + .mode-btn {
2458
- border-left: 1px solid var(--ink);
2454
+ border-left: 1px solid var(--edge);
2459
2455
  }
2460
2456
  .mode-btn.active {
2461
- background: var(--ink);
2462
- color: var(--bg);
2457
+ background: var(--fill);
2458
+ color: var(--fill-ink);
2463
2459
  }
2464
2460
  .mode-btn:disabled {
2465
2461
  cursor: not-allowed;
@@ -2589,7 +2585,7 @@
2589
2585
  border-color: var(--danger);
2590
2586
  }
2591
2587
  .var-remove:disabled {
2592
- opacity: 0.4;
2588
+ opacity: var(--dim);
2593
2589
  cursor: not-allowed;
2594
2590
  }
2595
2591
  .var-empty {
@@ -22,12 +22,13 @@
22
22
  text-transform: uppercase;
23
23
  letter-spacing: 0.1em;
24
24
  padding: 3px 7px;
25
- border: 1px solid var(--ink);
25
+ border: 1px solid var(--edge);
26
26
  white-space: nowrap;
27
27
  }
28
28
  .status.running {
29
- background: var(--ink);
30
- color: var(--bg);
29
+ background: var(--fill);
30
+ color: var(--fill-ink);
31
+ border-color: var(--fill);
31
32
  }
32
33
  .status.stopped {
33
34
  background: transparent;
@@ -35,8 +36,9 @@
35
36
  border-color: var(--ink-faint);
36
37
  }
37
38
  .status.creating {
38
- background: var(--ink);
39
- color: var(--bg);
39
+ background: var(--fill);
40
+ color: var(--fill-ink);
41
+ border-color: var(--fill);
40
42
  animation: lcd-blink 1.1s steps(1) infinite;
41
43
  }
42
44
  .status.error {
@@ -48,7 +50,7 @@
48
50
  @keyframes lcd-blink {
49
51
  50% {
50
52
  background: transparent;
51
- color: var(--ink);
53
+ color: var(--fill);
52
54
  }
53
55
  }
54
56
  </style>
@@ -129,16 +129,24 @@
129
129
  }
130
130
  }
131
131
 
132
+ /* Reads the *resolved* background/color off `.term` rather than the tokens behind
133
+ them: getPropertyValue('--screen-bg') hands back the literal "light-dark(…)"
134
+ token stream, which xterm can't parse and silently drops on the floor. */
132
135
  function applyTheme() {
133
- if (!term) return;
134
- const cs = getComputedStyle(document.documentElement);
135
- const bg = cs.getPropertyValue('--bg').trim() || '#1a1a1a';
136
- const fg = cs.getPropertyValue('--ink').trim() || '#e6e6e6';
136
+ if (!term || !el) return;
137
+ const cs = getComputedStyle(el);
138
+ const bg = cs.backgroundColor || '#0d0e0a';
139
+ const fg = cs.color || '#d8d9cf';
137
140
  term.options.theme = { background: bg, foreground: fg, cursor: fg, cursorAccent: bg };
138
141
  }
139
142
 
140
143
  onMount(() => {
141
144
  let themeObs: MutationObserver | undefined;
145
+ // In "auto" there is no data-theme attribute to mutate, so the OS preference
146
+ // flipping is the only signal the mutation observer below would never see.
147
+ const scheme = window.matchMedia('(prefers-color-scheme: dark)');
148
+ const onScheme = () => applyTheme();
149
+ scheme.addEventListener('change', onScheme);
142
150
  (async () => {
143
151
  const [{ Terminal }, { FitAddon }] = await Promise.all([
144
152
  import('@xterm/xterm'),
@@ -178,6 +186,7 @@
178
186
  disposed = true;
179
187
  sizeObs.disconnect();
180
188
  themeObs?.disconnect();
189
+ scheme.removeEventListener('change', onScheme);
181
190
  if (retry) clearTimeout(retry);
182
191
  try {
183
192
  ws?.close();
@@ -212,10 +221,13 @@
212
221
  </div>
213
222
 
214
223
  <style>
224
+ /* Also the source `applyTheme` reads xterm's colors back out of — resolved
225
+ standard properties, since a custom property's own value is unresolved. */
215
226
  .term {
216
227
  width: 100%;
217
228
  height: 100%;
218
- background: var(--bg);
229
+ background: var(--screen-bg);
230
+ color: var(--screen-ink);
219
231
  padding: 6px 8px;
220
232
  box-sizing: border-box;
221
233
  }
@@ -263,8 +275,8 @@
263
275
  .overlay :global(button:focus-visible),
264
276
  .overlay :global(button[aria-pressed='true']) {
265
277
  opacity: 1;
266
- background: var(--ink);
267
- color: var(--bg);
278
+ background: var(--fill);
279
+ color: var(--fill-ink);
268
280
  }
269
281
  @media (prefers-reduced-motion: reduce) {
270
282
  .overlay :global(button) {
@@ -3,8 +3,15 @@
3
3
  import Moon from '@lucide/svelte/icons/moon';
4
4
  import Monitor from '@lucide/svelte/icons/monitor';
5
5
  import { getTheme, setTheme, applyTheme, type Theme } from '../theme.ts';
6
+ import { apiPost } from '../api.ts';
6
7
 
7
8
  let theme: Theme = $state(getTheme());
9
+ /** Which half of the CRT power-cycle is playing; null when idle. */
10
+ let phase = $state<'collapse' | 'restore' | null>(null);
11
+
12
+ // Must match the CSS below: the theme swap has to land while the tube is dark.
13
+ const COLLAPSE_MS = 320;
14
+ const RESTORE_MS = 380;
8
15
 
9
16
  const options: { value: Theme; label: string; icon: typeof Sun }[] = [
10
17
  { value: 'auto', label: 'Auto', icon: Monitor },
@@ -12,10 +19,29 @@
12
19
  { value: 'dark', label: 'Dark', icon: Moon }
13
20
  ];
14
21
 
15
- function select(value: Theme) {
16
- theme = value;
22
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
23
+
24
+ async function select(value: Theme) {
25
+ if (value === theme || phase) return;
26
+ // Persist first: an interrupted transition still lands the choice.
17
27
  setTheme(value);
28
+ // Settings is a popup, so the tabs behind it only repaint if the stream tells them to.
29
+ void apiPost('/api/settings/theme', { value }).catch(() => {});
30
+
31
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
32
+ theme = value;
33
+ applyTheme(value);
34
+ return;
35
+ }
36
+
37
+ phase = 'collapse';
38
+ await wait(COLLAPSE_MS);
39
+ // Swapped behind the black, so the repaint itself is never on screen.
40
+ theme = value;
18
41
  applyTheme(value);
42
+ phase = 'restore';
43
+ await wait(RESTORE_MS);
44
+ phase = null;
19
45
  }
20
46
  </script>
21
47
 
@@ -35,11 +61,19 @@
35
61
  {/each}
36
62
  </div>
37
63
 
64
+ {#if phase}
65
+ <div class="crt" class:collapse={phase === 'collapse'} class:restore={phase === 'restore'}>
66
+ <span class="bar top"></span>
67
+ <span class="bar bottom"></span>
68
+ <span class="line"></span>
69
+ </div>
70
+ {/if}
71
+
38
72
  <style>
39
73
  .picker {
40
74
  display: inline-flex;
41
75
  flex: none;
42
- border: 1px solid var(--ink);
76
+ border: 1px solid var(--edge);
43
77
  background: var(--bg-card);
44
78
  }
45
79
  .segment {
@@ -58,20 +92,128 @@
58
92
  cursor: pointer;
59
93
  }
60
94
  .segment + .segment {
61
- border-left: 1px solid var(--ink);
95
+ border-left: 1px solid var(--edge);
62
96
  }
63
97
  .segment:hover:not(.active) {
64
- background: var(--ink);
65
- color: var(--bg);
98
+ background: var(--fill);
99
+ color: var(--fill-ink);
66
100
  }
67
101
  .segment.active {
68
102
  font-weight: 700;
69
- background: var(--ink);
70
- color: var(--bg);
103
+ background: var(--fill);
104
+ color: var(--fill-ink);
71
105
  cursor: default;
72
106
  }
73
107
  .segment:focus-visible {
74
108
  outline: 2px solid var(--ink);
75
109
  outline-offset: -2px;
76
110
  }
111
+
112
+ /* Switching themes power-cycles the monitor. `position: fixed` is viewport-relative
113
+ only while no ancestor is transformed/filtered — giving one of Settings' wrappers
114
+ a transform would re-root this to that ancestor instead. */
115
+ .crt {
116
+ position: fixed;
117
+ inset: 0;
118
+ z-index: 9999;
119
+ pointer-events: none;
120
+ }
121
+ /* Black closing in from both edges, squeezing the picture toward the centre —
122
+ the deflection yoke losing the vertical sweep. */
123
+ .bar {
124
+ position: absolute;
125
+ left: 0;
126
+ right: 0;
127
+ height: 50%;
128
+ background: #000;
129
+ }
130
+ .bar.top {
131
+ top: 0;
132
+ transform-origin: top center;
133
+ }
134
+ .bar.bottom {
135
+ bottom: 0;
136
+ transform-origin: bottom center;
137
+ }
138
+ /* Tracks --screen-ink, so the tube warms back up in the phosphor colour of
139
+ whichever theme it is coming back as. */
140
+ .line {
141
+ position: absolute;
142
+ top: 50%;
143
+ left: 0;
144
+ right: 0;
145
+ height: 2px;
146
+ margin-top: -1px;
147
+ background: var(--screen-ink);
148
+ box-shadow: 0 0 14px 3px var(--screen-ink);
149
+ opacity: 0;
150
+ }
151
+
152
+ .crt.collapse .bar {
153
+ animation: crt-collapse 200ms cubic-bezier(0.7, 0, 0.84, 0) forwards;
154
+ }
155
+ .crt.collapse .line {
156
+ animation: crt-line-out 320ms linear forwards;
157
+ }
158
+ /* Held closed through the delay so the line can bloom before the picture opens. */
159
+ .crt.restore .bar {
160
+ animation: crt-open 260ms cubic-bezier(0.16, 1, 0.3, 1) 120ms both;
161
+ }
162
+ .crt.restore .line {
163
+ animation: crt-line-in 380ms linear both;
164
+ }
165
+
166
+ @keyframes crt-collapse {
167
+ from {
168
+ transform: scaleY(0);
169
+ }
170
+ to {
171
+ transform: scaleY(1);
172
+ }
173
+ }
174
+ @keyframes crt-open {
175
+ from {
176
+ transform: scaleY(1);
177
+ }
178
+ to {
179
+ transform: scaleY(0);
180
+ }
181
+ }
182
+ /* Picture gone: the residual charge flares as a line, pinches to a dot, dies. */
183
+ @keyframes crt-line-out {
184
+ 0%,
185
+ 55% {
186
+ opacity: 0;
187
+ transform: scaleX(1);
188
+ }
189
+ 62%,
190
+ 75% {
191
+ opacity: 1;
192
+ transform: scaleX(1);
193
+ }
194
+ 100% {
195
+ opacity: 0;
196
+ transform: scaleX(0.015);
197
+ }
198
+ }
199
+ /* Dot blooms back into a line, then the picture opens around it. */
200
+ @keyframes crt-line-in {
201
+ 0% {
202
+ opacity: 0;
203
+ transform: scaleX(0.015);
204
+ }
205
+ 12% {
206
+ opacity: 1;
207
+ transform: scaleX(0.015);
208
+ }
209
+ 32%,
210
+ 55% {
211
+ opacity: 1;
212
+ transform: scaleX(1);
213
+ }
214
+ 100% {
215
+ opacity: 0;
216
+ transform: scaleX(1);
217
+ }
218
+ }
77
219
  </style>
@@ -66,7 +66,7 @@
66
66
  {creating ? 'Creating…' : 'New instance'}
67
67
  </Button>
68
68
  <Button
69
- variant="primary"
69
+ variant="default"
70
70
  size="sm"
71
71
  square
72
72
  icon={altMode === 'terminal' ? Terminal : LayoutTemplate}