codebay 0.9.1 → 0.10.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 (77) hide show
  1. package/.mochi/manifest.json +213 -146
  2. package/.mochi/svelte-client/HydratableIsland-aebn1fa1.js +1 -0
  3. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-rx6arrv5.js +2 -0
  4. package/.mochi/svelte-client/{_hydrate-AvatarsView_257s9yq1wwr4f-92hepzby.js → _hydrate-AvatarsView_257s9yq1wwr4f-jpjdmgpe.js} +1 -1
  5. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-c9y47dat.js +1 -0
  6. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-d61feq89.js +38 -0
  7. package/.mochi/svelte-client/{chunk-2j95sv83.js → chunk-0fjx3z3r.js} +1 -1
  8. package/.mochi/svelte-client/chunk-8xvr02wh.js +2 -0
  9. package/.mochi/svelte-client/{chunk-m497q88s.js → chunk-9143wy3j.js} +2 -2
  10. package/.mochi/svelte-client/{chunk-v65n8th6.js → chunk-bn731rda.js} +1 -1
  11. package/.mochi/svelte-client/{chunk-a2psjmrm.js → chunk-j72r8pkk.js} +2 -2
  12. package/.mochi/svelte-client/chunk-k37wj1jm.js +2 -0
  13. package/.mochi/svelte-client/{chunk-mpv3qx28.js → chunk-p20tkpde.js} +1 -1
  14. package/.mochi/svelte-client/chunk-pc0fm37c.js +2 -0
  15. package/.mochi/svelte-client/{chunk-6rbs0mhx.js → chunk-t0ap7q89.js} +1 -1
  16. package/.mochi/svelte-client/{chunk-rs41by6w.js → chunk-wvtkwfsh.js} +1 -1
  17. package/.mochi/svelte-compile/{App-7vjcp38v.server.js → App-59cdj86f.server.js} +167 -103
  18. package/.mochi/svelte-compile/{Avatars-e2cfc95t.server.js → Avatars-fmagfyj2.server.js} +1 -1
  19. package/.mochi/svelte-compile/{Instance-h78n2dsv.server.js → Instance-s1gqwzmx.server.js} +16 -10
  20. package/.mochi/svelte-compile/{Settings-tav2asn7.server.js → Settings-k447mt0x.server.js} +540 -88
  21. package/.mochi/svelte-compile/{chunk-brjv9pyd.js → chunk-3t7v4hk0.js} +1 -7
  22. package/.mochi/svelte-compile/chunk-gkjgybb2.js +15 -0
  23. package/.mochi/svelte-compile/{chunk-m8ygpyhr.js → chunk-th2dbqvc.js} +20 -32
  24. package/.mochi/svelte-css/Button-oharop2xz6am.css +1 -0
  25. package/.mochi/svelte-css/IdeBar-3bgr3m5z0lpeg.css +1 -0
  26. package/.mochi/svelte-css/{InstanceView-3vzow3ttxzlh3.css → InstanceView-1pd7j16bd2b16.css} +1 -1
  27. package/.mochi/svelte-css/SettingsView-n3oy75uonzp2.css +1 -0
  28. package/.mochi/svelte-css/{TopBar-2q87li6yt8ids.css → TopBar-2y76zxwehrfos.css} +1 -1
  29. package/migrations/0007_instance_avatar.sql +1 -0
  30. package/package.json +2 -2
  31. package/src/avatars/diamond.ts +5 -6
  32. package/src/avatars/index.ts +1 -16
  33. package/src/avatars/pick.server.ts +25 -0
  34. package/src/avatars/whale.ts +7 -8
  35. package/src/components/AppShell.svelte +29 -4
  36. package/src/components/Avatar.svelte +8 -8
  37. package/src/components/Button.svelte +22 -1
  38. package/src/components/DashboardView.svelte +14 -2
  39. package/src/components/FolderBrowser.svelte +6 -2
  40. package/src/components/IdeBar.svelte +56 -13
  41. package/src/components/IdeLoader.svelte +15 -3
  42. package/src/components/InstanceCard.svelte +6 -1
  43. package/src/components/InstanceView.svelte +33 -15
  44. package/src/components/SettingsView.svelte +548 -1
  45. package/src/components/TopBar.svelte +37 -10
  46. package/src/components/UiShowcase.svelte +5 -10
  47. package/src/container-injections/claude-aliases.ts +1 -1
  48. package/src/container-injections/claude-code-custom.ts +2 -1
  49. package/src/container-injections/claude-code-ide-extension.ts +5 -0
  50. package/src/container-injections/claude-code-models.ts +2 -1
  51. package/src/container-injections/claude-code-update.ts +62 -1
  52. package/src/container-injections/claude-effort-level.ts +32 -0
  53. package/src/container-injections/claude-permission-mode.ts +40 -0
  54. package/src/container-injections/custom-env-vars.ts +82 -0
  55. package/src/container-injections/host-env-vars.ts +2 -1
  56. package/src/lib/config.server.ts +3 -0
  57. package/src/lib/db.server.ts +8 -3
  58. package/src/lib/devcontainer.server.ts +129 -42
  59. package/src/lib/health.server.ts +55 -11
  60. package/src/lib/injections.server.ts +45 -38
  61. package/src/lib/instances.server.ts +181 -19
  62. package/src/lib/log-capture.server.ts +262 -0
  63. package/src/lib/ports.server.ts +31 -0
  64. package/src/lib/proxy.server.ts +2 -2
  65. package/src/pages/Settings.svelte +22 -1
  66. package/src/routes.ts +98 -2
  67. package/src/types.ts +36 -0
  68. package/.mochi/svelte-client/HydratableIsland-k6q8esj4.js +0 -1
  69. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-sg96rp17.js +0 -2
  70. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-p3f649gk.js +0 -1
  71. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-dzmgmv78.js +0 -29
  72. package/.mochi/svelte-client/chunk-7wkvn2vx.js +0 -2
  73. package/.mochi/svelte-client/chunk-m4fzsd5b.js +0 -2
  74. package/.mochi/svelte-css/Button-2vtbrzb5kgu25.css +0 -1
  75. package/.mochi/svelte-css/IdeBar-10xodudcx57qr.css +0 -1
  76. package/.mochi/svelte-css/SettingsView-2qfauz68axro1.css +0 -1
  77. package/src/container-injections/claude-skip-permissions.ts +0 -28
