codebay 0.8.0 → 0.9.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 (79) hide show
  1. package/.mochi/import-css/xterm-1yy1hf1g.css +252 -0
  2. package/.mochi/manifest.json +330 -211
  3. package/.mochi/svelte-client/HydratableIsland-k6q8esj4.js +1 -0
  4. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-sg96rp17.js +2 -0
  5. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-92hepzby.js +14 -0
  6. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-p3f649gk.js +1 -0
  7. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-dzmgmv78.js +29 -0
  8. package/.mochi/svelte-client/chunk-2j95sv83.js +1 -0
  9. package/.mochi/svelte-client/chunk-6rbs0mhx.js +1 -0
  10. package/.mochi/svelte-client/chunk-7wkvn2vx.js +2 -0
  11. package/.mochi/svelte-client/chunk-a2psjmrm.js +2 -0
  12. package/.mochi/svelte-client/chunk-m497q88s.js +2 -0
  13. package/.mochi/svelte-client/{chunk-fqg2qzj8.js → chunk-m4fzsd5b.js} +1 -1
  14. package/.mochi/svelte-client/chunk-mpv3qx28.js +36 -0
  15. package/.mochi/svelte-client/chunk-rs41by6w.js +2 -0
  16. package/.mochi/svelte-client/{chunk-qj7zy2d9.js → chunk-v65n8th6.js} +4 -4
  17. package/.mochi/svelte-compile/{App-6rcf0d74.server.js → App-7vjcp38v.server.js} +368 -37
  18. package/.mochi/svelte-compile/{Avatars-pzyz0tv3.server.js → Avatars-e2cfc95t.server.js} +130 -25
  19. package/.mochi/svelte-compile/{ClientStats-z3tn9ad6.server.js → ClientStats-4cphmrnp.server.js} +1 -1
  20. package/.mochi/svelte-compile/{DefaultError-wh77vebf.server.js → DefaultError-je52k17d.server.js} +1 -1
  21. package/.mochi/svelte-compile/{Instance-49ksrp11.server.js → Instance-h78n2dsv.server.js} +16 -13
  22. package/.mochi/svelte-compile/{Settings-sz5d6qj7.server.js → Settings-tav2asn7.server.js} +153 -127
  23. package/.mochi/svelte-compile/{chunk-fpe16ywq.js → chunk-60mpagr9.js} +17 -7
  24. package/.mochi/svelte-compile/chunk-9nqc2r0y.js +9706 -0
  25. package/.mochi/svelte-compile/{chunk-6ghzacpg.js → chunk-bpqwf1nw.js} +36 -4
  26. package/.mochi/svelte-compile/chunk-jx6h1xzp.js +20 -0
  27. package/.mochi/svelte-compile/{chunk-q8rbesaw.js → chunk-m8ygpyhr.js} +5 -4
  28. package/.mochi/svelte-compile/chunk-rtdne1m8.js +31 -0
  29. package/.mochi/svelte-compile/{chunk-we0ggtws.js → chunk-xpt1a7jm.js} +2 -1
  30. package/.mochi/svelte-css/AvatarEditor-2mdq8ss346drw.css +1 -0
  31. package/.mochi/svelte-css/{AvatarsView-2ughbzc26luyb.css → AvatarsView-2llo48w0wt0xn.css} +1 -1
  32. package/.mochi/svelte-css/FolderBrowser-2pxp9z1o7zixk.css +1 -0
  33. package/.mochi/svelte-css/{IdeBar-2qoqi2ya9zdn3.css → IdeBar-10xodudcx57qr.css} +1 -1
  34. package/.mochi/svelte-css/{SettingsView-3uycxa8z548m4.css → SettingsView-2qfauz68axro1.css} +1 -1
  35. package/.mochi/svelte-css/TerminalPane-3bwgk6ulzq6b3.css +1 -0
  36. package/.mochi/svelte-css/TerminalSplit-2416xfuln89a3.css +1 -0
  37. package/migrations/0005_instance_mode.sql +1 -0
  38. package/migrations/0006_terminal_split.sql +1 -0
  39. package/package.json +3 -1
  40. package/src/avatars/serialize.ts +35 -6
  41. package/src/components/AppShell.svelte +46 -8
  42. package/src/components/Avatar.svelte +3 -2
  43. package/src/components/AvatarEditor.svelte +96 -19
  44. package/src/components/AvatarsView.svelte +0 -0
  45. package/src/components/DashboardView.svelte +22 -7
  46. package/src/components/FolderBrowser.svelte +88 -8
  47. package/src/components/HealthBox.svelte +12 -5
  48. package/src/components/IdeBar.svelte +10 -0
  49. package/src/components/InstanceCard.svelte +3 -1
  50. package/src/components/InstanceView.svelte +1 -0
  51. package/src/components/SettingsView.svelte +108 -0
  52. package/src/components/TerminalPane.svelte +274 -0
  53. package/src/components/TerminalSplit.svelte +154 -0
  54. package/src/container-injections/claude-code-ide-extension.ts +2 -0
  55. package/src/container-injections/ttyd.ts +49 -0
  56. package/src/lib/config.server.ts +3 -0
  57. package/src/lib/db.server.ts +14 -4
  58. package/src/lib/devcontainer.server.ts +136 -22
  59. package/src/lib/health.server.ts +1 -1
  60. package/src/lib/injections.server.ts +15 -5
  61. package/src/lib/instances.server.ts +64 -10
  62. package/src/lib/proxy.server.ts +87 -11
  63. package/src/pages/App.svelte +5 -3
  64. package/src/pages/Settings.svelte +4 -0
  65. package/src/routes.ts +55 -6
  66. package/src/types.ts +18 -0
  67. package/.mochi/svelte-client/HydratableIsland-qyjx1d98.js +0 -1
  68. package/.mochi/svelte-client/_hydrate-AppShell_2rtnxn6pgp2rh-fkna7bj2.js +0 -2
  69. package/.mochi/svelte-client/_hydrate-AvatarsView_257s9yq1wwr4f-89wtkb86.js +0 -13
  70. package/.mochi/svelte-client/_hydrate-InstanceView_2ma45l9bybfhl-eamnbyqk.js +0 -1
  71. package/.mochi/svelte-client/_hydrate-SettingsView_8hmarwmf8onq-kdra4mcb.js +0 -28
  72. package/.mochi/svelte-client/chunk-330mrspw.js +0 -2
  73. package/.mochi/svelte-client/chunk-4jgkak13.js +0 -1
  74. package/.mochi/svelte-client/chunk-k5m4kt20.js +0 -2
  75. package/.mochi/svelte-client/chunk-rpdphch7.js +0 -2
  76. package/.mochi/svelte-compile/chunk-bacb4b0r.js +0 -14
  77. package/.mochi/svelte-css/AvatarEditor-2ewwtejaml4e2.css +0 -1
  78. package/.mochi/svelte-css/FolderBrowser-1x428zb3f2kti.css +0 -1
  79. /package/.mochi/svelte-compile/{chunk-btmcxhqy.js → chunk-brjv9pyd.js} +0 -0
