codebay 0.3.0 → 0.4.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.
- package/.mochi/manifest.json +297 -269
- package/.mochi/svelte-client/HydratableIsland-79z9xq4b.js +1 -0
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-f5rzyjm8.js +2 -0
- package/.mochi/svelte-client/{_hydrate-AvatarsView_257s9yq1wwr4f-3w16y9jh.js → _hydrate-AvatarsView_257s9yq1wwr4f-fxyd5qx4.js} +2 -2
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-nv31ptdp.js +1 -0
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-r9vvc7rx.js +28 -0
- package/.mochi/svelte-client/chunk-6eexkr6p.js +2 -0
- package/.mochi/svelte-client/chunk-7znwaf7t.js +2 -0
- package/.mochi/svelte-client/chunk-dz1019cy.js +1 -0
- package/.mochi/svelte-client/{chunk-qxwt25hn.js → chunk-nmf4dq86.js} +1 -1
- package/.mochi/svelte-client/{chunk-0jtmkpx4.js → chunk-ssa0ad2f.js} +2 -2
- package/.mochi/svelte-client/chunk-zmmb1c4b.js +2 -0
- package/.mochi/svelte-compile/{App-f0q5n4rk.server.js → App-9qjy14bc.server.js} +38 -23
- package/.mochi/svelte-compile/{Avatars-p8gfef7x.server.js → Avatars-sq8mjn6k.server.js} +2 -2
- package/.mochi/svelte-compile/{Instance-atjwsczx.server.js → Instance-13j59daw.server.js} +2 -2
- package/.mochi/svelte-compile/{Settings-q88xw6h8.server.js → Settings-p0s2awg1.server.js} +404 -103
- package/.mochi/svelte-compile/{chunk-9bjk7yx6.js → chunk-6ghzacpg.js} +1 -1
- package/.mochi/svelte-compile/{chunk-4kwy539a.js → chunk-bacb4b0r.js} +1 -1
- package/.mochi/svelte-compile/{chunk-sxqys3nw.js → chunk-dfe2km6a.js} +5 -2
- package/.mochi/svelte-compile/{chunk-v6796ga4.js → chunk-fpe16ywq.js} +28 -6
- package/.mochi/svelte-css/Brand-1bh2b800g5vrn.css +1 -0
- package/.mochi/svelte-css/SettingsView-3uycxa8z548m4.css +1 -0
- package/README.md +4 -0
- package/package.json +8 -8
- package/src/avatars/index.ts +5 -0
- package/src/components/AppBar.svelte +8 -1
- package/src/components/AppShell.svelte +17 -3
- package/src/components/Brand.svelte +15 -4
- package/src/components/DashboardView.svelte +5 -2
- package/src/components/IdeBar.svelte +9 -1
- package/src/components/InstanceCard.svelte +7 -3
- package/src/components/InstanceView.svelte +4 -1
- package/src/components/SettingsCog.svelte +9 -1
- package/src/components/SettingsView.svelte +476 -18
- package/src/components/TopBar.svelte +4 -1
- package/src/container-injections/claude-code-models.ts +74 -0
- package/src/container-injections/git-identity.ts +30 -3
- package/src/lib/injections.server.ts +3 -0
- package/src/lib/instances.server.ts +10 -1
- package/src/lib/popup-nav.ts +37 -0
- package/src/pages/App.svelte +10 -3
- package/src/pages/Settings.svelte +31 -0
- package/src/routes.ts +88 -2
- package/.mochi/svelte-client/HydratableIsland-wrq79y0w.js +0 -1
- package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-faqf4hmc.js +0 -2
- package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-5g8meb6a.js +0 -1
- package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-jsw4edxb.js +0 -23
- package/.mochi/svelte-client/chunk-04qv4w6w.js +0 -2
- package/.mochi/svelte-client/chunk-597d17x0.js +0 -1
- package/.mochi/svelte-client/chunk-5p31aqh0.js +0 -2
- package/.mochi/svelte-client/chunk-gjjhya9m.js +0 -2
- package/.mochi/svelte-css/Brand-1la7zw5uhcbl4.css +0 -1
- package/.mochi/svelte-css/SettingsView-1nfdwnm16cpp2.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
Icon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-fpe16ywq.js";
|
|
5
5
|
|
|
6
6
|
// src/avatars/anchor.ts
|
|
7
7
|
var art = {
|
|
@@ -916,6 +916,9 @@ function fnv1a(s) {
|
|
|
916
916
|
function pickAvatar(id) {
|
|
917
917
|
return avatars[fnv1a(id) % avatars.length];
|
|
918
918
|
}
|
|
919
|
+
function findAvatar(name) {
|
|
920
|
+
return name ? avatars.find((a) => a.name === name) : undefined;
|
|
921
|
+
}
|
|
919
922
|
|
|
920
923
|
// src/components/Avatar.svelte
|
|
921
924
|
import"svelte/internal/flags/async";
|
|
@@ -1665,4 +1668,4 @@ function AppBar($$renderer, $$props) {
|
|
|
1665
1668
|
$$renderer.push(`<!----></header>`);
|
|
1666
1669
|
}
|
|
1667
1670
|
|
|
1668
|
-
export { OFF, ON, ROWS, COLS, avatars, Avatar, Button, Toaster, dist_default, AppBar };
|
|
1671
|
+
export { OFF, ON, ROWS, COLS, avatars, findAvatar, Avatar, Button, Toaster, dist_default, AppBar };
|
|
@@ -1770,7 +1770,7 @@ import { randomUUID } from "crypto";
|
|
|
1770
1770
|
// node_modules/mochi-framework/src/cache/cache-storage.ts
|
|
1771
1771
|
import { mkdirSync, rmSync } from "fs";
|
|
1772
1772
|
import { mkdir, open, readdir, rename, rm, stat, unlink } from "fs/promises";
|
|
1773
|
-
import { join, relative } from "path";
|
|
1773
|
+
import { join, relative, resolve } from "path";
|
|
1774
1774
|
|
|
1775
1775
|
// node_modules/mochi-framework/src/events.ts
|
|
1776
1776
|
var mochiEvents = pinGlobal("__mochi_events__", () => {
|
|
@@ -1799,6 +1799,7 @@ var mochiEvents = pinGlobal("__mochi_events__", () => {
|
|
|
1799
1799
|
});
|
|
1800
1800
|
|
|
1801
1801
|
// node_modules/mochi-framework/src/cache/cache-storage.ts
|
|
1802
|
+
var sweeperOwners = pinGlobal("__mochi_cache_sweeper_owners__", () => new Map);
|
|
1802
1803
|
function isBlobRef(value) {
|
|
1803
1804
|
return typeof value === "object" && value !== null && value.__mochiBlobRef === true;
|
|
1804
1805
|
}
|
|
@@ -1927,6 +1928,8 @@ class FileStorage {
|
|
|
1927
1928
|
offloadBinary;
|
|
1928
1929
|
initialTimer;
|
|
1929
1930
|
intervalTimer;
|
|
1931
|
+
sweepKey;
|
|
1932
|
+
sweepOwner;
|
|
1930
1933
|
constructor(options) {
|
|
1931
1934
|
this.directory = options.directory;
|
|
1932
1935
|
this.maxAge = options.maxAge ?? 600000;
|
|
@@ -2080,7 +2083,7 @@ class FileStorage {
|
|
|
2080
2083
|
}));
|
|
2081
2084
|
return options.reportKeys ? { removed, removedKeys } : { removed };
|
|
2082
2085
|
}
|
|
2083
|
-
|
|
2086
|
+
stopSweepTimers() {
|
|
2084
2087
|
if (this.initialTimer) {
|
|
2085
2088
|
clearTimeout(this.initialTimer);
|
|
2086
2089
|
}
|
|
@@ -2090,6 +2093,14 @@ class FileStorage {
|
|
|
2090
2093
|
this.initialTimer = undefined;
|
|
2091
2094
|
this.intervalTimer = undefined;
|
|
2092
2095
|
}
|
|
2096
|
+
dispose() {
|
|
2097
|
+
this.stopSweepTimers();
|
|
2098
|
+
if (this.sweepKey !== undefined && sweeperOwners.get(this.sweepKey) === this.sweepOwner) {
|
|
2099
|
+
sweeperOwners.delete(this.sweepKey);
|
|
2100
|
+
}
|
|
2101
|
+
this.sweepKey = undefined;
|
|
2102
|
+
this.sweepOwner = undefined;
|
|
2103
|
+
}
|
|
2093
2104
|
async readKey(filePath) {
|
|
2094
2105
|
try {
|
|
2095
2106
|
const parsed = JSON.parse(await Bun.file(filePath).text());
|
|
@@ -2162,10 +2173,15 @@ class FileStorage {
|
|
|
2162
2173
|
logger.warn(`Cache sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2163
2174
|
}
|
|
2164
2175
|
};
|
|
2176
|
+
const key = resolve(this.directory);
|
|
2177
|
+
sweeperOwners.get(key)?.stop();
|
|
2178
|
+
this.sweepKey = key;
|
|
2179
|
+
this.sweepOwner = { stop: () => this.stopSweepTimers() };
|
|
2165
2180
|
this.initialTimer = setTimeout(run2, 1000);
|
|
2166
2181
|
this.intervalTimer = setInterval(run2, intervalMs);
|
|
2167
2182
|
this.initialTimer.unref?.();
|
|
2168
2183
|
this.intervalTimer.unref?.();
|
|
2184
|
+
sweeperOwners.set(key, this.sweepOwner);
|
|
2169
2185
|
}
|
|
2170
2186
|
}
|
|
2171
2187
|
|
|
@@ -3057,13 +3073,13 @@ async function fetchImageSource(src, opts) {
|
|
|
3057
3073
|
} catch {
|
|
3058
3074
|
throw new ImageError(502, "Failed to fetch source image");
|
|
3059
3075
|
}
|
|
3060
|
-
const
|
|
3061
|
-
if (res.status >= 300 && res.status < 400 &&
|
|
3076
|
+
const location2 = res.headers.get("location");
|
|
3077
|
+
if (res.status >= 300 && res.status < 400 && location2) {
|
|
3062
3078
|
if (hop >= maxRedirects) {
|
|
3063
3079
|
throw new ImageError(502, "Too many redirects");
|
|
3064
3080
|
}
|
|
3065
3081
|
await res.body?.cancel();
|
|
3066
|
-
target = new URL(
|
|
3082
|
+
target = new URL(location2, url).href;
|
|
3067
3083
|
continue;
|
|
3068
3084
|
}
|
|
3069
3085
|
break;
|
|
@@ -3214,6 +3230,12 @@ function HydratableBoundary($$renderer, $$props) {
|
|
|
3214
3230
|
$$renderer2.push(`<!---->`);
|
|
3215
3231
|
});
|
|
3216
3232
|
}
|
|
3233
|
+
// src/lib/popup-nav.ts
|
|
3234
|
+
var POPUP_PARAM = "popup";
|
|
3235
|
+
function withPopupMarker(href) {
|
|
3236
|
+
return `${href}?${POPUP_PARAM}=1`;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3217
3239
|
// node_modules/@lucide/svelte/dist/Icon.svelte
|
|
3218
3240
|
import"svelte/internal/flags/async";
|
|
3219
3241
|
import * as $2 from "svelte/internal/server";
|
|
@@ -3292,4 +3314,4 @@ function Icon($$renderer, $$props) {
|
|
|
3292
3314
|
});
|
|
3293
3315
|
}
|
|
3294
3316
|
|
|
3295
|
-
export { emitIslandProps, isDev, cookies, HydratableBoundary, Icon };
|
|
3317
|
+
export { emitIslandProps, isDev, cookies, HydratableBoundary, Icon, withPopupMarker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.brand.svelte-94cd8h{display:inline-flex;font-family:var(--font-display);text-transform:uppercase;letter-spacing:.04em;color:inherit;text-decoration:none;align-items: center;gap:11px;font-size:22px;font-weight:700}.logo.svelte-94cd8h{display:inline-flex;align-items: center}.words.svelte-94cd8h{display:inline-flex;align-items:baseline;gap:11px}.tagline.svelte-94cd8h{font-family:var(--font-sans);letter-spacing:.08em;color:var(--ink-soft);font-size:12px;font-weight:500}@media (max-width:640px){.tagline.svelte-94cd8h{display:none}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.page.svelte-1n6ht58{display:flex;flex-direction:column;min-height:100vh}.title.svelte-1n6ht58{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft);align-items: center;padding:0 14px;font-size:12px}.content.svelte-1n6ht58{display:flex;flex-direction:column;flex:1;align-items: center;gap:16px;padding:32px 20px}.card.svelte-1n6ht58{background:var(--bg-card);border:1px solid var(--rule);width:100%;max-width:560px;height:max-content}.danger-card.svelte-1n6ht58{border-color:var(--danger)}.disabled-card.svelte-1n6ht58{opacity:.55;pointer-events:none}.danger-card.svelte-1n6ht58 .btn{white-space:nowrap;flex:none}.version.svelte-1n6ht58{font-family:var(--font-mono);letter-spacing:.08em;color:var(--ink-soft);font-size:11px}.shutting.svelte-1n6ht58{font-family:var(--font-mono);color:var(--danger);text-align:right;flex:none;max-width:200px;font-size:12px;line-height:1.4}.row.svelte-1n6ht58{display:flex;justify-content:space-between;align-items: center;gap:16px;padding:18px}.row.divided.svelte-1n6ht58{border-top:1px solid var(--rule);align-items: flex-start}.row.divided.svelte-1n6ht58 .btn{white-space:nowrap;flex:none}.sub.svelte-1n6ht58{padding:0 18px 14px}code.svelte-1n6ht58{font-family:var(--font-mono);background:var(--bg);border:1px solid var(--rule);border-radius:3px;padding:1px 4px;font-size:.92em}.label.svelte-1n6ht58{display:flex;color:var(--ink);align-items: flex-start;gap:12px;min-width:0}.label.svelte-1n6ht58>svg{flex:none}.text.svelte-1n6ht58{min-width:0}.name.svelte-1n6ht58{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;font-size:13px}.arch.svelte-1n6ht58{letter-spacing:.04em;color:var(--ink-soft);border:1px solid var(--rule);vertical-align:middle;border-radius:3px;margin-left:8px;padding:2px 6px;font-size:11px}.tip.svelte-1n6ht58{color:var(--ink-soft);background:color-mix(in srgb,var(--info)10%,transparent);border:1px solid color-mix(in srgb,var(--info)35%,transparent);border-radius:4px;width:100%;margin-top:4px;padding:8px 10px}.desc.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-faint);margin-top:4px;font-size:12px;line-height:1.4}.image-row.svelte-1n6ht58,.token-row.svelte-1n6ht58{flex-wrap:wrap}.image-controls.svelte-1n6ht58{display:flex;flex:1;justify-content:flex-end;align-items: center;gap:8px;min-width:220px}.image-input.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink);background:var(--bg);border:1px solid var(--rule);flex:1;min-width:0;padding:8px 10px;font-size:12px}.image-input.svelte-1n6ht58:focus-visible{outline:2px solid var(--ink);outline-offset:1px}.image-controls.svelte-1n6ht58 .btn{flex:none}.msg.svelte-1n6ht58{font-family:var(--font-mono);width:100%;margin-top:2px;font-size:12px;line-height:1.4}.msg.error.svelte-1n6ht58{color:var(--danger)}.msg.ok.svelte-1n6ht58{color:var(--ink-soft)}.pets.svelte-1n6ht58{display:grid;grid-template-columns:repeat(auto-fill,minmax(44px,1fr));gap:6px}.pets.svelte-1n6ht58 form:where(.svelte-1n6ht58){display:contents}.pet.svelte-1n6ht58:disabled{cursor:progress}.pet.svelte-1n6ht58{display:flex;appearance:none;cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center;align-items: center;padding:4px}.pet.svelte-1n6ht58:hover{border-color:var(--rule)}.pet.selected.svelte-1n6ht58{border-color:var(--attn-done);background:var(--switch-on-bg)}.pet.svelte-1n6ht58:focus-visible{outline:2px solid var(--ink);outline-offset:1px}.switch.svelte-1n6ht58{position:relative;cursor:pointer;flex:none}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58){position:absolute;opacity:0;cursor:pointer;margin:0;inset:0}.track.svelte-1n6ht58{display:block;border:1px solid var(--rule);background:var(--bg);border-radius:999px;width:44px;height:24px;transition:background .15s}.thumb.svelte-1n6ht58{display:block;background:var(--ink-faint);border-radius:999px;width:18px;height:18px;margin:2px;transition:transform .15s,background .15s}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):checked+.track:where(.svelte-1n6ht58){background:var(--switch-on-bg)}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):checked+.track:where(.svelte-1n6ht58) .thumb:where(.svelte-1n6ht58){background:var(--ink);transform:translate(20px)}.switch.svelte-1n6ht58 input:where(.svelte-1n6ht58):focus-visible+.track:where(.svelte-1n6ht58){outline:2px solid var(--ink);outline-offset:2px}.model-fields.svelte-1n6ht58{display:flex;flex-direction:column;flex:1;gap:6px;min-width:220px}.model-row.svelte-1n6ht58{display:flex;align-items: center;gap:8px}.model-label.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-soft);text-align:right;flex:none;width:72px;font-size:11px}.model-row.svelte-1n6ht58 .image-input:where(.svelte-1n6ht58){flex:1}.model-save-row.svelte-1n6ht58{display:flex;justify-content:flex-end;margin-top:4px}.var-row.svelte-1n6ht58{flex-direction:column;align-items:stretch;gap:10px}.var-list.svelte-1n6ht58{display:flex;flex-direction:column;gap:6px}.var-item.svelte-1n6ht58{display:flex;background:var(--bg);border:1px solid var(--rule);align-items: center;gap:8px;padding:6px 8px}.var-name.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink);word-break:break-all;flex:1;min-width:0;font-size:12px}.var-status.svelte-1n6ht58{font-family:var(--font-mono);letter-spacing:.04em;color:var(--danger);flex:none;font-size:11px}.var-status.present.svelte-1n6ht58{color:var(--ink-soft)}.var-remove.svelte-1n6ht58{display:inline-flex;color:var(--ink-soft);border:1px solid var(--rule);cursor:pointer;background:0 0;flex:none;justify-content:center;align-items: center;width:22px;height:22px;padding:0}.var-remove.svelte-1n6ht58:hover:not(:disabled){color:var(--danger);border-color:var(--danger)}.var-remove.svelte-1n6ht58:disabled{opacity:.4;cursor:not-allowed}.var-empty.svelte-1n6ht58{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.var-add.svelte-1n6ht58{display:flex;gap:8px}.var-add.svelte-1n6ht58 .btn{flex:none}
|
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ The UI opens at `http://localhost:6969`. State (SQLite DB + per-instance workspa
|
|
|
23
23
|
- `CODEBAY_GITHUB_TOKEN` — GitHub token to inject instead of reading `gh auth token` from the host
|
|
24
24
|
- `DISABLE_OPEN_BROWSER=1` — skip opening the browser on startup
|
|
25
25
|
|
|
26
|
+
## Troubleshooting
|
|
27
|
+
|
|
28
|
+
- `warn: incorrect peer dependency "svelte@5.56.8"` on startup — harmless. It comes from `svelte-french-toast`, whose published peer range predates Svelte 5; the library works correctly on Svelte 5. Nothing to fix.
|
|
29
|
+
|
|
26
30
|
## Development
|
|
27
31
|
|
|
28
32
|
```sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Web UI for spinning up isolated devcontainer instances from any local folder, each with a browser-based VS Code (code-server) — built for parallel Claude Code workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@devcontainers/cli": "^0.88.0",
|
|
63
|
-
"@fontsource-variable/doto": "^5.
|
|
64
|
-
"@fontsource-variable/jetbrains-mono": "^5.
|
|
65
|
-
"@lucide/svelte": "^1.
|
|
63
|
+
"@fontsource-variable/doto": "^5.3.0",
|
|
64
|
+
"@fontsource-variable/jetbrains-mono": "^5.3.0",
|
|
65
|
+
"@lucide/svelte": "^1.28.0",
|
|
66
66
|
"@zihaolam/bun-sqlite-migrations": "^1.0.3",
|
|
67
67
|
"dockerode": "^5.0.1",
|
|
68
|
-
"mochi-framework": "^0.9.
|
|
68
|
+
"mochi-framework": "^0.9.1",
|
|
69
69
|
"svelte": "^5.56.8",
|
|
70
70
|
"svelte-french-toast": "^1.2.0"
|
|
71
71
|
},
|
|
@@ -74,13 +74,13 @@
|
|
|
74
74
|
"@types/bun": "1.3.14",
|
|
75
75
|
"@types/dockerode": "^4.0.1",
|
|
76
76
|
"eslint": "^10.8.0",
|
|
77
|
-
"eslint-plugin-svelte": "^3.
|
|
77
|
+
"eslint-plugin-svelte": "^3.22.0",
|
|
78
78
|
"globals": "^17.8.0",
|
|
79
|
-
"prettier": "^3.9.
|
|
79
|
+
"prettier": "^3.9.6",
|
|
80
80
|
"prettier-plugin-svelte": "^4.1.1",
|
|
81
81
|
"svelte-check": "4.7.4",
|
|
82
82
|
"typescript": "^6.0.3",
|
|
83
|
-
"typescript-eslint": "^8.
|
|
83
|
+
"typescript-eslint": "^8.65.0"
|
|
84
84
|
},
|
|
85
85
|
"resolutions": {
|
|
86
86
|
"msgpackr-extract": "npm:@mochi-framework/msgpackr-extract-stub@*",
|
package/src/avatars/index.ts
CHANGED
|
@@ -124,5 +124,10 @@ export function pickAvatar(id: string): AvatarArt {
|
|
|
124
124
|
return avatars[fnv1a(id) % avatars.length]!;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/** Lookup by name, for sprites chosen by hand rather than hashed from an id. */
|
|
128
|
+
export function findAvatar(name: string | undefined): AvatarArt | undefined {
|
|
129
|
+
return name ? avatars.find((a) => a.name === name) : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
export { decode } from './types.ts';
|
|
128
133
|
export type { AvatarArt } from './types.ts';
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import House from '@lucide/svelte/icons/house';
|
|
4
|
+
import { onBackLinkClick } from '../lib/popup-nav.ts';
|
|
4
5
|
|
|
5
6
|
let { children }: { children?: Snippet } = $props();
|
|
6
7
|
</script>
|
|
7
8
|
|
|
8
9
|
<header class="bar">
|
|
9
|
-
<a
|
|
10
|
+
<a
|
|
11
|
+
class="home"
|
|
12
|
+
href="/"
|
|
13
|
+
title="All instances"
|
|
14
|
+
aria-label="All instances"
|
|
15
|
+
onclick={onBackLinkClick}><House size={18} /></a
|
|
16
|
+
>
|
|
10
17
|
{@render children?.()}
|
|
11
18
|
</header>
|
|
12
19
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { ideUrl, type Instance, type Preflight } from '../types.ts';
|
|
3
|
+
import { findAvatar, type AvatarArt } from '../avatars/index.ts';
|
|
3
4
|
import { SvelteSet } from 'svelte/reactivity';
|
|
4
5
|
import DashboardView from './DashboardView.svelte';
|
|
5
6
|
import IdeBar from './IdeBar.svelte';
|
|
@@ -13,8 +14,14 @@
|
|
|
13
14
|
let {
|
|
14
15
|
preflight,
|
|
15
16
|
initialPath,
|
|
16
|
-
snapshot
|
|
17
|
-
|
|
17
|
+
snapshot,
|
|
18
|
+
pet
|
|
19
|
+
}: {
|
|
20
|
+
preflight: Preflight;
|
|
21
|
+
initialPath: string;
|
|
22
|
+
snapshot: Instance[];
|
|
23
|
+
pet?: AvatarArt;
|
|
24
|
+
} = $props();
|
|
18
25
|
|
|
19
26
|
// Seeding from the SSR snapshot is intentional — the live stream overwrites it.
|
|
20
27
|
// svelte-ignore state_referenced_locally
|
|
@@ -22,6 +29,9 @@
|
|
|
22
29
|
// Auth is preserved across stream updates because it's only ever probed at SSR.
|
|
23
30
|
// svelte-ignore state_referenced_locally
|
|
24
31
|
let livePreflight = $state<Preflight>(preflight);
|
|
32
|
+
// Seeded from SSR, then updated live so changing the pet in settings swaps the header without a reload.
|
|
33
|
+
// svelte-ignore state_referenced_locally
|
|
34
|
+
let livePet = $state<AvatarArt | undefined>(pet);
|
|
25
35
|
// svelte-ignore state_referenced_locally
|
|
26
36
|
let loaded = $state(snapshot.length > 0);
|
|
27
37
|
const running = $derived(instances.filter((i) => i.status === 'running'));
|
|
@@ -147,6 +157,10 @@
|
|
|
147
157
|
livePreflight = { ...livePreflight, docker: msg.data.docker, cli: msg.data.cli };
|
|
148
158
|
return;
|
|
149
159
|
}
|
|
160
|
+
if (msg.type === 'pet') {
|
|
161
|
+
livePet = findAvatar(msg.data.name ?? undefined);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
150
164
|
if (msg.type === 'health') {
|
|
151
165
|
// A tick in flight when a rebuild starts probes the *old* container and
|
|
152
166
|
// reports it accessible, which would mount the iframe against the replacement too early.
|
|
@@ -223,7 +237,7 @@
|
|
|
223
237
|
oncancelrename={cancelRename}
|
|
224
238
|
/>
|
|
225
239
|
{:else}
|
|
226
|
-
<DashboardView preflight={livePreflight} {instances} {loaded} />
|
|
240
|
+
<DashboardView preflight={livePreflight} {instances} {loaded} pet={livePet} />
|
|
227
241
|
{/if}
|
|
228
242
|
|
|
229
243
|
<!-- Always mounted, only hidden, so the iframes survive navigation. -->
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Package from '@lucide/svelte/icons/package';
|
|
3
|
+
import Avatar from './Avatar.svelte';
|
|
4
|
+
import type { AvatarArt } from '../avatars/index.ts';
|
|
5
|
+
|
|
6
|
+
// Resolved server-side and threaded through props, so SSR and hydration agree and the logo can't flash.
|
|
7
|
+
let { pet }: { pet?: AvatarArt } = $props();
|
|
3
8
|
</script>
|
|
4
9
|
|
|
5
10
|
<a class="brand" href="/"
|
|
6
|
-
><span class="logo"
|
|
7
|
-
>
|
|
11
|
+
><span class="logo"
|
|
12
|
+
>{#if pet}<Avatar art={pet} name={pet.name} scale={4} />{:else}<Package size={22} />{/if}</span
|
|
13
|
+
><span class="words"><span>Codebay</span><span class="tagline">devcontainer manager</span></span
|
|
8
14
|
></a
|
|
9
15
|
>
|
|
10
16
|
|
|
11
17
|
<style>
|
|
12
18
|
.brand {
|
|
13
19
|
display: inline-flex;
|
|
14
|
-
align-items:
|
|
20
|
+
align-items: center;
|
|
15
21
|
gap: 11px;
|
|
16
22
|
font-family: var(--font-display);
|
|
17
23
|
font-weight: 700;
|
|
@@ -24,7 +30,12 @@
|
|
|
24
30
|
.logo {
|
|
25
31
|
display: inline-flex;
|
|
26
32
|
align-items: center;
|
|
27
|
-
|
|
33
|
+
}
|
|
34
|
+
/* Keep the wordmark and tagline on a shared baseline, but center the group against the logo. */
|
|
35
|
+
.words {
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: baseline;
|
|
38
|
+
gap: 11px;
|
|
28
39
|
}
|
|
29
40
|
.tagline {
|
|
30
41
|
font-family: var(--font-sans);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { type Instance, type Preflight } from '../types.ts';
|
|
3
|
+
import type { AvatarArt } from '../avatars/index.ts';
|
|
3
4
|
import FolderBrowser from './FolderBrowser.svelte';
|
|
4
5
|
import InstanceCard from './InstanceCard.svelte';
|
|
5
6
|
import TopBar from './TopBar.svelte';
|
|
@@ -11,8 +12,9 @@
|
|
|
11
12
|
let {
|
|
12
13
|
preflight,
|
|
13
14
|
instances,
|
|
14
|
-
loaded
|
|
15
|
-
|
|
15
|
+
loaded,
|
|
16
|
+
pet
|
|
17
|
+
}: { preflight: Preflight; instances: Instance[]; loaded: boolean; pet?: AvatarArt } = $props();
|
|
16
18
|
|
|
17
19
|
let browserOpen = $state(false);
|
|
18
20
|
let creating = $state(false);
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
|
|
100
102
|
<TopBar
|
|
101
103
|
auth={preflight.auth}
|
|
104
|
+
{pet}
|
|
102
105
|
canDelete={instances.length > 0}
|
|
103
106
|
{ready}
|
|
104
107
|
{creating}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Settings from '@lucide/svelte/icons/settings';
|
|
4
4
|
import Avatar from './Avatar.svelte';
|
|
5
5
|
import AppBar from './AppBar.svelte';
|
|
6
|
+
import { withPopupMarker } from '../lib/popup-nav.ts';
|
|
6
7
|
|
|
7
8
|
let {
|
|
8
9
|
running,
|
|
@@ -69,7 +70,14 @@
|
|
|
69
70
|
{/each}
|
|
70
71
|
</nav>
|
|
71
72
|
{/if}
|
|
72
|
-
<a
|
|
73
|
+
<a
|
|
74
|
+
class="cog"
|
|
75
|
+
href={withPopupMarker('/settings')}
|
|
76
|
+
target="_blank"
|
|
77
|
+
rel="noopener noreferrer"
|
|
78
|
+
title="Settings"
|
|
79
|
+
aria-label="Settings"><Settings size={18} /></a
|
|
80
|
+
>
|
|
73
81
|
</AppBar>
|
|
74
82
|
|
|
75
83
|
<style>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import PortsBox from './PortsBox.svelte';
|
|
6
6
|
import StatusBadge from './StatusBadge.svelte';
|
|
7
7
|
import Button from './Button.svelte';
|
|
8
|
+
import { withPopupMarker } from '../lib/popup-nav.ts';
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
instance,
|
|
@@ -81,8 +82,11 @@
|
|
|
81
82
|
{:else if instance.status === 'stopped' || (instance.status === 'error' && instance.container_id)}
|
|
82
83
|
<Button size="sm" onclick={() => onact('start')}>Start</Button>
|
|
83
84
|
{:else if instance.status === 'creating'}
|
|
84
|
-
<Button
|
|
85
|
-
|
|
85
|
+
<Button
|
|
86
|
+
size="sm"
|
|
87
|
+
href={withPopupMarker(`/instances/${instance.id}`)}
|
|
88
|
+
target="_blank"
|
|
89
|
+
rel="noopener noreferrer">View logs</Button
|
|
86
90
|
>
|
|
87
91
|
{/if}
|
|
88
92
|
{#if canRebuild}
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
<Button
|
|
96
100
|
size="sm"
|
|
97
101
|
ghost
|
|
98
|
-
href={`/instances/${instance.id}`}
|
|
102
|
+
href={withPopupMarker(`/instances/${instance.id}`)}
|
|
99
103
|
target="_blank"
|
|
100
104
|
rel="noopener noreferrer">Details</Button
|
|
101
105
|
>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { apiPost, apiDelete } from '../api.ts';
|
|
11
11
|
import { enhance } from 'mochi-framework';
|
|
12
12
|
import type { MochiEnhanceOptions } from 'mochi-framework';
|
|
13
|
+
import { onBackLinkClick, installPopupBackTrap } from '../lib/popup-nav.ts';
|
|
13
14
|
|
|
14
15
|
let { id, injectionChecks = 0 }: { id: string; injectionChecks?: number } = $props();
|
|
15
16
|
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
node.scrollTop = node.scrollHeight;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
$effect(() => installPopupBackTrap());
|
|
29
|
+
|
|
27
30
|
// Reset on every connect, because the server replays its whole buffer each time.
|
|
28
31
|
$effect(() =>
|
|
29
32
|
liveSocket(
|
|
@@ -120,7 +123,7 @@
|
|
|
120
123
|
</script>
|
|
121
124
|
|
|
122
125
|
<header class="topbar">
|
|
123
|
-
<a class="back" href="/"><ArrowLeft size={15} /> All instances</a>
|
|
126
|
+
<a class="back" href="/" onclick={onBackLinkClick}><ArrowLeft size={15} /> All instances</a>
|
|
124
127
|
<div class="title">
|
|
125
128
|
<span class="name">{instance?.name ?? 'Instance'}</span>
|
|
126
129
|
{#if instance}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Settings from '@lucide/svelte/icons/settings';
|
|
3
|
+
import { withPopupMarker } from '../lib/popup-nav.ts';
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
|
-
<a
|
|
6
|
+
<a
|
|
7
|
+
class="cog"
|
|
8
|
+
href={withPopupMarker('/settings')}
|
|
9
|
+
target="_blank"
|
|
10
|
+
rel="noopener noreferrer"
|
|
11
|
+
aria-label="Settings"
|
|
12
|
+
title="Settings"><Settings size={16} /></a
|
|
13
|
+
>
|
|
6
14
|
|
|
7
15
|
<style>
|
|
8
16
|
.cog {
|