@@ -1,7 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { type Instance } from '../types.ts';
3
+ import { findAvatar } from '../avatars/index.ts';
3
4
  import Settings from '@lucide/svelte/icons/settings';
5
+ import RotateCw from '@lucide/svelte/icons/rotate-cw';
4
6
  import Terminal from '@lucide/svelte/icons/terminal';
7
+ import LayoutTemplate from '@lucide/svelte/icons/layout-template';
5
8
  import Avatar from './Avatar.svelte';
6
9
  import AppBar from './AppBar.svelte';
7
10
  import { withPopupMarker } from '../lib/popup-nav.ts';
@@ -12,6 +15,7 @@
12
15
  attention,
13
16
  editingId,
14
17
  editingName = $bindable(),
18
+ onreload,
15
19
  onselect,
16
20
  onstartrename,
17
21
  oncommitrename,
@@ -22,6 +26,8 @@
22
26
  attention: Record<string, 'done' | 'waiting' | null>;
23
27
  editingId: string | null;
24
28
  editingName: string;
29
+ /** Absent for terminal tabs, which carry their own reload, and until the iframe exists. */
30
+ onreload?: () => void;
25
31
  onselect: (id: string) => void;
26
32
  onstartrename: (instance: Instance) => void;
27
33
  oncommitrename: (id: string) => void;
@@ -41,7 +47,11 @@
41
47
  >