@@ -1,15 +1,47 @@
1
1
  // @bun
2
2
  import {
3
3
  Icon
4
- } from "./chunk-fpe16ywq.js";
4
+ } from "./chunk-60mpagr9.js";
5
5
 
6
- // node_modules/@lucide/svelte/dist/icons/plus.svelte
6
+ // node_modules/@lucide/svelte/dist/icons/terminal.svelte
7
7
  import"svelte/internal/flags/async";
8
8
  import * as $ from "svelte/internal/server";
9
+ function Terminal($$renderer, $$props) {
10
+ let { $$slots, $$events, ...props } = $$props;
11
+ const iconNode = [
12
+ ["path", { d: "M12 19h8" }],
13
+ ["path", { d: "m4 17 6-6-6-6" }]
14
+ ];
15
+ Icon($$renderer, $.spread_props([{ name: "terminal" }, props, { iconNode }]));
16
+ }
17
+ // node_modules/@lucide/svelte/dist/icons/layout-template.svelte
18
+ import"svelte/internal/flags/async";
19
+ import * as $2 from "svelte/internal/server";
20
+ function Layout_template($$renderer, $$props) {
21
+ let { $$slots, $$events, ...props } = $$props;
22
+ const iconNode = [
23
+ [
24
+ "rect",
25
+ { width: "18", height: "7", x: "3", y: "3", rx: "1" }
26
+ ],
27
+ [
28
+ "rect",
29
+ { width: "9", height: "7", x: "3", y: "14", rx: "1" }
30
+ ],
31
+ [
32
+ "rect",
33
+ { width: "5", height: "7", x: "16", y: "14", rx: "1" }
34
+ ]
35
+ ];
36
+ Icon($$renderer, $2.spread_props([{ name: "layout-template" }, props, { iconNode }]));
37
+ }
38
+ // node_modules/@lucide/svelte/dist/icons/plus.svelte
39
+ import"svelte/internal/flags/async";
40
+ import * as $3 from "svelte/internal/server";
9
41
  function Plus($$renderer, $$props) {
10
42
  let { $$slots, $$events, ...props } = $$props;
11
43
  const iconNode = [["path", { d: "M5 12h14" }], ["path", { d: "M12 5v14" }]];
12
- Icon($$renderer, $.spread_props([{ name: "plus" }, props, { iconNode }]));
44
+ Icon($$renderer, $3.spread_props([{ name: "plus" }, props, { iconNode }]));
13
45
  }
14
46
  // src/settings.ts
15
47
  var SOUND_KEY = "codebay.sound";
@@ -78,4 +110,4 @@ function playChime(state) {
78
110
  el.play().then(() => console.log(`[chime] playChime(${state}) \u2014 playing`)).catch((err) => console.warn(`[chime] playChime(${state}) \u2014 play rejected:`, err?.message ?? err));
79
111
  }
80
112
 
81
- export { Plus, soundEnabled, setSoundEnabled, unlockAudio, playChime };
113
+ export { Terminal, Layout_template, Plus, soundEnabled, setSoundEnabled, unlockAudio, playChime };
@@ -0,0 +1,20 @@
1
+ // @bun
2
+ import {
3
+ Icon
4
+ } from "./chunk-60mpagr9.js";
5
+
6
+ // node_modules/@lucide/svelte/dist/icons/rotate-ccw.svelte
7
+ import"svelte/internal/flags/async";
8
+ import * as $ from "svelte/internal/server";
9
+ function Rotate_ccw($$renderer, $$props) {
10
+ let { $$slots, $$events, ...props } = $$props;
11
+ const iconNode = [
12
+ [
13
+ "path",
14
+ { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }
15
+ ],
16
+ ["path", { d: "M3 3v5h5" }]
17
+ ];
18
+ Icon($$renderer, $.spread_props([{ name: "rotate-ccw" }, props, { iconNode }]));
19
+ }
20
+ export { Rotate_ccw };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  Icon
4
- } from "./chunk-fpe16ywq.js";
4
+ } from "./chunk-60mpagr9.js";
5
5
 
6
6
  // src/avatars/anchor.ts
