codebay 0.0.1 → 0.2.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 (202) hide show
  1. package/.mochi/import-css/index-0nwv3654.css +18 -0
  2. package/.mochi/import-css/index-xndx4980.css +54 -0
  3. package/.mochi/manifest.json +1608 -0
  4. package/.mochi/server-island.js +1 -0
  5. package/.mochi/svelte-client/HydratableIsland-ps09gjy2.js +1 -0
  6. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-62w5zwww.js +2 -0
  7. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-jmm501zf.js +13 -0
  8. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-ewk4n2c0.js +1 -0
  9. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-e7e89md0.js +23 -0
  10. package/.mochi/svelte-client/chunk-jx5cekhh.js +2 -0
  11. package/.mochi/svelte-client/chunk-k3my1snp.js +2 -0
  12. package/.mochi/svelte-client/chunk-kgn1b2t1.js +4 -0
  13. package/.mochi/svelte-client/chunk-ry7q5zk5.js +1 -0
  14. package/.mochi/svelte-client/chunk-x843txjc.js +2 -0
  15. package/.mochi/svelte-client/chunk-zjcg8kw5.js +2 -0
  16. package/.mochi/svelte-compile/App-8k1gyxdc.server.js +1167 -0
  17. package/.mochi/svelte-compile/Avatars-f7c6rp96.server.js +351 -0
  18. package/.mochi/svelte-compile/ClientStats-z3tn9ad6.server.js +99 -0
  19. package/.mochi/svelte-compile/DefaultError-wh77vebf.server.js +52 -0
  20. package/.mochi/svelte-compile/Instance-yvps12zg.server.js +355 -0
  21. package/.mochi/svelte-compile/Settings-jwg3httb.server.js +1169 -0
  22. package/.mochi/svelte-compile/chunk-4kwy539a.js +14 -0
  23. package/.mochi/svelte-compile/chunk-9bjk7yx6.js +81 -0
  24. package/.mochi/svelte-compile/chunk-dqgstte4.js +52 -0
  25. package/.mochi/svelte-compile/chunk-sxqys3nw.js +1668 -0
  26. package/.mochi/svelte-compile/chunk-v6796ga4.js +3295 -0
  27. package/.mochi/svelte-compile/chunk-we0ggtws.js +34 -0
  28. package/.mochi/svelte-css/AppBar-3bxinv4pabv8l.css +1 -0
  29. package/.mochi/svelte-css/AppShell-36mpg5smnbmuw.css +1 -0
  30. package/.mochi/svelte-css/Avatar-24th63awm9mna.css +1 -0
  31. package/.mochi/svelte-css/AvatarEditor-1ryrs1md5g36y.css +1 -0
  32. package/.mochi/svelte-css/AvatarsView-3hq37u4dgtljn.css +1 -0
  33. package/.mochi/svelte-css/BackLink-2wlu0eovz9j86.css +1 -0
  34. package/.mochi/svelte-css/BranchBox-1yqu1q2mgnjwz.css +1 -0
  35. package/.mochi/svelte-css/Brand-1la7zw5uhcbl4.css +1 -0
  36. package/.mochi/svelte-css/Button-2vtbrzb5kgu25.css +1 -0
  37. package/.mochi/svelte-css/CheckmarkIcon-3qlazj2oc50jc.css +1 -0
  38. package/.mochi/svelte-css/ClientStats-19qzckq40p2q9.css +1 -0
  39. package/.mochi/svelte-css/CoinButton-1rej3sh3tkjud.css +1 -0
  40. package/.mochi/svelte-css/CredMenu-9hwgeptp7crb.css +1 -0
  41. package/.mochi/svelte-css/DashboardView-1cy4xpqhja9sx.css +1 -0
  42. package/.mochi/svelte-css/DefaultError-2b0daamydxgb2.css +1 -0
  43. package/.mochi/svelte-css/ErrorIcon-2x0tzsqxskw28.css +1 -0
  44. package/.mochi/svelte-css/FolderBrowser-1x428zb3f2kti.css +1 -0
  45. package/.mochi/svelte-css/HealthBox-er2her0jukwl.css +1 -0
  46. package/.mochi/svelte-css/IdeBar-2qoqi2ya9zdn3.css +1 -0
  47. package/.mochi/svelte-css/IdeLoader-26rlko8e00j3g.css +1 -0
  48. package/.mochi/svelte-css/InstanceCard-16f77aysm7lgp.css +1 -0
  49. package/.mochi/svelte-css/InstanceView-wq85bxtusont.css +1 -0
  50. package/.mochi/svelte-css/LoaderIcon-soq5wt3p05ye.css +1 -0
  51. package/.mochi/svelte-css/PortsBox-1t25b5yg7jva3.css +1 -0
  52. package/.mochi/svelte-css/SettingsCog-jftyadvo64qo.css +1 -0
  53. package/.mochi/svelte-css/SettingsView-3bbkzsvnrth3v.css +1 -0
  54. package/.mochi/svelte-css/Skeleton-8k69usiju43h.css +1 -0
  55. package/.mochi/svelte-css/StatusBadge-3mq0j7z6aq4j.css +1 -0
  56. package/.mochi/svelte-css/ThemePicker-1ir8jyocm8mvi.css +1 -0
  57. package/.mochi/svelte-css/ToastBar-3havgkmtofntq.css +1 -0
  58. package/.mochi/svelte-css/ToastIcon-2z5wrkc95khg0.css +1 -0
  59. package/.mochi/svelte-css/ToastMessage-2jfwb2og2lfa8.css +1 -0
  60. package/.mochi/svelte-css/ToastWrapper-1fbnym10rm2nv.css +1 -0
  61. package/.mochi/svelte-css/Toaster-kk60j193854n.css +1 -0
  62. package/.mochi/svelte-css/TopBar-2q87li6yt8ids.css +1 -0
  63. package/LICENSE +21 -0
  64. package/README.md +33 -3
  65. package/bin/codebay.ts +22 -0
  66. package/migrations/0001_init.sql +20 -0
  67. package/migrations/0002_port_forwards.sql +9 -0
  68. package/migrations/0003_options.sql +4 -0
  69. package/migrations/0004_instance_image.sql +1 -0
  70. package/package.json +87 -9
  71. package/public/sounds/done.wav +0 -0
  72. package/public/sounds/waiting.wav +0 -0
  73. package/src/__fixtures__/HelloWorld.svelte +4 -0
  74. package/src/additional-svelte-typings.d.ts +1 -0
  75. package/src/api.ts +61 -0
  76. package/src/avatars/anchor.ts +17 -0
  77. package/src/avatars/bear.ts +17 -0
  78. package/src/avatars/bee.ts +17 -0
  79. package/src/avatars/bob-marley.ts +17 -0
  80. package/src/avatars/car.ts +17 -0
  81. package/src/avatars/carpathians.ts +17 -0
  82. package/src/avatars/cat-body.ts +17 -0
  83. package/src/avatars/cat-sits.ts +17 -0
  84. package/src/avatars/cat.ts +17 -0
  85. package/src/avatars/celtic-cross.ts +17 -0
  86. package/src/avatars/cherry.ts +17 -0
  87. package/src/avatars/cotton.ts +17 -0
  88. package/src/avatars/crab.ts +17 -0
  89. package/src/avatars/crown.ts +17 -0
  90. package/src/avatars/diamond.ts +17 -0
  91. package/src/avatars/dog.ts +17 -0
  92. package/src/avatars/elephant.ts +17 -0
  93. package/src/avatars/fish.ts +17 -0
  94. package/src/avatars/fleur-de-lis.ts +17 -0
  95. package/src/avatars/flower.ts +17 -0
  96. package/src/avatars/fox.ts +17 -0
  97. package/src/avatars/frog.ts +17 -0
  98. package/src/avatars/ghost.ts +17 -0
  99. package/src/avatars/giraffe.ts +17 -0
  100. package/src/avatars/glasses.ts +17 -0
  101. package/src/avatars/heart.ts +17 -0
  102. package/src/avatars/index.ts +128 -0
  103. package/src/avatars/invader.ts +17 -0
  104. package/src/avatars/kangaroo.ts +17 -0
  105. package/src/avatars/key.ts +17 -0
  106. package/src/avatars/lightning.ts +17 -0
  107. package/src/avatars/mushroom.ts +17 -0
  108. package/src/avatars/octopus.ts +17 -0
  109. package/src/avatars/owl.ts +17 -0
  110. package/src/avatars/penguin.ts +17 -0
  111. package/src/avatars/planet.ts +17 -0
  112. package/src/avatars/rabbit.ts +17 -0
  113. package/src/avatars/rat.ts +17 -0
  114. package/src/avatars/robot.ts +17 -0
  115. package/src/avatars/rocket.ts +17 -0
  116. package/src/avatars/sea.ts +17 -0
  117. package/src/avatars/serialize.ts +65 -0
  118. package/src/avatars/skull.ts +17 -0
  119. package/src/avatars/snail.ts +17 -0
  120. package/src/avatars/snake.ts +17 -0
  121. package/src/avatars/snakegame.ts +17 -0
  122. package/src/avatars/star.ts +17 -0
  123. package/src/avatars/svelte.ts +17 -0
  124. package/src/avatars/target.ts +17 -0
  125. package/src/avatars/tree.ts +17 -0
  126. package/src/avatars/types.ts +24 -0
  127. package/src/avatars/ukraine.ts +17 -0
  128. package/src/avatars/whale.ts +17 -0
  129. package/src/avatars/wheat.ts +17 -0
  130. package/src/avatars/yinyan.ts +17 -0
  131. package/src/components/AppBar.svelte +35 -0
  132. package/src/components/AppShell.svelte +302 -0
  133. package/src/components/Avatar.svelte +235 -0
  134. package/src/components/AvatarEditor.svelte +321 -0
  135. package/src/components/AvatarsView.svelte +113 -0
  136. package/src/components/BranchBox.svelte +32 -0
  137. package/src/components/Brand.svelte +41 -0
  138. package/src/components/Button.svelte +161 -0
  139. package/src/components/CoinButton.svelte +121 -0
  140. package/src/components/CredMenu.svelte +171 -0
  141. package/src/components/DashboardView.svelte +198 -0
  142. package/src/components/FolderBrowser.svelte +635 -0
  143. package/src/components/HealthBox.svelte +161 -0
  144. package/src/components/IdeBar.svelte +186 -0
  145. package/src/components/IdeLoader.svelte +135 -0
  146. package/src/components/InstanceCard.svelte +220 -0
  147. package/src/components/InstanceView.svelte +565 -0
  148. package/src/components/PortsBox.svelte +73 -0
  149. package/src/components/SettingsCog.svelte +22 -0
  150. package/src/components/SettingsView.svelte +1484 -0
  151. package/src/components/Skeleton.svelte +43 -0
  152. package/src/components/StatusBadge.svelte +54 -0
  153. package/src/components/ThemePicker.svelte +77 -0
  154. package/src/components/TopBar.svelte +64 -0
  155. package/src/components/UiShowcase.svelte +578 -0
  156. package/src/components/ui-showcase/ComponentDemo.svelte +85 -0
  157. package/src/container-injections/attention-hooks.ts +117 -0
  158. package/src/container-injections/claude-aliases.ts +43 -0
  159. package/src/container-injections/claude-code-credentials.ts +145 -0
  160. package/src/container-injections/claude-code-custom.ts +125 -0
  161. package/src/container-injections/claude-no-coauthor.ts +35 -0
  162. package/src/container-injections/claude-skip-permissions.ts +38 -0
  163. package/src/container-injections/claude-statusline.ts +109 -0
  164. package/src/container-injections/git-identity.ts +63 -0
  165. package/src/container-injections/git-safe-directory.ts +18 -0
  166. package/src/container-injections/github-credentials.ts +162 -0
  167. package/src/container-injections/host-env-vars.ts +108 -0
  168. package/src/container-injections/tmux.ts +67 -0
  169. package/src/fontsource.d.ts +3 -0
  170. package/src/index.ts +81 -0
  171. package/src/lib/auth.server.ts +95 -0
  172. package/src/lib/bridge.server.ts +34 -0
  173. package/src/lib/config.server.ts +122 -0
  174. package/src/lib/crypto.server.ts +12 -0
  175. package/src/lib/db.server.ts +196 -0
  176. package/src/lib/devcontainer.server.ts +503 -0
  177. package/src/lib/docker-client.server.ts +61 -0
  178. package/src/lib/docker.server.ts +139 -0
  179. package/src/lib/exec.server.ts +92 -0
  180. package/src/lib/git.server.ts +68 -0
  181. package/src/lib/health.server.ts +105 -0
  182. package/src/lib/injections.server.ts +72 -0
  183. package/src/lib/instances.server.ts +552 -0
  184. package/src/lib/picker.server.ts +46 -0
  185. package/src/lib/ports.server.ts +9 -0
  186. package/src/lib/proxy.server.ts +170 -0
  187. package/src/lib/repo-url.ts +60 -0
  188. package/src/lib/spawn.server.ts +14 -0
  189. package/src/lib/theme.server.ts +22 -0
  190. package/src/live.ts +50 -0
  191. package/src/pages/App.svelte +14 -0
  192. package/src/pages/Avatars.svelte +7 -0
  193. package/src/pages/Instance.svelte +9 -0
  194. package/src/pages/Settings.svelte +73 -0
  195. package/src/pages/UI.svelte +10 -0
  196. package/src/routes.ts +440 -0
  197. package/src/settings.ts +14 -0
  198. package/src/shell.html +100 -0
  199. package/src/sound.ts +69 -0
  200. package/src/theme.ts +24 -0
  201. package/src/types.ts +79 -0
  202. package/svelte.config.js +5 -0