42
48
  {#if editingId === inst.id}
43
49
  <div class="tab-label editing">
44
- <Avatar id={inst.id} name={inst.name} scale={4} />
50
+ <Avatar
51
+ name={inst.name}
52
+ art={findAvatar(inst.avatar ?? undefined) ?? null}
53
+ scale={4}
54
+ />
45
55
  <!-- svelte-ignore a11y_autofocus -->
46
56
  <input
47
57
  class="tab-name-edit"
@@ -63,10 +73,16 @@
63
73
  ondblclick={() => onstartrename(inst)}
64
74
  title={inst.name}
65
75
  >
66
- <Avatar id={inst.id} name={inst.name} scale={4} />
76
+ <Avatar
77
+ name={inst.name}
78
+ art={findAvatar(inst.avatar ?? undefined) ?? null}
79
+ scale={4}
80
+ />
67
81
  <span class="tab-name">{inst.name}</span>
68
82
  {#if inst.mode === 'terminal'}
69
83
  <span class="tab-mode" title="Terminal-only instance"><Terminal size={13} /></span>
84
+ {:else}
85
+ <span class="tab-mode" title="Full IDE instance"><LayoutTemplate size={13} /></span>
70
86
  {/if}
71
87
  </button>
72
88
  {/if}
@@ -74,28 +90,55 @@
74
90
  {/each}
75
91
  </nav>
76
92
  {/if}
77
- <a
78
- class="cog"
79
- href={withPopupMarker('/settings')}
80
- target="_blank"
81
- rel="noopener noreferrer"
82
- title="Settings"
83
- aria-label="Settings"><Settings size={18} /></a
84
- >
93
+ <div class="right">
94
+ {#if onreload}
95
+ <button
96
+ type="button"
97
+ class="reload"
98
+ onclick={onreload}
99
+ title="Reload editor"
100
+ aria-label="Reload editor"><RotateCw size={18} /></button
101
+ >
102
+ {/if}
103
+ <a
104
+ class="cog"
105
+ href={withPopupMarker('/settings')}
106
+ target="_blank"
107
+ rel="noopener noreferrer"
108
+ title="Settings"
109
+ aria-label="Settings"><Settings size={18} /></a
110
+ >
111
+ </div>
85
112
  </AppBar>
86
113
 
87
114
  <style>
88
- .cog {
115
+ /* Grouped so the auto-margin doesn't have to move when the reload button comes and goes. */
116
+ .right {
117
+ display: flex;
118
+ align-items: stretch;
119
+ margin-left: auto;
120
+ }
121
+ .cog,
122
+ .reload {
89
123
  display: inline-flex;
90
124
  align-items: center;
91
125
  justify-content: center;
92
126
  width: 48px;
93
127
  flex: none;
94
- margin-left: auto;
95
128
  color: var(--ink);
96
129
  border-left: 1px solid var(--rule);
97
130
  }
98
- .cog:hover {
131
+ .reload {
132
+ appearance: none;
133
+ background: transparent;
134
+ border-top: 0;
135
+ border-right: 0;
136
+ border-bottom: 0;
137
+ padding: 0;
138
+ cursor: pointer;
139
+ }
140
+ .cog:hover,
141
+ .reload:hover {
99
142
  background: var(--ink);
100
143
  color: var(--bg);
101
144
  }
@@ -1,14 +1,23 @@
1
1
  <script lang="ts">
2
2
  import Button from './Button.svelte';
3
+ import type { InstanceMode } from '../types.ts';
3
4
 
4
5
  // A fixed flip count per tick, rather than a per-bit probability, is what keeps
5
6
  // the cadence visually even; `speed` scales only the interval, never the churn.
6
7
  let {
7
8
  speed = 1,
9
+ mode = 'ide',
8
10
  // Omit both unless the wait could in principle never end.
9
11
  stalledAfterMs,
10
12
  onoverride
11
- }: { speed?: number; stalledAfterMs?: number; onoverride?: () => void } = $props();
13
+ }: {
14
+ speed?: number;
15
+ mode?: InstanceMode;
16
+ stalledAfterMs?: number;
17
+ onoverride?: () => void;
18
+ } = $props();
19
+
20
+ const isTerminal = $derived(mode === 'terminal');
12
21
 
13
22
  let stalled = $state(false);
14
23
  $effect(() => {
@@ -52,11 +61,14 @@
52
61
  {/each}
53
62
  </div>
54
63
  <div class="label">
55
- Loading editor<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span>
64
+ Loading {isTerminal ? 'terminal' : 'editor'}<span class="dot">.</span><span class="dot">.</span
65
+ ><span class="dot">.</span>
56
66
  </div>
57
67
  {#if stalled}
58
68
  <div class="stalled">
59
- <p>Waiting for code-server to answer inside the container.</p>
69
+ <p>
70
+ Waiting for {isTerminal ? 'the terminal' : 'code-server'} to answer inside the container.
71
+ </p>
60
72
  {#if onoverride}
61
73
  <Button size="sm" onclick={onoverride}>Open anyway</Button>
62
74
  {/if}
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { type Instance } from '../types.ts';
3
+ import { findAvatar } from '../avatars/index.ts';
3
4
  import Avatar from './Avatar.svelte';
4
5
  import BranchBox from './BranchBox.svelte';
5
6
  import PortsBox from './PortsBox.svelte';
@@ -52,7 +53,11 @@
52
53
  </div>
53
54
  {/if}
54
55
  <div class="card-head">
55
- <Avatar id={instance.id} name={instance.name} interactive />
56
+ <Avatar
57
+ name={instance.name}
58
+ art={findAvatar(instance.avatar ?? undefined) ?? null}
59
+ interactive
60
+ />
56
61
  {#if editing}
57
62
  <!-- svelte-ignore a11y_autofocus -->
58
63
  <input
@@ -22,26 +22,24 @@
22
22
  let logEl = $state<HTMLDivElement | null>(null);
23
23
  let following = $state(true);
24
24
 
25
+ // tick() lets the <pre> text node grow before we measure, so we reach the true bottom.
26
+ async function scrollToBottom() {
27
+ await tick();
28
+ logEl?.scrollTo(0, logEl.scrollHeight);
29
+ }
30
+
25
31
  // Pin to the newest output whenever logs grow — but only while following.
26
- // tick() lets the <pre> text node grow before we scroll, so we reach the true bottom.
27
32
  $effect(() => {
28
33
  if (!following || logs.length === 0) return; // reading logs.length tracks appended chunks
29
- tick().then(() => {
30
- if (following) logEl?.scrollTo(0, logEl.scrollHeight);
31
- });
34
+ scrollToBottom();
32
35
  });
33
36
 
34
- // A user scrolling up pauses following; programmatic scroll-to-bottom keeps the
35
- // gap ~0, so it never trips this — only re-clicking "Follow log" resumes.
36
- function onLogScroll() {
37
- if (!logEl) return;
38
- const gap = logEl.scrollHeight - logEl.clientHeight - logEl.scrollTop;
39
- if (gap > 40) following = false;
40
- }
41
-
37
+ // Following is deliberately sticky: only this button changes it. Inferring it from
38
+ // scroll events misfired constantly, since streaming chunks and reflow above the log
39
+ // both move scrollTop without the user touching anything.
42
40
  function toggleFollow() {
43
41
  following = !following;
44
- if (following) logEl?.scrollTo(0, logEl.scrollHeight);
42
+ if (following) scrollToBottom();
45
43
  }
46
44
 
47
45
  $effect(() => installPopupBackTrap());
@@ -255,7 +253,14 @@
255
253
  <div class="log-bar panel-bar">
256
254
  <span>Boot log</span>
257
255
  <div class="log-actions">
258
- <button class="copy" class:active={following} onclick={toggleFollow}>
256
+ <button
257
+ class="copy"
258
+ class:active={following}
259
+ aria-pressed={following}
260
+ title={following ? 'Following — click to pause' : 'Paused — click to follow new output'}
261
+ onclick={toggleFollow}
262
+ >
263
+ {#if following}<span class="dot"></span>{/if}
259
264
  {following ? 'Following' : 'Follow log'}
260
265
  </button>
261
266
  <button class="copy" onclick={copyLogs} disabled={!logs}>
@@ -263,7 +268,7 @@
263
268
  </button>
264
269
  </div>
265
270
  </div>
266
- <div class="logs" bind:this={logEl} onscroll={onLogScroll}>
271
+ <div class="logs" bind:this={logEl}>
267
272
  <pre>{logs || 'Waiting for output…'}<span class="caret"></span></pre>
268
273
  </div>
269
274
  {#if instance?.status === 'error' && instance.error}
@@ -535,6 +540,9 @@
535
540
  align-items: center;
536
541
  }
537
542
  .copy {
543
+ display: inline-flex;
544
+ align-items: center;
545
+ gap: 5px;
538
546
  font-family: var(--font-mono);
539
547
  font-weight: 600;
540
548
  font-size: 10px;
@@ -554,6 +562,14 @@
554
562
  background: transparent;
555
563
  color: var(--bg);
556
564
  }
565
+ /* The dot, not the inverted fill, is what separates "following" from a mere hover. */
566
+ .dot {
567
+ flex: none;
568
+ width: 6px;
569
+ height: 6px;
570
+ border-radius: 50%;
571
+ background: var(--attn-done);
572
+ }
557
573
  .copy:disabled {
558
574
  opacity: 0.5;
559
575
  cursor: not-allowed;
@@ -568,6 +584,8 @@
568
584
  height: calc(100vh - 320px);
569
585
  min-height: 360px;
570
586
  overflow: auto;
587
+ /* Scroll anchoring would shift scrollTop as the <pre> grows; a paused log must stay put. */
588
+ overflow-anchor: none;
571
589
  }
572
590
  .logs pre {
573
591
  margin: 0;