7
7
  var art = {
@@ -1166,7 +1166,8 @@ function Avatar($$renderer, $$props) {
1166
1166
  }
1167
1167
  const s = $.derived(() => Math.max(1, Math.round(scale)));
1168
1168
  const gap = $.derived(() => s() >= 2 ? 0.5 : 0);
1169
- const cells = $.derived(() => decode(art63 ?? pickAvatar(id)));
1169
+ const resolved = $.derived(() => art63 ?? pickAvatar(id));
1170
+ const cells = $.derived(() => decode(resolved()));
1170
1171
  const grid = $.derived(() => Array.from({ length: 100 }, (_, i) => {
1171
1172
  const r = Math.floor(i / 10);
1172
1173
  const c = i % 10;
@@ -1177,7 +1178,7 @@ function Avatar($$renderer, $$props) {
1177
1178
  interactive,
1178
1179
  pressed,
1179
1180
  ghosting
1180
- })} role="img"${$.attr("aria-label", name)}${$.attr("title", name)}${$.attr_style(`width:${$.stringify(10 * s())}px;height:${$.stringify(10 * s())}px;--gap:${$.stringify(gap())}px;--bx:${$.stringify(bx)};--by:${$.stringify(by)};--spin:${$.stringify(spin)}deg;--hue:${$.stringify(hue)}deg`)}><!--[-->`);
1181
+ })} role="img"${$.attr("aria-label", name)}${$.attr("title", resolved().name)}${$.attr_style(`width:${$.stringify(10 * s())}px;height:${$.stringify(10 * s())}px;--gap:${$.stringify(gap())}px;--bx:${$.stringify(bx)};--by:${$.stringify(by)};--spin:${$.stringify(spin)}deg;--hue:${$.stringify(hue)}deg`)}><!--[-->`);
1181
1182
  const each_array = $.ensure_array_like(grid());
1182
1183
  for (let i = 0, $$length = each_array.length;i < $$length; i++) {
1183
1184
  let cell = each_array[i];
@@ -1869,4 +1870,4 @@ function AppBar($$renderer, $$props) {
1869
1870
  $$renderer.push(`<!----></header>`);
1870
1871
  }
1871
1872
 
1872
- export { OFF, ON, GRAY, ROWS, COLS, avatars, findAvatar, Avatar, Button, Toaster, dist_default, AppBar };
1873
+ export { OFF, ON, GRAY, ROWS, COLS, decode, avatars, findAvatar, Avatar, Button, Toaster, dist_default, AppBar };
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ import"./chunk-xpt1a7jm.js";
3
+
4
+ // node_modules/@xterm/addon-fit/lib/addon-fit.mjs
5
+ var h = 2;
6
+ var _ = 1;
7
+ var o = class {
8
+ activate(e) {
9
+ this._terminal = e;
10
+ }
11
+ dispose() {}
12
+ fit() {
13
+ let e = this.proposeDimensions();
14
+ if (!e || !this._terminal || isNaN(e.cols) || isNaN(e.rows))
15
+ return;
16
+ let t = this._terminal._core;
17
+ (this._terminal.rows !== e.rows || this._terminal.cols !== e.cols) && (t._renderService.clear(), this._terminal.resize(e.cols, e.rows));
18
+ }
19
+ proposeDimensions() {
20
+ if (!this._terminal || !this._terminal.element || !this._terminal.element.parentElement)
21
+ return;
22
+ let t = this._terminal._core._renderService.dimensions;
23
+ if (t.css.cell.width === 0 || t.css.cell.height === 0)
24
+ return;
25
+ let s = this._terminal.options.scrollback === 0 ? 0 : this._terminal.options.overviewRuler?.width || 14, r = window.getComputedStyle(this._terminal.element.parentElement), l = parseInt(r.getPropertyValue("height")), a = Math.max(0, parseInt(r.getPropertyValue("width"))), i = window.getComputedStyle(this._terminal.element), n = { top: parseInt(i.getPropertyValue("padding-top")), bottom: parseInt(i.getPropertyValue("padding-bottom")), right: parseInt(i.getPropertyValue("padding-right")), left: parseInt(i.getPropertyValue("padding-left")) }, m = n.top + n.bottom, d = n.right + n.left, c = l - m, p = a - d - s;
26
+ return { cols: Math.max(h, Math.floor(p / t.css.cell.width)), rows: Math.max(_, Math.floor(c / t.css.cell.height)) };
27
+ }
28
+ };
29
+ export {
30
+ o as FitAddon
31
+ };
@@ -30,5 +30,6 @@ var __toESM = (mod, isNodeMode, target) => {
30
30
  return to;
31
31
  };
32
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = import.meta.require;
33
34
 
34
- export { __toESM, __commonJS };
35
+ export { __toESM, __commonJS, __require };
@@ -0,0 +1 @@
1
+ .editor.svelte-qu6vp4{display:flex;background:var(--bg-card);border:1px solid var(--rule);overflow:hidden;flex-direction:column;width:100%}.head.svelte-qu6vp4{display:flex;border-bottom:1px solid var(--ink);background:var(--ink);justify-content:space-between;align-items: center;gap:12px;padding:14px 18px}.head.svelte-qu6vp4 h2:where(.svelte-qu6vp4){font-family:var(--font-display);text-transform:uppercase;letter-spacing:.06em;color:var(--bg);margin:0;font-size:17px;font-weight:700}.head-cancel.svelte-qu6vp4{display:inline-flex;border:1px solid var(--bg);color:var(--bg);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;cursor:pointer;background:0 0;flex:none;align-items: center;gap:6px;padding:5px 10px;font-size:11px}.head-cancel.svelte-qu6vp4:hover{background:var(--bg);color:var(--ink)}.head-cancel.svelte-qu6vp4:focus-visible{outline:none;box-shadow:0 0 0 2px var(--rule-soft)}.body.svelte-qu6vp4{display:flex;flex-wrap:wrap;gap:18px;padding:18px}.grid-frame.svelte-qu6vp4{display:grid;grid-template-columns:repeat(10,40px);grid-template-rows:repeat(10,40px);border:1px solid var(--ink);background:var(--bg);flex:none;width:400px;height:400px}.ring-cell.svelte-qu6vp4{box-sizing:border-box;background-clip:content-box;background-color:var(--rule-soft);opacity:.4;padding:0 1px 1px 0}.grid.svelte-qu6vp4{display:grid;grid-template-columns:repeat(8,1fr);grid-template-rows:repeat(8,1fr);grid-column:2/10;grid-row:2/10;cursor:crosshair;touch-action:none}.cell.svelte-qu6vp4{box-sizing:border-box;background-clip:content-box;background-color:var(--rule-soft);padding:0 1px 1px 0}.cell.gray.svelte-qu6vp4{background-color:var(--led-gray)}.cell.on.svelte-qu6vp4{background-color:var(--ink)}.side.svelte-qu6vp4{display:flex;flex-direction:column;flex:1;gap:16px;min-width:160px}.preview-label.svelte-qu6vp4{text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);font-size:11px;font-family:var(--font-mono)}.preview-row.svelte-qu6vp4{display:flex;align-items: flex-end;gap:10px;margin-top:6px}.name-field.svelte-qu6vp4{display:flex;flex-direction:column;gap:6px}.name-field.svelte-qu6vp4 input:where(.svelte-qu6vp4){font-family:var(--font-mono);border:1px solid var(--ink);background:var(--bg);color:var(--ink);padding:8px 12px;font-size:13px}.name-field.svelte-qu6vp4 input:where(.svelte-qu6vp4)::placeholder{color:var(--ink-faint)}.name-field.svelte-qu6vp4 input[readonly]:where(.svelte-qu6vp4){color:var(--ink-soft);cursor:default}.name-field.svelte-qu6vp4 input:where(.svelte-qu6vp4):focus{outline:none;box-shadow:inset 3px 3px 0 var(--rule-soft)}.mode-field.svelte-qu6vp4{display:flex;flex-direction:column;gap:6px}.modes.svelte-qu6vp4{display:flex;border:1px solid var(--ink)}.mode.svelte-qu6vp4{border:none;border-right:1px solid var(--ink);background:var(--bg);color:var(--ink);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;cursor:pointer;flex:1;padding:7px 6px;font-size:11px}.mode.svelte-qu6vp4:last-child{border-right:none}.mode.active.svelte-qu6vp4{background:var(--ink);color:var(--bg)}.mode.svelte-qu6vp4:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--rule-soft)}.tools.svelte-qu6vp4{display:flex;gap:8px}.foot.svelte-qu6vp4{display:flex;border-top:1px solid var(--ink);flex-wrap:wrap;justify-content:space-between;align-items: center;gap:12px;padding:14px 18px}.hint.svelte-qu6vp4{font-family:var(--font-mono);color:var(--ink-faint);flex:1;min-width:180px;font-size:12px}.actions.svelte-qu6vp4{display:flex;gap:8px}.actions.svelte-qu6vp4 .btn{white-space:nowrap}
@@ -1 +1 @@
1
- .page.svelte-fkq7sr{display:flex;flex-direction:column;min-height:100vh}.title.svelte-fkq7sr{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-fkq7sr{display:flex;flex-direction:column;flex:1;align-items: center;gap:24px;padding:32px 20px}.content.svelte-fkq7sr>*{width:100%;max-width:720px}.catalog.svelte-fkq7sr{background:var(--bg-card);border:1px solid var(--rule);padding:18px}.catalog.svelte-fkq7sr header:where(.svelte-fkq7sr){margin-bottom:20px}.catalog.svelte-fkq7sr h2:where(.svelte-fkq7sr){font-family:var(--font-display);text-transform:uppercase;letter-spacing:.06em;margin:0;font-size:17px;font-weight:700}.catalog.svelte-fkq7sr header:where(.svelte-fkq7sr) p:where(.svelte-fkq7sr){font-family:var(--font-mono);color:var(--ink-soft);margin:4px 0 0;font-size:12px}ul.svelte-fkq7sr{list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:20px;margin:0;padding:0}li.svelte-fkq7sr{display:flex;flex-direction:column;align-items: center;gap:8px}.label.svelte-fkq7sr{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft);align-items: center;gap:4px;font-size:11px}.robot-mark.svelte-fkq7sr{display:inline-flex;color:var(--ink-faint)}
1
+ .page.svelte-fkq7sr{display:flex;flex-direction:column;min-height:100vh}.title.svelte-fkq7sr{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-fkq7sr{display:flex;flex-direction:column;flex:1;align-items: center;gap:24px;padding:32px 20px}.content.svelte-fkq7sr>*{width:100%;max-width:720px}.catalog.svelte-fkq7sr{background:var(--bg-card);border:1px solid var(--rule);padding:18px}.catalog.svelte-fkq7sr header:where(.svelte-fkq7sr){margin-bottom:20px}.catalog.svelte-fkq7sr h2:where(.svelte-fkq7sr){font-family:var(--font-display);text-transform:uppercase;letter-spacing:.06em;margin:0;font-size:17px;font-weight:700}.catalog.svelte-fkq7sr header:where(.svelte-fkq7sr) p:where(.svelte-fkq7sr){font-family:var(--font-mono);color:var(--ink-soft);margin:4px 0 0;font-size:12px}ul.svelte-fkq7sr{list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:20px;margin:0;padding:0}li.svelte-fkq7sr{display:flex;flex-direction:column;align-items: center;gap:8px}.label.svelte-fkq7sr{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft);align-items: center;gap:4px;font-size:11px}.robot-mark.svelte-fkq7sr{display:inline-flex;color:var(--ink-faint)}.editor-slot.svelte-fkq7sr{display:flex}.editor-slot.svelte-fkq7sr>*{width:100%}.edit-btn.svelte-fkq7sr{display:inline-flex;border:1px solid var(--rule);background:var(--bg);color:var(--ink-soft);font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;cursor:pointer;align-items: center;gap:4px;padding:4px 8px;font-size:10px}.edit-btn.svelte-fkq7sr:hover{border-color:var(--ink);color:var(--ink)}.edit-btn.active.svelte-fkq7sr{background:var(--ink);border-color:var(--ink);color:var(--bg)}.edit-btn.svelte-fkq7sr:focus-visible{outline:none;box-shadow:0 0 0 2px var(--rule-soft)}.edit-btn.svelte-fkq7sr:disabled{cursor:default}
@@ -0,0 +1 @@
1
+ .overlay.svelte-oq97c4{position:fixed;background:color-mix(in srgb,var(--ink)50%,transparent);display:grid;z-index:50;place-items: center;padding:24px;inset:0}.modal.svelte-oq97c4{display:flex;background:var(--bg-card);overflow:hidden;box-shadow:8px 8px 0 var(--ink);flex-direction:column;width:min(620px,100%);max-height:80vh}.head.svelte-oq97c4{display:flex;border-bottom:1px solid var(--ink);background:var(--ink);justify-content:space-between;align-items: center;padding:14px 18px}.head.svelte-oq97c4 h2:where(.svelte-oq97c4){font-family:var(--font-display);text-transform:uppercase;letter-spacing:.06em;color:var(--bg);margin:0;font-size:17px;font-weight:700}.x.svelte-oq97c4{display:inline-flex;cursor:pointer;color:var(--bg);background:0 0;border:none;align-items: center}.x.svelte-oq97c4:hover{opacity:.7}.mode.svelte-oq97c4{display:flex;border-bottom:1px solid var(--rule-soft);align-items: center;gap:12px;padding:12px 18px}.mode-label.svelte-oq97c4{text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);font-size:11px}.mode-toggle.svelte-oq97c4{display:flex;border:1px solid var(--ink)}.mode-btn.svelte-oq97c4{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.05em;background:var(--bg);color:var(--ink);cursor:pointer;border:none;align-items: center;gap:6px;padding:7px 14px;font-size:12px;font-weight:700}.mode-btn.svelte-oq97c4+.mode-btn:where(.svelte-oq97c4){border-left:1px solid var(--ink)}.mode-btn.active.svelte-oq97c4{background:var(--ink);color:var(--bg)}.clone.svelte-oq97c4{border-bottom:1px solid var(--rule-soft);padding:12px 18px}.clone-label.svelte-oq97c4{text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);padding-bottom:6px;font-size:11px}.clone-row.svelte-oq97c4{display:flex;gap:8px}.clone-url.svelte-oq97c4,.clone-branch.svelte-oq97c4{font-family:var(--font-mono);border:1px solid var(--ink);background:var(--bg);color:var(--ink);padding:8px 12px;font-size:13px}.clone-url.svelte-oq97c4{flex:1;min-width:0}.clone-branch.svelte-oq97c4{flex:none;width:140px}.clone-url.svelte-oq97c4::placeholder,.clone-branch.svelte-oq97c4::placeholder{color:var(--ink-faint)}.clone-url.svelte-oq97c4:focus,.clone-branch.svelte-oq97c4:focus{outline:none;box-shadow:inset 3px 3px 0 var(--rule-soft)}.clone-btn.svelte-oq97c4{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.05em;border:1px solid var(--ink);background:var(--ink);color:var(--bg);cursor:pointer;white-space:nowrap;padding:8px 14px;font-size:12px;font-weight:700}.clone-btn.svelte-oq97c4:hover:not(:disabled){background:var(--bg-card);color:var(--ink)}.clone-btn.svelte-oq97c4:disabled{opacity:.4;cursor:not-allowed}.crumbs.svelte-oq97c4{border-bottom:1px solid var(--rule-soft);overflow-x:auto;overflow-y:hidden;flex-shrink:0;min-height:18px;padding:10px 18px}.crumbs.svelte-oq97c4 code:where(.svelte-oq97c4){font-family:var(--font-mono);color:var(--ink-soft);white-space:nowrap;font-size:12px}.recent.svelte-oq97c4{border-bottom:1px solid var(--rule-soft);overflow:auto;flex-shrink:0;max-height:40vh;padding:8px}.recent-label.svelte-oq97c4{text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);padding:2px 10px 6px;font-size:11px}.recent-row.svelte-oq97c4{position:relative;display:flex;align-items: center}.recent-pick.svelte-oq97c4{display:flex;cursor:pointer;font:inherit;text-align:left;color:var(--ink);background:0 0;border:none;border-radius:8px;flex:1;align-items: center;gap:10px;min-width:0;padding:8px 10px}.recent-pick.svelte-oq97c4:hover{background:var(--ink);color:var(--bg)}.recent-pick.svelte-oq97c4:hover .recent-path:where(.svelte-oq97c4){color:var(--bg)}.recent-text.svelte-oq97c4{display:flex;flex-direction:column;min-width:0}.recent-name.svelte-oq97c4{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.recent-path.svelte-oq97c4{font-family:var(--font-mono);color:var(--ink-faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}.recent-x.svelte-oq97c4{position:absolute;display:inline-flex;background:var(--bg-card);color:var(--ink-soft);cursor:pointer;opacity:0;border:none;border-radius:6px;align-items: center;padding:5px 7px;line-height:1;top:50%;right:8px;transform:translateY(-50%)}.recent-row.svelte-oq97c4:hover .recent-x:where(.svelte-oq97c4){opacity:1}.recent-x.svelte-oq97c4:hover{color:var(--bg);background:var(--ink)}.recent-toggle.svelte-oq97c4{cursor:pointer;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.05em;color:var(--ink);background:0 0;border:none;margin:4px 6px 2px;padding:4px 6px;font-size:12px}.recent-toggle.svelte-oq97c4:hover{text-decoration:underline}.search.svelte-oq97c4{border-bottom:1px solid var(--rule-soft);padding:10px 18px}.search.svelte-oq97c4 input:where(.svelte-oq97c4){font-family:var(--font-mono);border:1px solid var(--ink);background:var(--bg);color:var(--ink);width:100%;padding:8px 12px;font-size:13px}.search.svelte-oq97c4 input:where(.svelte-oq97c4)::placeholder{color:var(--ink-faint)}.search.svelte-oq97c4 input:where(.svelte-oq97c4):focus{outline:none;box-shadow:inset 3px 3px 0 var(--rule-soft)}.list.svelte-oq97c4{overflow:auto;flex:1;padding:8px}.row.svelte-oq97c4{display:flex;border:1px solid #0000;border-radius:8px;align-items: center;gap:6px;padding:2px 4px}.row.first-match.svelte-oq97c4{border-color:var(--rule-soft)}.row.svelte-oq97c4 .nav:where(.svelte-oq97c4){display:flex;cursor:pointer;font:inherit;text-align:left;color:var(--ink);background:0 0;border:none;border-radius:8px;flex:1;align-items: center;gap:10px;padding:9px 10px}.row.svelte-oq97c4 .nav:where(.svelte-oq97c4):hover{background:var(--ink);color:var(--bg)}.row.svelte-oq97c4 .nav:where(.svelte-oq97c4):hover .icon:where(.svelte-oq97c4){color:var(--bg)}.ename.svelte-oq97c4{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge.svelte-oq97c4{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em;color:var(--ink);border:1px solid var(--ink);background:0 0;padding:2px 5px;font-size:9px;font-weight:600}.row.svelte-oq97c4 .nav:where(.svelte-oq97c4):hover .badge:where(.svelte-oq97c4){color:var(--bg);border-color:var(--bg)}.pick-inline.svelte-oq97c4{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.05em;border:1px solid var(--ink);background:var(--bg-card);cursor:pointer;color:var(--ink);padding:6px 10px;font-size:11px;font-weight:600}.pick-inline.svelte-oq97c4:hover{background:var(--ink);color:var(--bg)}.muted.svelte-oq97c4{color:var(--ink-faint);text-align:center;padding:18px}.muted.err.svelte-oq97c4{color:var(--ink);font-weight:600}.warn.svelte-oq97c4{display:flex;background:var(--warn-bg);color:var(--warn-ink);font-family:var(--font-mono);border-top:1px solid var(--warn-line);align-items: flex-start;gap:8px;padding:10px 18px;font-size:12.5px}.warn.svelte-oq97c4 svg{flex:none;margin-top:1px}.warn.svelte-oq97c4 code:where(.svelte-oq97c4){font-family:var(--font-mono);border:1px solid var(--warn-line);background:#ffffff59;padding:0 5px;font-size:.9em}.foot.svelte-oq97c4{display:flex;border-top:1px solid var(--ink);justify-content:space-between;align-items: center;gap:12px;padding:14px 18px}.hint.svelte-oq97c4{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.primary.svelte-oq97c4{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--ink);background:var(--ink);color:var(--bg);cursor:pointer;white-space:nowrap;padding:9px 16px;font-size:13px;font-weight:700}.primary.svelte-oq97c4:hover:not(:disabled){background:var(--bg-card);color:var(--ink)}.primary.svelte-oq97c4:disabled{opacity:.4;cursor:not-allowed}.icon.svelte-oq97c4{display:inline-flex;color:var(--ink-soft);align-items: center}
@@ -1 +1 @@
1
- .cog.svelte-r39acv{display:inline-flex;color:var(--ink);border-left:1px solid var(--rule);flex:none;justify-content:center;align-items: center;width:48px;margin-left:auto}.cog.svelte-r39acv:hover{background:var(--ink);color:var(--bg)}.tabs.svelte-r39acv{display:flex;overflow-x:auto;align-items:stretch;min-width:0}.tab.svelte-r39acv{display:inline-flex;border-right:1px solid var(--rule-soft);align-items:stretch}.tab.active.svelte-r39acv{background:var(--ink)}.tab.attn-done.svelte-r39acv{animation:svelte-r39acv-attn-pulse-green 1.8s ease-in-out infinite}.tab.attn-waiting.svelte-r39acv{animation:svelte-r39acv-attn-pulse-amber 1.8s ease-in-out infinite}@keyframes svelte-r39acv-attn-pulse-green{0%,to{background:0 0;box-shadow:inset 0 -2px 0 color-mix(in srgb,var(--attn-done)40%,transparent)}50%{background:color-mix(in srgb,var(--attn-done)32%,transparent);box-shadow:inset 0 -2px 0 var(--attn-done)}}@keyframes svelte-r39acv-attn-pulse-amber{0%,to{background:0 0;box-shadow:inset 0 -2px 0 color-mix(in srgb,var(--attn-waiting)40%,transparent)}50%{background:color-mix(in srgb,var(--attn-waiting)32%,transparent);box-shadow:inset 0 -2px 0 var(--attn-waiting)}}@media (prefers-reduced-motion:reduce){.tab.attn-done.svelte-r39acv,.tab.attn-waiting.svelte-r39acv{animation-duration:0s;background:color-mix(in srgb,var(--attn-done)28%,transparent)}.tab.attn-waiting.svelte-r39acv{background:color-mix(in srgb,var(--attn-waiting)28%,transparent)}}.tab-label.svelte-r39acv{appearance:none;cursor:pointer;color:var(--ink-soft);font-family:var(--font-mono);display:inline-flex;text-transform:uppercase;letter-spacing:.06em;background:0 0;border:0;align-items: center;gap:10px;max-width:240px;padding:0 12px;font-size:12px}.tab-name.svelte-r39acv{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tab.svelte-r39acv:not(.active) .tab-label:where(.svelte-r39acv):hover{color:var(--ink)}.tab.active.svelte-r39acv .tab-label:where(.svelte-r39acv){color:var(--bg)}.tab-label.editing.svelte-r39acv{cursor:default}.tab-name-edit.svelte-r39acv{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--ink-soft);background:var(--bg);color:var(--ink);outline:none;width:130px;margin:0;padding:2px 4px;font-size:12px}
1
+ .cog.svelte-r39acv{display:inline-flex;color:var(--ink);border-left:1px solid var(--rule);flex:none;justify-content:center;align-items: center;width:48px;margin-left:auto}.cog.svelte-r39acv:hover{background:var(--ink);color:var(--bg)}.tabs.svelte-r39acv{display:flex;overflow-x:auto;align-items:stretch;min-width:0}.tab.svelte-r39acv{display:inline-flex;border-right:1px solid var(--rule-soft);align-items:stretch}.tab.active.svelte-r39acv{background:var(--ink)}.tab.attn-done.svelte-r39acv{animation:svelte-r39acv-attn-pulse-green 1.8s ease-in-out infinite}.tab.attn-waiting.svelte-r39acv{animation:svelte-r39acv-attn-pulse-amber 1.8s ease-in-out infinite}@keyframes svelte-r39acv-attn-pulse-green{0%,to{background:0 0;box-shadow:inset 0 -2px 0 color-mix(in srgb,var(--attn-done)40%,transparent)}50%{background:color-mix(in srgb,var(--attn-done)32%,transparent);box-shadow:inset 0 -2px 0 var(--attn-done)}}@keyframes svelte-r39acv-attn-pulse-amber{0%,to{background:0 0;box-shadow:inset 0 -2px 0 color-mix(in srgb,var(--attn-waiting)40%,transparent)}50%{background:color-mix(in srgb,var(--attn-waiting)32%,transparent);box-shadow:inset 0 -2px 0 var(--attn-waiting)}}@media (prefers-reduced-motion:reduce){.tab.attn-done.svelte-r39acv,.tab.attn-waiting.svelte-r39acv{animation-duration:0s;background:color-mix(in srgb,var(--attn-done)28%,transparent)}.tab.attn-waiting.svelte-r39acv{background:color-mix(in srgb,var(--attn-waiting)28%,transparent)}}.tab-label.svelte-r39acv{appearance:none;cursor:pointer;color:var(--ink-soft);font-family:var(--font-mono);display:inline-flex;text-transform:uppercase;letter-spacing:.06em;background:0 0;border:0;align-items: center;gap:10px;max-width:240px;padding:0 12px;font-size:12px}.tab-name.svelte-r39acv{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tab-mode.svelte-r39acv{display:inline-flex;opacity:.75;flex:none;align-items: center}.tab.svelte-r39acv:not(.active) .tab-label:where(.svelte-r39acv):hover{color:var(--ink)}.tab.active.svelte-r39acv .tab-label:where(.svelte-r39acv){color:var(--bg)}.tab-label.editing.svelte-r39acv{cursor:default}.tab-name-edit.svelte-r39acv{font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.06em;border:1px solid var(--ink-soft);background:var(--bg);color:var(--ink);outline:none;width:130px;margin:0;padding:2px 4px;font-size:12px}
@@ -1 +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}
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}.mode-toggle.svelte-1n6ht58{display:flex;border:1px solid var(--ink);flex:none}.mode-btn.svelte-1n6ht58{display:inline-flex;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.05em;background:var(--bg);color:var(--ink);cursor:pointer;border:none;align-items: center;gap:6px;padding:7px 14px;font-size:12px;font-weight:700}.mode-btn.svelte-1n6ht58+.mode-btn:where(.svelte-1n6ht58){border-left:1px solid var(--ink)}.mode-btn.active.svelte-1n6ht58{background:var(--ink);color:var(--bg)}.mode-btn.svelte-1n6ht58:disabled{cursor:not-allowed}.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}
@@ -0,0 +1 @@
1
+ .term.svelte-ui3nvu{background:var(--bg);box-sizing:border-box;width:100%;height:100%;padding:6px 8px}.term.svelte-ui3nvu .xterm,.term.svelte-ui3nvu .xterm-viewport{height:100%}.overlay.svelte-ui3nvu{position:absolute;display:flex;pointer-events:none;align-items: center;gap:8px;top:6px;right:8px}.status.svelte-ui3nvu{font-family:var(--font-mono);letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);font-size:11px}.overlay.svelte-ui3nvu button{pointer-events:auto;appearance:none;display:inline-flex;cursor:pointer;background:var(--bg);border:1px solid var(--rule);color:var(--ink);opacity:.3;justify-content:center;align-items: center;width:24px;height:24px;padding:0;transition:opacity .15s}.overlay.svelte-ui3nvu button:hover,.overlay.svelte-ui3nvu button[aria-pressed=true]{opacity:1;background:var(--ink);color:var(--bg)}.overlay.svelte-ui3nvu button:focus-visible{opacity:1;background:var(--ink);color:var(--bg)}@media (prefers-reduced-motion:reduce){.overlay.svelte-ui3nvu button{transition:none}}
@@ -0,0 +1 @@
1
+ .split.svelte-1ysvu88{display:flex;width:100%;height:100%}.side.svelte-1ysvu88{position:relative;flex:0 auto;min-width:0}.side.hidden.svelte-1ysvu88{display:none}.divider.svelte-1ysvu88{cursor:col-resize;background:var(--rule);z-index:1;background-clip:content-box;border-left:3px solid #0000;border-right:3px solid #0000;flex:none;width:7px;margin:0 -3px}.divider.hidden.svelte-1ysvu88{display:none}.divider.svelte-1ysvu88:focus-visible{outline:2px solid var(--ink);outline-offset:-2px}
@@ -0,0 +1 @@
1
+ ALTER TABLE instances ADD COLUMN mode TEXT NOT NULL DEFAULT 'ide';
@@ -0,0 +1 @@
1
+ ALTER TABLE instances ADD COLUMN terminal_split INTEGER NOT NULL DEFAULT 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebay",
3
- "version": "0.8.0",
3
+ "version": "0.9.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",
@@ -63,6 +63,8 @@
63
63
  "@fontsource-variable/doto": "^5.3.0",
64
64
  "@fontsource-variable/jetbrains-mono": "^5.3.0",
65
65
  "@lucide/svelte": "^1.28.0",
66
+ "@xterm/addon-fit": "^0.11.0",
67
+ "@xterm/xterm": "^6.0.0",
66
68
  "@zihaolam/bun-sqlite-migrations": "^1.0.3",
67
69
  "dockerode": "^5.0.1",
68
70
  "mochi-framework": "^0.9.1",
@@ -4,6 +4,10 @@ import { type AvatarArt, ROWS, COLS, ON, GRAY } from './types.ts';
4
4
 
5
5
  export const REPO_URL = 'https://github.com/khromov/codebay';
6
6
 
7
+ // A brand-new sprite vs. a hand-redraw of an existing (AI-generated) one — the two
8
+ // produce differently-shaped issues so the contribution skill can tell them apart.
9
+ export type ContributionMode = 'create' | 'edit';
10
+
7
11
  // The name doubles as a filename and a catalog key, hence the aggressive squash.
8
12
  export function normalizeName(raw: string): string {
9
13
  return raw
@@ -27,7 +31,8 @@ export function cellsToPixels(cells: number[]): string[] {
27
31
  }
28
32
 
29
33
  // Byte-identical to the hand-written sprites (tabs, single quotes, trailing newline),
30
- // so a maintainer can drop it in and prettier won't touch it.
34
+ // so a maintainer can drop it in and prettier won't touch it. It never emits `robot`,
35
+ // so a redraw of an AI sprite lands as a plain hand-drawn module — human-made by construction.
31
36
  export function toModuleSource(art: AvatarArt): string {
32
37
  const rows = art.pixels.map((row) => `\t\t'${row}'`).join(',\n');
33
38
  return `import type { AvatarArt } from './types.ts';
@@ -44,9 +49,15 @@ export default art;
44
49
  }
45
50
 
46
51
  // The art is ~64 chars, far below any URL length limit.
47
- export function toIssueUrl(art: AvatarArt): string {
48
- const title = `Avatar contribution: ${art.name}`;
49
- const body = [
52
+ export function toIssueUrl(art: AvatarArt, mode: ContributionMode = 'create'): string {
53
+ const body = mode === 'edit' ? editIssueBody(art) : createIssueBody(art);
54
+ const title = mode === 'edit' ? `Avatar edit: ${art.name}` : `Avatar contribution: ${art.name}`;
55
+ const params = new URLSearchParams({ title, body });
56
+ return `${REPO_URL}/issues/new?${params}`;
57
+ }
58
+
59
+ function createIssueBody(art: AvatarArt): string {
60
+ return [
50
61
  "Hi! I drew this avatar in the editor easter egg and I'd love to see it in the official set.",
51
62
  '',
52
63
  '```',
@@ -61,6 +72,24 @@ export function toIssueUrl(art: AvatarArt): string {
61
72
  '',
62
73
  '_It should satisfy `src/avatars/avatars.test.ts`: exactly 8×8, only `#`/`+`/`.` pixels, unique name._'
63
74
  ].join('\n');
64
- const params = new URLSearchParams({ title, body });
65
- return `${REPO_URL}/issues/new?${params}`;
75
+ }
76
+
77
+ // A redraw replaces an existing AI-generated sprite in place; the module carries no
78
+ // `robot` flag, so landing it promotes the sprite from robot-drawn to hand-drawn.
79
+ function editIssueBody(art: AvatarArt): string {
80
+ return [
81
+ `Hi! I redrew the existing \`${art.name}\` sprite by hand in the editor easter egg — it started as an AI-generated placeholder and this replaces it.`,
82
+ '',
83
+ '```',
84
+ ...art.pixels,
85
+ '```',
86
+ '',
87
+ `Ready-to-paste replacement for \`src/avatars/${art.name}.ts\` (the \`robot\` flag is intentionally dropped — it's hand-drawn now):`,
88
+ '',
89
+ '```ts',
90
+ toModuleSource(art).trimEnd(),
91
+ '```',
92
+ '',
93
+ '_It should satisfy `src/avatars/avatars.test.ts`: exactly 8×8, only `#`/`+`/`.` pixels._'
94
+ ].join('\n');
66
95
  }
@@ -1,10 +1,11 @@
1
1
  <script lang="ts">
2
- import { ideUrl, type Instance, type Preflight } from '../types.ts';
2
+ import { ideUrl, type Instance, type InstanceFilter, type Preflight } from '../types.ts';
3
3
  import { findAvatar, type AvatarArt } from '../avatars/index.ts';
4
4
  import { SvelteSet } from 'svelte/reactivity';
5
5
  import DashboardView from './DashboardView.svelte';
6
6
  import IdeBar from './IdeBar.svelte';
7
7
  import IdeLoader from './IdeLoader.svelte';
8
+ import TerminalSplit from './TerminalSplit.svelte';
8
9
  import { playChime, unlockAudio } from '../sound.ts';
9
10
  import { liveStream } from '../live.ts';
10
11
  import { apiPost } from '../api.ts';
@@ -15,12 +16,14 @@
15
16
  preflight,
16
17
  initialPath,
17
18
  snapshot,
18
- pet
19
+ pet,
20
+ filter
19
21
  }: {
20
22
  preflight: Preflight;
21
23
  initialPath: string;
22
24
  snapshot: Instance[];
23
25
  pet?: AvatarArt;
26
+ filter: InstanceFilter;
24
27
  } = $props();
25
28
 
26
29
  // Seeding from the SSR snapshot is intentional — the live stream overwrites it.
@@ -32,6 +35,9 @@
32
35
  // Seeded from SSR, then updated live so changing the pet in settings swaps the header without a reload.
33
36
  // svelte-ignore state_referenced_locally
34
37
  let livePet = $state<AvatarArt | undefined>(pet);
38
+ // Owned here (not in DashboardView) so it survives the dashboard↔IDE tab switches that remount the view.
39
+ // svelte-ignore state_referenced_locally
40
+ let liveFilter = $state<InstanceFilter>(filter);
35
41
  // svelte-ignore state_referenced_locally
36
42
  let loaded = $state(snapshot.length > 0);
37
43
  const running = $derived(instances.filter((i) => i.status === 'running'));
@@ -70,6 +76,14 @@
70
76
  }