@@ -0,0 +1,635 @@
1
+ <script lang="ts">
2
+ import { tick } from 'svelte';
3
+ import type { BrowseResult, FolderHistoryEntry } from '../types.ts';
4
+ import { isRepoUrl } from '../lib/repo-url.ts';
5
+ import X from '@lucide/svelte/icons/x';
6
+ import FolderClock from '@lucide/svelte/icons/folder-clock';
7
+ import GitBranch from '@lucide/svelte/icons/git-branch';
8
+ import ArrowUp from '@lucide/svelte/icons/arrow-up';
9
+ import Folder from '@lucide/svelte/icons/folder';
10
+ import TriangleAlert from '@lucide/svelte/icons/triangle-alert';
11
+ import { apiFetch, apiDelete } from '../api.ts';
12
+
13
+ let {
14
+ onpick,
15
+ onclose
16
+ }: { onpick: (source: string, opts?: { branch?: string }) => void; onclose: () => void } =
17
+ $props();
18
+
19
+ let result = $state<BrowseResult | null>(null);
20
+ let loading = $state(true);
21
+ let errorMsg = $state<string | null>(null);
22
+ let query = $state('');
23
+ let searchInput = $state<HTMLInputElement | null>(null);
24
+ let history = $state<FolderHistoryEntry[]>([]);
25
+ let showAll = $state(false);
26
+ let repoUrl = $state('');
27
+ let repoBranch = $state('');
28
+
29
+ const repoValid = $derived(isRepoUrl(repoUrl));
30
+
31
+ function cloneRepoUrl() {
32
+ if (!repoValid) return;
33
+ onpick(repoUrl.trim(), { branch: repoBranch.trim() || undefined });
34
+ }
35
+
36
+ const filtered = $derived(
37
+ result
38
+ ? result.entries.filter((e) => e.name.toLowerCase().includes(query.trim().toLowerCase()))
39
+ : []
40
+ );
41
+
42
+ const shownHistory = $derived(showAll ? history : history.slice(0, 5));
43
+
44
+ async function loadHistory() {
45
+ try {
46
+ const data = await apiFetch<{ history?: FolderHistoryEntry[] }>('/api/history');
47
+ history = data.history ?? [];
48
+ } catch {
49
+ /* history is a convenience; ignore failures */
50
+ }
51
+ }
52
+
53
+ async function removeHistory(path: string) {
54
+ const prev = history;
55
+ history = history.filter((h) => h.source_path !== path);
56
+ try {
57
+ await apiDelete('/api/history', { sourcePath: path });
58
+ } catch {
59
+ history = prev; // restore on failure (network error, or a non-2xx response)
60
+ }
61
+ }
62
+
63
+ async function load(path: string | null = null) {
64
+ loading = true;
65
+ errorMsg = null;
66
+ query = '';
67
+ try {
68
+ const url = path ? `/api/browse?path=${encodeURIComponent(path)}` : '/api/browse';
69
+ result = await apiFetch<BrowseResult>(url, undefined, 'Could not list folder');
70
+ } catch (err) {
71
+ errorMsg = (err as Error).message;
72
+ } finally {
73
+ loading = false;
74
+ }
75
+ await tick();
76
+ searchInput?.focus();
77
+ }
78
+
79
+ $effect(() => {
80
+ load();
81
+ loadHistory();
82
+ });
83
+ </script>
84
+
85
+ <div
86
+ class="overlay"
87
+ role="button"
88
+ tabindex="0"
89
+ onclick={onclose}
90
+ onkeydown={(e) => e.key === 'Escape' && onclose()}
91
+ >
92
+ <div
93
+ class="modal panel"
94
+ role="dialog"
95
+ aria-modal="true"
96
+ aria-label="Pick a project folder"
97
+ tabindex="-1"
98
+ onclick={(e) => e.stopPropagation()}
99
+ onkeydown={() => {}}
100
+ >
101
+ <div class="head">
102
+ <h2>Pick a project folder</h2>
103
+ <button class="x" onclick={onclose} aria-label="Close"><X size={16} /></button>
104
+ </div>
105
+
106
+ <div class="clone">
107
+ <div class="clone-label">Clone a Git repository</div>
108
+ <div class="clone-row">
109
+ <input
110
+ class="clone-url"
111
+ type="text"
112
+ placeholder="https://github.com/owner/repo"
113
+ bind:value={repoUrl}
114
+ spellcheck="false"
115
+ autocomplete="off"
116
+ onkeydown={(e) => {
117
+ if (e.key === 'Enter') {
118
+ e.preventDefault();
119
+ cloneRepoUrl();
120
+ }
121
+ }}
122
+ />
123
+ <input
124
+ class="clone-branch"
125
+ type="text"
126
+ placeholder="branch (optional)"
127
+ bind:value={repoBranch}
128
+ spellcheck="false"
129
+ autocomplete="off"
130
+ onkeydown={(e) => {
131
+ if (e.key === 'Enter') {
132
+ e.preventDefault();
133
+ cloneRepoUrl();
134
+ }
135
+ }}
136
+ />
137
+ <button class="clone-btn" disabled={!repoValid} onclick={cloneRepoUrl}>Clone</button>
138
+ </div>
139
+ </div>
140
+
141
+ <div class="crumbs">
142
+ <code>{result?.path ?? '…'}</code>
143
+ </div>
144
+
145
+ {#if history.length > 0}
146
+ <div class="recent">
147
+ <div class="recent-label">Recent</div>
148
+ {#each shownHistory as entry (entry.source_path)}
149
+ <div class="recent-row">
150
+ <button class="recent-pick" onclick={() => onpick(entry.source_path)}>
151
+ <span class="icon">
152
+ {#if isRepoUrl(entry.source_path)}<GitBranch size={16} />{:else}<FolderClock
153
+ size={16}
154
+ />{/if}
155
+ </span>
156
+ <span class="recent-text">
157
+ <span class="recent-name">{entry.name}</span>
158
+ <span class="recent-path">{entry.source_path}</span>
159
+ </span>
160
+ </button>
161
+ <button
162
+ class="recent-x"
163
+ aria-label="Remove {entry.name} from history"
164
+ title="Remove from history"
165
+ onclick={(e) => {
166
+ e.stopPropagation();
167
+ removeHistory(entry.source_path);
168
+ }}
169
+ >
170
+ <X size={14} />
171
+ </button>
172
+ </div>
173
+ {/each}
174
+ {#if history.length > 5}
175
+ <button class="recent-toggle" onclick={() => (showAll = !showAll)}>
176
+ {showAll ? 'Show fewer' : `Show all (${history.length})`}
177
+ </button>
178
+ {/if}
179
+ </div>
180
+ {/if}
181
+
182
+ <div class="search">
183
+ <input
184
+ type="text"
185
+ placeholder="Filter folders…"
186
+ bind:value={query}
187
+ bind:this={searchInput}
188
+ spellcheck="false"
189
+ autocomplete="off"
190
+ onkeydown={(e) => {
191
+ const first = filtered[0];
192
+ if (e.key === 'Enter' && first) {
193
+ e.preventDefault();
194
+ load(first.path);
195
+ }
196
+ }}
197
+ />
198
+ </div>
199
+
200
+ <div class="list">
201
+ {#if loading}
202
+ <div class="muted">Loading…</div>
203
+ {:else if errorMsg}
204
+ <div class="muted err">{errorMsg}</div>
205
+ {:else if result}
206
+ {#if result.parent && !query}
207
+ <div class="row">
208
+ <button class="nav" onclick={() => load(result?.parent ?? null)}>
209
+ <span class="icon"><ArrowUp size={16} /></span>
210
+ <span class="ename">..</span>
211
+ </button>
212
+ </div>
213
+ {/if}
214
+ {#if filtered.length === 0}
215
+ <div class="muted">{query ? 'No folders match.' : 'No subfolders here.'}</div>
216
+ {/if}
217
+ {#each filtered as entry, i (entry.path)}
218
+ <div class="row" class:first-match={query.trim() && i === 0}>
219
+ <button class="nav" onclick={() => load(entry.path)}>
220
+ <span class="icon"><Folder size={16} /></span>
221
+ <span class="ename">{entry.name}</span>
222
+ {#if entry.hasDevcontainer}<span class="badge">devcontainer</span>{/if}
223
+ </button>
224
+ <button class="pick-inline" onclick={() => onpick(entry.path)}>Select</button>
225
+ </div>
226
+ {/each}
227
+ {/if}
228
+ </div>
229
+
230
+ {#if result && !result.hasDevcontainer}
231
+ <div class="warn">
232
+ <TriangleAlert size={15} />
233
+ <span
234
+ >No <code>devcontainer.json</code> in this folder — a default image will be used if you select
235
+ it.</span
236
+ >
237
+ </div>
238
+ {/if}
239
+
240
+ <div class="foot">
241
+ <span class="hint">Browse to a folder, then select this folder or any subfolder.</span>
242
+ <button class="primary" disabled={!result} onclick={() => result && onpick(result.path)}>
243
+ Select this folder
244
+ </button>
245
+ </div>
246
+ </div>
247
+ </div>
248
+
249
+ <style>
250
+ .overlay {
251
+ position: fixed;
252
+ inset: 0;
253
+ background: color-mix(in srgb, var(--ink) 50%, transparent);
254
+ display: grid;
255
+ place-items: center;
256
+ padding: 24px;
257
+ z-index: 50;
258
+ }
259
+ .modal {
260
+ width: min(620px, 100%);
261
+ max-height: 80vh;
262
+ display: flex;
263
+ flex-direction: column;
264
+ background: var(--bg-card);
265
+ overflow: hidden;
266
+ box-shadow: 8px 8px 0 var(--ink);
267
+ }
268
+ .head {
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: space-between;
272
+ padding: 14px 18px;
273
+ border-bottom: 1px solid var(--ink);
274
+ background: var(--ink);
275
+ }
276
+ .head h2 {
277
+ margin: 0;
278
+ font-family: var(--font-display);
279
+ font-weight: 700;
280
+ font-size: 17px;
281
+ text-transform: uppercase;
282
+ letter-spacing: 0.06em;
283
+ color: var(--bg);
284
+ }
285
+ .x {
286
+ display: inline-flex;
287
+ align-items: center;
288
+ border: none;
289
+ background: none;
290
+ cursor: pointer;
291
+ color: var(--bg);
292
+ }
293
+ .x:hover {
294
+ opacity: 0.7;
295
+ }
296
+ .clone {
297
+ padding: 12px 18px;
298
+ border-bottom: 1px solid var(--rule-soft);
299
+ }
300
+ .clone-label {
301
+ font-size: 11px;
302
+ text-transform: uppercase;
303
+ letter-spacing: 0.06em;
304
+ color: var(--ink-faint);
305
+ padding-bottom: 6px;
306
+ }
307
+ .clone-row {
308
+ display: flex;
309
+ gap: 8px;
310
+ }
311
+ .clone-url,
312
+ .clone-branch {
313
+ font-family: var(--font-mono);
314
+ font-size: 13px;
315
+ padding: 8px 12px;
316
+ border: 1px solid var(--ink);
317
+ background: var(--bg);
318
+ color: var(--ink);
319
+ }
320
+ .clone-url {
321
+ flex: 1;
322
+ min-width: 0;
323
+ }
324
+ .clone-branch {
325
+ width: 140px;
326
+ flex: none;
327
+ }
328
+ .clone-url::placeholder,
329
+ .clone-branch::placeholder {
330
+ color: var(--ink-faint);
331
+ }
332
+ .clone-url:focus,
333
+ .clone-branch:focus {
334
+ outline: none;
335
+ box-shadow: inset 3px 3px 0 var(--rule-soft);
336
+ }
337
+ .clone-btn {
338
+ font-family: var(--font-mono);
339
+ font-weight: 700;
340
+ font-size: 12px;
341
+ text-transform: uppercase;
342
+ letter-spacing: 0.05em;
343
+ padding: 8px 14px;
344
+ border: 1px solid var(--ink);
345
+ background: var(--ink);
346
+ color: var(--bg);
347
+ cursor: pointer;
348
+ white-space: nowrap;
349
+ }
350
+ .clone-btn:hover:not(:disabled) {
351
+ background: var(--bg-card);
352
+ color: var(--ink);
353
+ }
354
+ .clone-btn:disabled {
355
+ opacity: 0.4;
356
+ cursor: not-allowed;
357
+ }
358
+ .crumbs {
359
+ padding: 10px 18px;
360
+ border-bottom: 1px solid var(--rule-soft);
361
+ overflow-x: auto;
362
+ overflow-y: hidden;
363
+ /* Reserve a full line so the path isn't clipped by the horizontal scrollbar. */
364
+ min-height: 18px;
365
+ flex-shrink: 0;
366
+ }
367
+ .crumbs code {
368
+ font-family: var(--font-mono);
369
+ font-size: 12px;
370
+ color: var(--ink-soft);
371
+ white-space: nowrap;
372
+ }
373
+ .recent {
374
+ padding: 8px;
375
+ border-bottom: 1px solid var(--rule-soft);
376
+ /* Fixed so the folder list can't squish it. */
377
+ flex-shrink: 0;
378
+ max-height: 40vh;
379
+ overflow: auto;
380
+ }
381
+ .recent-label {
382
+ font-size: 11px;
383
+ text-transform: uppercase;
384
+ letter-spacing: 0.06em;
385
+ color: var(--ink-faint);
386
+ padding: 2px 10px 6px;
387
+ }
388
+ .recent-row {
389
+ position: relative;
390
+ display: flex;
391
+ align-items: center;
392
+ }
393
+ .recent-pick {
394
+ flex: 1;
395
+ display: flex;
396
+ align-items: center;
397
+ gap: 10px;
398
+ min-width: 0;
399
+ padding: 8px 10px;
400
+ border: none;
401
+ background: none;
402
+ cursor: pointer;
403
+ font: inherit;
404
+ text-align: left;
405
+ border-radius: 8px;
406
+ color: var(--ink);
407
+ }
408
+ .recent-pick:hover {
409
+ background: var(--ink);
410
+ color: var(--bg);
411
+ }
412
+ .recent-pick:hover .recent-path {
413
+ color: var(--bg);
414
+ }
415
+ .recent-text {
416
+ display: flex;
417
+ flex-direction: column;
418
+ min-width: 0;
419
+ }
420
+ .recent-name {
421
+ overflow: hidden;
422
+ text-overflow: ellipsis;
423
+ white-space: nowrap;
424
+ }
425
+ .recent-path {
426
+ font-family: var(--font-mono);
427
+ font-size: 11px;
428
+ color: var(--ink-faint);
429
+ overflow: hidden;
430
+ text-overflow: ellipsis;
431
+ white-space: nowrap;
432
+ }
433
+ .recent-x {
434
+ position: absolute;
435
+ right: 8px;
436
+ top: 50%;
437
+ transform: translateY(-50%);
438
+ display: inline-flex;
439
+ align-items: center;
440
+ border: none;
441
+ background: var(--bg-card);
442
+ color: var(--ink-soft);
443
+ cursor: pointer;
444
+ line-height: 1;
445
+ padding: 5px 7px;
446
+ border-radius: 6px;
447
+ opacity: 0;
448
+ }
449
+ .recent-row:hover .recent-x {
450
+ opacity: 1;
451
+ }
452
+ .recent-x:hover {
453
+ color: var(--bg);
454
+ background: var(--ink);
455
+ }
456
+ .recent-toggle {
457
+ margin: 4px 6px 2px;
458
+ border: none;
459
+ background: none;
460
+ cursor: pointer;
461
+ font-family: var(--font-mono);
462
+ font-size: 12px;
463
+ text-transform: uppercase;
464
+ letter-spacing: 0.05em;
465
+ color: var(--ink);
466
+ padding: 4px 6px;
467
+ }
468
+ .recent-toggle:hover {
469
+ text-decoration: underline;
470
+ }
471
+ .search {
472
+ padding: 10px 18px;
473
+ border-bottom: 1px solid var(--rule-soft);
474
+ }
475
+ .search input {
476
+ width: 100%;
477
+ font-family: var(--font-mono);
478
+ font-size: 13px;
479
+ padding: 8px 12px;
480
+ border: 1px solid var(--ink);
481
+ background: var(--bg);
482
+ color: var(--ink);
483
+ }
484
+ .search input::placeholder {
485
+ color: var(--ink-faint);
486
+ }
487
+ .search input:focus {
488
+ outline: none;
489
+ box-shadow: inset 3px 3px 0 var(--rule-soft);
490
+ }
491
+ .list {
492
+ flex: 1;
493
+ overflow: auto;
494
+ padding: 8px;
495
+ }
496
+ .row {
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 6px;
500
+ padding: 2px 4px;
501
+ border: 1px solid transparent;
502
+ border-radius: 8px;
503
+ }
504
+ .row.first-match {
505
+ border-color: var(--rule-soft);
506
+ }
507
+ .row .nav {
508
+ flex: 1;
509
+ display: flex;
510
+ align-items: center;
511
+ gap: 10px;
512
+ padding: 9px 10px;
513
+ border: none;
514
+ background: none;
515
+ cursor: pointer;
516
+ font: inherit;
517
+ text-align: left;
518
+ border-radius: 8px;
519
+ color: var(--ink);
520
+ }
521
+ .row .nav:hover {
522
+ background: var(--ink);
523
+ color: var(--bg);
524
+ }
525
+ .row .nav:hover .icon {
526
+ color: var(--bg);
527
+ }
528
+ .ename {
529
+ overflow: hidden;
530
+ text-overflow: ellipsis;
531
+ white-space: nowrap;
532
+ }
533
+ .badge {
534
+ font-family: var(--font-mono);
535
+ font-weight: 600;
536
+ font-size: 9px;
537
+ text-transform: uppercase;
538
+ letter-spacing: 0.08em;
539
+ color: var(--ink);
540
+ background: transparent;
541
+ border: 1px solid var(--ink);
542
+ padding: 2px 5px;
543
+ }
544
+ .row .nav:hover .badge {
545
+ color: var(--bg);
546
+ border-color: var(--bg);
547
+ }
548
+ .pick-inline {
549
+ font-family: var(--font-mono);
550
+ font-weight: 600;
551
+ font-size: 11px;
552
+ text-transform: uppercase;
553
+ letter-spacing: 0.05em;
554
+ padding: 6px 10px;
555
+ border: 1px solid var(--ink);
556
+ background: var(--bg-card);
557
+ cursor: pointer;
558
+ color: var(--ink);
559
+ }
560
+ .pick-inline:hover {
561
+ background: var(--ink);
562
+ color: var(--bg);
563
+ }
564
+ .muted {
565
+ padding: 18px;
566
+ color: var(--ink-faint);
567
+ text-align: center;
568
+ }
569
+ .muted.err {
570
+ color: var(--ink);
571
+ font-weight: 600;
572
+ }
573
+ .warn {
574
+ display: flex;
575
+ align-items: flex-start;
576
+ gap: 8px;
577
+ padding: 10px 18px;
578
+ background: var(--warn-bg);
579
+ color: var(--warn-ink);
580
+ font-family: var(--font-mono);
581
+ font-size: 12.5px;
582
+ border-top: 1px solid var(--warn-line);
583
+ }
584
+ .warn :global(svg) {
585
+ flex: none;
586
+ margin-top: 1px;
587
+ }
588
+ .warn code {
589
+ font-family: var(--font-mono);
590
+ font-size: 0.9em;
591
+ /* Intentional white glint to lift the code chip off the amber warn bg — not a palette token. */
592
+ background: rgba(255, 255, 255, 0.35);
593
+ border: 1px solid var(--warn-line);
594
+ padding: 0 5px;
595
+ }
596
+ .foot {
597
+ display: flex;
598
+ align-items: center;
599
+ justify-content: space-between;
600
+ gap: 12px;
601
+ padding: 14px 18px;
602
+ border-top: 1px solid var(--ink);
603
+ }
604
+ .hint {
605
+ font-family: var(--font-mono);
606
+ font-size: 12px;
607
+ color: var(--ink-faint);
608
+ }
609
+ .primary {
610
+ font-family: var(--font-mono);
611
+ font-weight: 700;
612
+ font-size: 13px;
613
+ text-transform: uppercase;
614
+ letter-spacing: 0.06em;
615
+ padding: 9px 16px;
616
+ border: 1px solid var(--ink);
617
+ background: var(--ink);
618
+ color: var(--bg);
619
+ cursor: pointer;
620
+ white-space: nowrap;
621
+ }
622
+ .primary:hover:not(:disabled) {
623
+ background: var(--bg-card);
624
+ color: var(--ink);
625
+ }
626
+ .primary:disabled {
627
+ opacity: 0.4;
628
+ cursor: not-allowed;
629
+ }
630
+ .icon {
631
+ display: inline-flex;
632
+ align-items: center;
633
+ color: var(--ink-soft);
634
+ }
635
+ </style>