71
77
  }
72
78
 
79
+ // Optimistic local update + best-effort persist; the broadcast echo reconciles every open tab.
80
+ function setFilter(v: InstanceFilter) {
81
+ liveFilter = v;
82
+ void apiPost('/api/settings/filter', { value: v }).catch(() => {
83
+ /* best-effort — the in-memory value already reflects the choice */
84
+ });
85
+ }
86
+
73
87
  // Mochi has no client router, and a document reload would tear down the code-server iframes.
74
88
  // svelte-ignore state_referenced_locally
75
89
  let path = $state(initialPath);
@@ -174,6 +188,14 @@
174
188
  livePet = findAvatar(msg.data.name ?? undefined);
175
189
  return;
176
190
  }
191
+ if (msg.type === 'filter') {
192
+ liveFilter = msg.data.value;
193
+ return;
194
+ }
195
+ if (msg.type === 'default-mode') {
196
+ livePreflight = { ...livePreflight, defaultMode: msg.data.mode };
197
+ return;
198
+ }
177
199
  if (msg.type === 'health') {
178
200
  // A tick in flight when a rebuild starts probes the *old* container and
179
201
  // reports it accessible, which would mount the iframe against the replacement too early.
@@ -250,7 +272,14 @@
250
272
  oncancelrename={cancelRename}
251
273
  />
252
274
  {:else}
253
- <DashboardView preflight={livePreflight} {instances} {loaded} pet={livePet} />
275
+ <DashboardView
276
+ preflight={livePreflight}
277
+ {instances}
278
+ {loaded}
279
+ pet={livePet}
280
+ filter={liveFilter}
281
+ onFilter={setFilter}
282
+ />
254
283
  {/if}
255
284
 
256
285
  <!-- Always mounted, only hidden, so the iframes survive navigation. -->
@@ -259,14 +288,23 @@
259
288
  {#if visited.has(inst.id)}
260
289
  <div class="pane" class:active={inst.id === active}>
261
290
  {#if mountable(inst.id)}
262
- <iframe src={ideUrl(inst)} title={inst.name} onload={() => loadedFrames.add(inst.id)}
263
- ></iframe>
291
+ {#if inst.mode === 'terminal'}
292
+ <TerminalSplit
293
+ id={inst.id}
294
+ active={inst.id === active}
295
+ initialOpen={inst.terminal_split === 1}
296
+ />
297
+ {:else}
298
+ <iframe src={ideUrl(inst)} title={inst.name} onload={() => loadedFrames.add(inst.id)}
299
+ ></iframe>
300
+ {/if}
264
301
  {/if}
265
- <!-- Two distinct waits: the health probe is unbounded and gets the override,
266
- the iframe's own `load` is bounded and gets a plain loader. -->
302
+ <!-- Two distinct waits: the health probe is unbounded and gets the override, the
303
+ iframe's own `load` is bounded and gets a plain loader. The terminal has no
304
+ `load` event, so once it's mountable its own "connecting…" covers the gap. -->
267
305
  {#if !mountable(inst.id)}
268
306
  <IdeLoader stalledAfterMs={STALLED_AFTER_MS} onoverride={() => forced.add(inst.id)} />
269
- {:else if !loadedFrames.has(inst.id)}
307
+ {:else if inst.mode !== 'terminal' && !loadedFrames.has(inst.id)}
270
308
  <IdeLoader />
271
309
  {/if}
272
310
  </div>
@@ -67,7 +67,8 @@
67
67
  // 0.5px is a crisp single device-pixel at 2×; at 1× the cells can't spare any gutter.
68
68
  const gap = $derived(s >= 2 ? 0.5 : 0);
69
69
 
70
- const cells = $derived(decode(art ?? pickAvatar(id)));
70
+ const resolved = $derived(art ?? pickAvatar(id));
71
+ const cells = $derived(decode(resolved));
71
72
  // The 10×10 outer ring is unlit LED cells — a bezel of real dots, not blank padding.
72
73
  const grid = $derived(
73
74
  Array.from({ length: 100 }, (_, i) => {
@@ -86,7 +87,7 @@
86
87
  class:ghosting
87
88
  role="img"
88
89
  aria-label={name}
89
- title={name}
90
+ title={resolved.name}
90
91
  style="width:{10 * s}px;height:{10 *
91
92
  s}px;--gap:{gap}px;--bx:{bx};--by:{by};--spin:{spin}deg;--hue:{hue}deg"
92
93
  {onpointerdown}