babavoss 0.12.5 → 0.13.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 (52) hide show
  1. package/gui/babavoss-web.js +234 -0
  2. package/gui/chunk-5hpp1ypv.js +11710 -0
  3. package/gui/chunk-a20zfekq.js +8639 -0
  4. package/gui/chunk-k3cm8j6r.js +433 -0
  5. package/gui/chunk-pk09y98y.js +50 -0
  6. package/gui/chunk-smz02qa6.js +186 -0
  7. package/gui/chunk-wwqypxre.js +49 -0
  8. package/gui/gui.js +2015 -0
  9. package/gui/react-compiler-runtime.js +39 -0
  10. package/gui/react-dom-client.js +21 -0
  11. package/gui/react-dom.js +44 -0
  12. package/gui/react-jsx-runtime.js +19 -0
  13. package/gui/react.js +105 -0
  14. package/gui/theme.css +3053 -0
  15. package/index.ts +1 -1
  16. package/package.json +8 -6
  17. package/src/{promptware → agents}/compile.ts +12 -12
  18. package/src/{promptware → agents}/define.ts +2 -2
  19. package/src/{promptware → agents}/disk.ts +7 -4
  20. package/src/{promptware → agents}/sync.ts +2 -2
  21. package/src/{promptware → agents}/system.ts +17 -17
  22. package/src/baba/check.ts +3 -3
  23. package/src/baba/config.ts +5 -2
  24. package/src/baba/init.ts +13 -18
  25. package/src/baba/node.ts +2 -2
  26. package/src/baba/worker.ts +13 -5
  27. package/src/build/mdx.ts +4 -4
  28. package/src/build/project.ts +6 -1
  29. package/src/build/views.ts +3 -3
  30. package/src/desktop/index.ts +1 -1
  31. package/src/door/core.ts +2 -2
  32. package/src/ecs/baba.ts +27 -17
  33. package/src/generated/build.ts +1 -1
  34. package/src/gui/gui.tsx +3 -3
  35. package/src/gui/{promptware.tsx → instructions.tsx} +12 -12
  36. package/src/gui/theme.css +1 -1
  37. package/src/guide/add-a-desktop.mdx +2 -2
  38. package/src/guide/index.ts +4 -4
  39. package/src/guide/write-a-system.mdx +3 -3
  40. package/src/guide/{write-promptware.mdx → write-agentic-instructions.mdx} +17 -24
  41. package/src/{prompt → instructions}/evals.ts +1 -1
  42. package/src/{prompt → instructions}/index.ts +9 -8
  43. package/src/{prompt → instructions}/jsx-runtime.ts +3 -3
  44. package/src/mcp/main.ts +3 -3
  45. package/src/mcp/tools.ts +4 -4
  46. package/src/runtime/harness.ts +2 -2
  47. package/src/shell/run.ts +18 -18
  48. package/src/spec/index.ts +1 -1
  49. package/src/web/core.tsx +2 -2
  50. /package/src/{promptware → agents}/markdown.d.ts +0 -0
  51. /package/src/{prompt → instructions}/jsx-dev-runtime.ts +0 -0
  52. /package/src/{prompt → instructions}/mdx.d.ts +0 -0
package/gui/gui.js ADDED
@@ -0,0 +1,2015 @@
1
+ import {
2
+ require_client
3
+ } from "./chunk-5hpp1ypv.js";
4
+ import"./chunk-smz02qa6.js";
5
+ import {
6
+ ApiError,
7
+ Button,
8
+ Desktop,
9
+ DesktopFrame,
10
+ FieldError,
11
+ Input,
12
+ Maker,
13
+ Row,
14
+ Rows,
15
+ ScopeProvider,
16
+ Select,
17
+ Table,
18
+ Textarea,
19
+ accept,
20
+ api,
21
+ apiHeaders,
22
+ canonicalPath,
23
+ collect,
24
+ describeType,
25
+ effect,
26
+ formatFailure,
27
+ isApps,
28
+ matchRoute,
29
+ plan,
30
+ resource,
31
+ s,
32
+ system,
33
+ tableOf,
34
+ useBaba,
35
+ useBabaState,
36
+ useResource,
37
+ useScope
38
+ } from "./chunk-a20zfekq.js";
39
+ import {
40
+ require_jsx_runtime
41
+ } from "./chunk-pk09y98y.js";
42
+ import {
43
+ require_react
44
+ } from "./chunk-k3cm8j6r.js";
45
+ import {
46
+ __toESM
47
+ } from "./chunk-wwqypxre.js";
48
+
49
+ // src/gui/gui.tsx
50
+ var import_react6 = __toESM(require_react(), 1);
51
+ var import_client = __toESM(require_client(), 1);
52
+
53
+ // src/desktop/icons.ts
54
+ var iconTags = ["path", "circle", "ellipse", "line", "polyline", "polygon", "rect"];
55
+ var geometry = new Set(["d", "cx", "cy", "r", "rx", "ry", "x", "y", "x1", "y1", "x2", "y2", "width", "height", "points"]);
56
+ var keep = (pairs) => Object.fromEntries([...pairs].filter((m) => geometry.has(m[1])).map((m) => [m[1], m[2]]));
57
+ var isTag = (t) => iconTags.includes(t);
58
+ var iconName = /^[a-z0-9]+(-[a-z0-9]+)*$/;
59
+ function shapesOfSvg(text) {
60
+ const out = [];
61
+ for (const m of text.matchAll(/<([a-z]+)\b([^>]*?)\/?>/g))
62
+ if (isTag(m[1]))
63
+ out.push({ tag: m[1], attrs: keep(m[2].matchAll(/([a-z0-9-]+)="([^"]*)"/g)) });
64
+ return out;
65
+ }
66
+ function shapesOfModule(text) {
67
+ const out = [];
68
+ for (const m of text.matchAll(/\[\s*"([a-z]+)"\s*,\s*\{([^}]*)\}\s*\]/g))
69
+ if (isTag(m[1]))
70
+ out.push({ tag: m[1], attrs: keep(m[2].matchAll(/([a-z0-9]+)\s*:\s*"([^"]*)"/g)) });
71
+ return out;
72
+ }
73
+ var iconSources = (name) => [
74
+ { path: `.baba/node_modules/lucide-static/icons/${name}.svg`, parse: shapesOfSvg },
75
+ { path: `.baba/node_modules/lucide-react/dist/esm/icons/${name}.mjs`, parse: shapesOfModule }
76
+ ];
77
+ async function readIcon(name, fs) {
78
+ if (!iconName.test(name))
79
+ return [];
80
+ for (const source of iconSources(name)) {
81
+ let text;
82
+ try {
83
+ text = await fs.read(source.path);
84
+ } catch {
85
+ continue;
86
+ }
87
+ const shapes = source.parse(text);
88
+ if (shapes.length)
89
+ return shapes;
90
+ }
91
+ return [];
92
+ }
93
+
94
+ // src/desktop/index.ts
95
+ var themes = ["system", "light", "dark"];
96
+ var SESSION_IDLE = 24 * 60 * 60 * 1000;
97
+ var sessionNames = [
98
+ "oak",
99
+ "ash",
100
+ "elm",
101
+ "fir",
102
+ "yew",
103
+ "pine",
104
+ "birch",
105
+ "cedar",
106
+ "maple",
107
+ "willow",
108
+ "alder",
109
+ "aspen",
110
+ "beech",
111
+ "hazel",
112
+ "holly",
113
+ "larch",
114
+ "linden",
115
+ "rowan",
116
+ "spruce",
117
+ "poplar",
118
+ "olive",
119
+ "palm",
120
+ "plum",
121
+ "pear",
122
+ "fig",
123
+ "lime",
124
+ "teak",
125
+ "ebony",
126
+ "walnut",
127
+ "chestnut",
128
+ "cypress",
129
+ "juniper",
130
+ "laurel",
131
+ "magnolia",
132
+ "myrtle",
133
+ "sequoia",
134
+ "sycamore",
135
+ "acacia",
136
+ "baobab",
137
+ "ginkgo"
138
+ ];
139
+ function desktop(options) {
140
+ const catalog = (options.apps ?? options.surfaces ?? []).map((v, order) => ({ key: v.key, title: v.title, group: v.group ?? "", icon: v.icon ?? "", many: v.many ?? false, order, routes: [...v.routes ?? []] }));
141
+ if (new Set(catalog.map((v) => v.key)).size !== catalog.length || catalog.some((v) => !/^[a-z][a-z0-9-]*$/.test(v.key)))
142
+ throw new Error("desktop app keys must be unique lowercase names");
143
+ const sessionArg = { session: s.optional(s.string()) };
144
+ const routeArg = { ...sessionArg, app: s.string(), path: s.optional(s.string()), fresh: s.optional(s.boolean()) };
145
+ const windowArg = { ...sessionArg, window: s.optional(s.entity("desktopWindow")) };
146
+ const appShape = { key: s.key(s.string()), title: s.string(), group: s.string(), icon: s.string(), many: s.boolean(), order: s.integer(), routes: s.optional(s.array(s.string())) };
147
+ const sessionShape = {
148
+ key: s.key(s.string()),
149
+ selected: s.nullable(s.entity()),
150
+ theme: s.enum(...themes),
151
+ touched: s.optional(s.integer())
152
+ };
153
+ const settingsShape = { theme: s.enum(...themes) };
154
+ const windowShape = { session: s.entity(), app: s.entity(), path: s.string(), title: s.string(), memory: s.object({}, { open: true }) };
155
+ return system({
156
+ name: "desktop",
157
+ model: {
158
+ desktopApp: appShape,
159
+ desktopSession: sessionShape,
160
+ desktopWindow: windowShape,
161
+ desktopAsk: accept(routeArg),
162
+ desktopAnswer: { session: s.nullable(s.entity()), error: s.nullable(s.string()) },
163
+ desktopIcon: { name: s.string(), shapes: s.array(s.object({ tag: s.enum(...iconTags), attrs: s.object({}, { open: true }) })) },
164
+ desktopSettings: resource(settingsShape, { theme: "system" })
165
+ },
166
+ effects: {
167
+ desktopIconRead: effect({
168
+ summary: "a Lucide icon's shapes, read from the baba's lucide-static or lucide-react; none when neither has it",
169
+ args: s.object({ name: s.string() }),
170
+ retry: 30000,
171
+ result: s.object({ name: s.string(), shapes: s.array(s.object({ tag: s.enum(...iconTags), attrs: s.object({}, { open: true }) })) }),
172
+ run: async ({ name }, ctx) => ({ name, shapes: await readIcon(name, ctx.fs) })
173
+ })
174
+ }
175
+ }, (desktop2, p) => {
176
+ const { desktopApp: app, desktopSession: session, desktopWindow: window, desktopAsk: ask, desktopAnswer: answer, desktopIcon: icon, desktopSettings: settings, desktopIconRead: iconReadEffect } = desktop2;
177
+ const touchedAt = (w, e) => w.get(e, session)?.touched ?? 0;
178
+ function current(w) {
179
+ let best;
180
+ for (const r of w.query(session))
181
+ if (best === undefined || touchedAt(w, r.entity) >= touchedAt(w, best))
182
+ best = r.entity;
183
+ return best;
184
+ }
185
+ function mint(w) {
186
+ const taken = new Set(w.query(session).map((r) => r.desktopSession.key));
187
+ let key = sessionNames.find((n) => !taken.has(n));
188
+ for (let i = 2;key === undefined; i++)
189
+ key = sessionNames.map((n) => `${n}-${i}`).find((n) => !taken.has(n));
190
+ return w.spawn(session, { key, selected: null, theme: "system", touched: w.now });
191
+ }
192
+ const touch = (w, e) => {
193
+ w.add(e, session, { ...w.get(e, session), touched: w.now });
194
+ return e;
195
+ };
196
+ function ensure(w, key) {
197
+ if (key === undefined)
198
+ return current(w) ?? mint(w);
199
+ if (!key.trim())
200
+ throw new Error("session must have a name");
201
+ return w.find(session, key) ?? w.spawn(session, { key, selected: null, theme: "system", touched: w.now });
202
+ }
203
+ const windowsOf = (w, ws) => w.query(window).filter((r) => r.desktopWindow.session === ws);
204
+ function sync(w) {
205
+ for (const item of catalog) {
206
+ const e = w.find(app, item.key);
207
+ if (e === undefined)
208
+ w.spawn(app, item);
209
+ else if (JSON.stringify(w.get(e, app)) !== JSON.stringify(item))
210
+ w.add(e, app, item);
211
+ }
212
+ for (const r of w.query(app))
213
+ if (!catalog.some((v) => v.key === r.desktopApp.key))
214
+ w.despawn(r.entity);
215
+ const seen = new Set;
216
+ for (const r of [...w.query(window)].reverse()) {
217
+ const at = `${r.desktopWindow.session}:${r.desktopWindow.app}`;
218
+ const many = w.get(r.desktopWindow.app, app)?.many ?? false;
219
+ if (!w.has(r.desktopWindow.app, app) || !w.has(r.desktopWindow.session, session) || !many && seen.has(at)) {
220
+ w.despawn(r.entity);
221
+ continue;
222
+ }
223
+ seen.add(at);
224
+ const routes = w.get(r.desktopWindow.app, app)?.routes ?? [];
225
+ if (routes.length && !matchRoute(routes, r.desktopWindow.path))
226
+ w.add(r.entity, window, { ...r.desktopWindow, path: "/" });
227
+ }
228
+ const now = current(w);
229
+ for (const r of w.query(session)) {
230
+ const v = r.desktopSession;
231
+ if (v.theme !== "system") {
232
+ if (w.get(settings).theme === "system")
233
+ w.set(settings, { theme: v.theme });
234
+ w.add(r.entity, session, { ...v, theme: "system" });
235
+ }
236
+ if (v.selected !== null && !w.has(v.selected, window))
237
+ w.add(r.entity, session, { ...w.get(r.entity, session), selected: null });
238
+ else if (v.selected === null && r.entity !== now && w.now - (v.touched ?? 0) > SESSION_IDLE && !windowsOf(w, r.entity).length)
239
+ w.despawn(r.entity);
240
+ }
241
+ }
242
+ function show(w, e, win) {
243
+ w.add(e, session, { ...w.get(e, session), selected: win, touched: w.now });
244
+ }
245
+ function open(w, asked) {
246
+ const item = catalog.find((v) => v.key === asked.app);
247
+ if (!item)
248
+ throw new Error(`unknown desktop app ${asked.app}`);
249
+ if (asked.path !== undefined && (!asked.path.startsWith("/") || asked.path.startsWith("//")))
250
+ throw new Error("an app path must start with one slash");
251
+ const a = asked.path === undefined ? asked : { ...asked, path: canonicalPath(asked.path) };
252
+ if (a.path !== undefined && item.routes.length && !matchRoute(item.routes, a.path))
253
+ throw new Error(`${a.path} is not a path of ${a.app}; its routes: ${item.routes.join(", ")}`);
254
+ sync(w);
255
+ const e = ensure(w, a.session), target = w.find(app, item.key);
256
+ const ws = w.get(e, session);
257
+ const wins = windowsOf(w, e).filter((r) => r.desktopWindow.app === target);
258
+ let win = a.path !== undefined ? wins.find((r) => r.desktopWindow.path === a.path) : undefined;
259
+ if (!win && !(a.fresh && item.many))
260
+ win = wins.find((r) => r.entity === ws.selected) ?? wins.at(-1);
261
+ if (win) {
262
+ if (a.path !== undefined && win.desktopWindow.path !== a.path)
263
+ w.add(win.entity, window, { ...win.desktopWindow, path: a.path });
264
+ show(w, e, win.entity);
265
+ } else {
266
+ show(w, e, w.spawn(window, { session: e, app: target, path: a.path ?? "/", title: "", memory: {} }));
267
+ }
268
+ return e;
269
+ }
270
+ function windowOf(w, e, id) {
271
+ const ws = w.get(e, session);
272
+ const target = id ?? ws.selected;
273
+ if (target === null || target === undefined)
274
+ throw new Error(`no window is selected in ${ws.key}`);
275
+ const v = w.get(target, window);
276
+ if (!v || v.session !== e)
277
+ throw new Error(`${target} is not a window of ${ws.key}`);
278
+ return { entity: target, desktopWindow: v };
279
+ }
280
+ return [
281
+ p.on(iconReadEffect, {
282
+ for: (w) => w.query(app).filter((r) => iconName.test(r.desktopApp.icon) && w.get(r.entity, icon)?.name !== r.desktopApp.icon).map((r) => [r.entity, { name: r.desktopApp.icon }]),
283
+ done: (w, e, r) => {
284
+ if (e !== null && w.has(e, app))
285
+ w.add(e, icon, { name: r.name, shapes: r.shapes });
286
+ }
287
+ }),
288
+ p.step("reconcile", (w) => {
289
+ if (w.query(session).length)
290
+ sync(w);
291
+ }),
292
+ p.step("requests", (w) => {
293
+ for (const r of w.query(ask)) {
294
+ try {
295
+ w.add(r.entity, answer, { session: open(w, r.desktopAsk), error: null });
296
+ } catch (error) {
297
+ w.add(r.entity, answer, { session: null, error: String(error) });
298
+ }
299
+ w.remove(r.entity, ask);
300
+ }
301
+ }),
302
+ p.action("desktop-open", {
303
+ summary: "open an app at a path: the window already there, else the app's current window, else a new one; `fresh` opens another where the app allows many",
304
+ args: routeArg,
305
+ result: s.entity(),
306
+ run: (w, a) => open(w, a)
307
+ }),
308
+ p.action("desktop-select", {
309
+ summary: "bring a window to the front",
310
+ args: { ...sessionArg, window: s.entity("desktopWindow") },
311
+ result: s.entity(),
312
+ run: (w, a) => {
313
+ sync(w);
314
+ const e = ensure(w, a.session);
315
+ const win = windowOf(w, e, a.window);
316
+ show(w, e, win.entity);
317
+ return e;
318
+ }
319
+ }),
320
+ p.action("desktop-close", {
321
+ summary: "close a window, the selected one unless named; closing the one in front brings the window to its left forward, else the one to its right, else nothing, and the page shows its launcher",
322
+ args: windowArg,
323
+ result: s.entity(),
324
+ run: (w, a) => {
325
+ sync(w);
326
+ const e = ensure(w, a.session);
327
+ const ws = w.get(e, session);
328
+ const win = windowOf(w, e, a.window);
329
+ const order = windowsOf(w, e), at = order.findIndex((r) => r.entity === win.entity);
330
+ const next = order[at - 1] ?? order[at + 1];
331
+ w.despawn(win.entity);
332
+ w.add(e, session, { ...ws, selected: ws.selected === win.entity ? next?.entity ?? null : ws.selected, touched: w.now });
333
+ return e;
334
+ }
335
+ }),
336
+ p.action("desktop-title", {
337
+ summary: "name a window after what its app shows, the selected one unless named",
338
+ args: { ...windowArg, title: s.string() },
339
+ result: s.entity(),
340
+ run: (w, a) => {
341
+ sync(w);
342
+ const e = ensure(w, a.session);
343
+ const win = windowOf(w, e, a.window);
344
+ if (win.desktopWindow.title !== a.title)
345
+ w.add(win.entity, window, { ...win.desktopWindow, title: a.title });
346
+ return win.entity;
347
+ }
348
+ }),
349
+ p.action("desktop-remember", {
350
+ summary: "keep a value on a window for its app, the selected window unless named; null forgets the key",
351
+ args: { ...windowArg, key: s.string(), value: s.unknown() },
352
+ result: s.entity(),
353
+ run: (w, a) => {
354
+ sync(w);
355
+ const e = ensure(w, a.session);
356
+ const win = windowOf(w, e, a.window);
357
+ const memory = { ...win.desktopWindow.memory };
358
+ if (a.value === null || a.value === undefined)
359
+ delete memory[a.key];
360
+ else
361
+ memory[a.key] = a.value;
362
+ w.add(win.entity, window, { ...win.desktopWindow, memory });
363
+ return win.entity;
364
+ }
365
+ }),
366
+ p.action("desktop-enter", {
367
+ summary: "enter a session: the session named, made if it is missing, else a new one with a name of its own; a tab enters on load and on focus, so the session it is on is the current one",
368
+ args: sessionArg,
369
+ result: s.entity(),
370
+ run: (w, a) => {
371
+ sync(w);
372
+ return touch(w, a.session === undefined ? mint(w) : ensure(w, a.session));
373
+ }
374
+ }),
375
+ p.action("desktop-leave", {
376
+ summary: "end a session: its windows close and the session goes; the current one unless named",
377
+ args: sessionArg,
378
+ result: s.entity(),
379
+ run: (w, a) => {
380
+ sync(w);
381
+ const e = ensure(w, a.session);
382
+ for (const r of windowsOf(w, e))
383
+ w.despawn(r.entity);
384
+ w.despawn(e);
385
+ return e;
386
+ }
387
+ }),
388
+ p.action("desktop-settings", {
389
+ summary: "set the baba's theme, the same on every page",
390
+ args: { ...sessionArg, theme: s.optional(s.enum(...themes)) },
391
+ run: (w, a) => {
392
+ sync(w);
393
+ if (a.theme)
394
+ w.set(settings, { theme: a.theme });
395
+ }
396
+ }),
397
+ p.query("desktop-state", {
398
+ summary: "a session, the current one unless named: its record, apps and windows; every session beside it, last touched first; the theme",
399
+ args: sessionArg,
400
+ result: s.object({
401
+ entity: s.nullable(s.entity()),
402
+ session: s.nullable(s.object(sessionShape)),
403
+ apps: s.array(s.object({ entity: s.entity(), ...appShape })),
404
+ windows: s.array(s.object({ entity: s.entity(), ...windowShape })),
405
+ sessions: s.array(s.object({ entity: s.entity(), key: s.string(), touched: s.integer(), windows: s.integer(), front: s.nullable(s.string()) })),
406
+ theme: s.enum(...themes)
407
+ }),
408
+ read: (w, a) => {
409
+ const e = a.session === undefined ? current(w) : w.find(session, a.session);
410
+ const sessions = w.query(session).map((r) => {
411
+ const wins = windowsOf(w, r.entity), front = wins.find((o) => o.entity === r.desktopSession.selected);
412
+ const label = front ? front.desktopWindow.title || (front.desktopWindow.path === "/" ? w.get(front.desktopWindow.app, app)?.title ?? null : front.desktopWindow.path) : null;
413
+ return { entity: r.entity, key: r.desktopSession.key, touched: r.desktopSession.touched ?? 0, windows: wins.length, front: label };
414
+ }).sort((x, y) => y.touched - x.touched || y.entity - x.entity);
415
+ return { entity: e ?? null, session: e === undefined ? null : w.get(e, session), apps: w.query(app).map((r) => ({ entity: r.entity, ...r.desktopApp })), windows: e === undefined ? [] : windowsOf(w, e).map((r) => ({ entity: r.entity, ...r.desktopWindow })), sessions, theme: w.get(settings).theme };
416
+ }
417
+ })
418
+ ];
419
+ });
420
+ }
421
+ var shell = desktop({});
422
+
423
+ // src/gui/inspector.tsx
424
+ var import_react = __toESM(require_react(), 1);
425
+ var jsx_runtime = __toESM(require_jsx_runtime(), 1);
426
+ function Inspector({ manifest }) {
427
+ const { w, raw, ready, round } = useBabaState();
428
+ const [filter, setFilter] = import_react.useState("");
429
+ if (!ready)
430
+ return /* @__PURE__ */ jsx_runtime.jsx("p", {
431
+ className: "bv-muted",
432
+ children: "Reading the state…"
433
+ });
434
+ const names = Object.keys(manifest.components);
435
+ const wanted = filter.trim();
436
+ const entities = w.all().filter((e) => !wanted || (wanted in w.of(e)));
437
+ const resources = Object.entries(manifest.resources).map(([n, r]) => ({ name: n, system: r.owner, value: JSON.stringify(w.get({ kind: "resource", name: n })) }));
438
+ return /* @__PURE__ */ jsx_runtime.jsxs("div", {
439
+ children: [
440
+ /* @__PURE__ */ jsx_runtime.jsxs("p", {
441
+ className: "bv-muted",
442
+ children: [
443
+ "round ",
444
+ round,
445
+ " · ",
446
+ w.all().length,
447
+ " entities · ",
448
+ w.jobs.length,
449
+ " jobs"
450
+ ]
451
+ }),
452
+ resources.length > 0 && /* @__PURE__ */ jsx_runtime.jsxs("section", {
453
+ className: "gui-section",
454
+ children: [
455
+ /* @__PURE__ */ jsx_runtime.jsx("h2", {
456
+ children: "Resources"
457
+ }),
458
+ /* @__PURE__ */ jsx_runtime.jsx(Table, {
459
+ columns: ["name", "system", "value"],
460
+ rows: resources
461
+ })
462
+ ]
463
+ }),
464
+ /* @__PURE__ */ jsx_runtime.jsxs("section", {
465
+ className: "gui-section",
466
+ children: [
467
+ /* @__PURE__ */ jsx_runtime.jsx("h2", {
468
+ children: "Entities"
469
+ }),
470
+ /* @__PURE__ */ jsx_runtime.jsxs("div", {
471
+ className: "gui-filter",
472
+ children: [
473
+ /* @__PURE__ */ jsx_runtime.jsx(Input, {
474
+ list: "gui-components",
475
+ placeholder: "with component…",
476
+ value: filter,
477
+ onChange: (e) => setFilter(e.target.value)
478
+ }),
479
+ /* @__PURE__ */ jsx_runtime.jsx("datalist", {
480
+ id: "gui-components",
481
+ children: names.map((n) => /* @__PURE__ */ jsx_runtime.jsx("option", {
482
+ value: n
483
+ }, n))
484
+ }),
485
+ wanted && /* @__PURE__ */ jsx_runtime.jsx(Button, {
486
+ variant: "quiet",
487
+ onClick: () => setFilter(""),
488
+ children: "clear"
489
+ })
490
+ ]
491
+ }),
492
+ entities.length === 0 && /* @__PURE__ */ jsx_runtime.jsxs("p", {
493
+ className: "bv-muted",
494
+ children: [
495
+ "No entity",
496
+ wanted ? ` with ${wanted}` : "",
497
+ "."
498
+ ]
499
+ }),
500
+ entities.map((e) => /* @__PURE__ */ jsx_runtime.jsxs("div", {
501
+ className: "gui-entity",
502
+ children: [
503
+ /* @__PURE__ */ jsx_runtime.jsxs("code", {
504
+ children: [
505
+ "#",
506
+ e
507
+ ]
508
+ }),
509
+ /* @__PURE__ */ jsx_runtime.jsxs("div", {
510
+ className: "gui-parts",
511
+ children: [
512
+ Object.entries(w.of(e)).map(([n, v]) => /* @__PURE__ */ jsx_runtime.jsxs("span", {
513
+ className: "gui-part",
514
+ children: [
515
+ /* @__PURE__ */ jsx_runtime.jsx("b", {
516
+ children: n
517
+ }),
518
+ /* @__PURE__ */ jsx_runtime.jsx("pre", {
519
+ children: v === true ? "" : JSON.stringify(v)
520
+ })
521
+ ]
522
+ }, n)),
523
+ w.jobs.filter((j) => j.entity === e).map((j) => /* @__PURE__ */ jsx_runtime.jsxs("span", {
524
+ className: "gui-part bv-muted",
525
+ children: [
526
+ /* @__PURE__ */ jsx_runtime.jsx("b", {
527
+ children: j.effect
528
+ }),
529
+ /* @__PURE__ */ jsx_runtime.jsx("pre", {
530
+ children: j.status
531
+ })
532
+ ]
533
+ }, j.effect))
534
+ ]
535
+ }),
536
+ /* @__PURE__ */ jsx_runtime.jsx(Button, {
537
+ variant: "quiet",
538
+ onClick: () => void raw({ op: "despawn", entity: e }),
539
+ children: "despawn"
540
+ })
541
+ ]
542
+ }, e))
543
+ ]
544
+ }),
545
+ w.jobs.length > 0 && /* @__PURE__ */ jsx_runtime.jsxs("section", {
546
+ className: "gui-section",
547
+ children: [
548
+ /* @__PURE__ */ jsx_runtime.jsx("h2", {
549
+ children: "Jobs"
550
+ }),
551
+ /* @__PURE__ */ jsx_runtime.jsx(Table, {
552
+ columns: ["effect", "entity", "args", "status"],
553
+ rows: w.jobs.map((j) => ({ effect: j.effect, entity: j.entity ?? "baba", args: JSON.stringify(j.args), status: j.status }))
554
+ })
555
+ ]
556
+ })
557
+ ]
558
+ });
559
+ }
560
+
561
+ // src/gui/runner.tsx
562
+ var import_react2 = __toESM(require_react(), 1);
563
+ var jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
564
+ function Runner({ manifest }) {
565
+ const [selected, setSelected] = import_react2.useState(null);
566
+ const calls = {};
567
+ for (const [n, d] of Object.entries(manifest.actions))
568
+ calls[n] = { kind: "fire", summary: d.summary, args: d.args, result: d.result };
569
+ for (const [n, q] of Object.entries(manifest.queries))
570
+ calls[n] = { kind: "read", summary: q.summary, args: q.args, result: q.result };
571
+ const names = Object.keys(calls);
572
+ if (!selected) {
573
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
574
+ className: "bv-runner",
575
+ children: [
576
+ names.length === 0 && /* @__PURE__ */ jsx_runtime2.jsx("p", {
577
+ className: "bv-muted",
578
+ children: "The baba declares no actions or queries."
579
+ }),
580
+ /* @__PURE__ */ jsx_runtime2.jsx(Rows, {
581
+ children: names.map((n) => /* @__PURE__ */ jsx_runtime2.jsx(Row, {
582
+ hint: `${calls[n].kind === "fire" ? "action" : "query"} · ${calls[n].summary}`,
583
+ onClick: () => setSelected(n),
584
+ children: /* @__PURE__ */ jsx_runtime2.jsx("code", {
585
+ children: n
586
+ })
587
+ }, n))
588
+ })
589
+ ]
590
+ });
591
+ }
592
+ const def = calls[selected];
593
+ return /* @__PURE__ */ jsx_runtime2.jsxs("div", {
594
+ className: "bv-runner",
595
+ children: [
596
+ /* @__PURE__ */ jsx_runtime2.jsx("div", {
597
+ className: "bv-runner-nav",
598
+ children: /* @__PURE__ */ jsx_runtime2.jsx(Button, {
599
+ variant: "quiet",
600
+ onClick: () => setSelected(null),
601
+ children: "← All"
602
+ })
603
+ }),
604
+ /* @__PURE__ */ jsx_runtime2.jsx(Form, {
605
+ name: selected,
606
+ def
607
+ }, selected)
608
+ ]
609
+ });
610
+ }
611
+ function Control({ id, f, value, set, invalid }) {
612
+ const text = typeof value === "string" ? value : "";
613
+ const common = { id, "aria-invalid": invalid || undefined, "aria-required": f.required || undefined };
614
+ switch (f.kind) {
615
+ case "boolean":
616
+ return /* @__PURE__ */ jsx_runtime2.jsx("input", {
617
+ type: "checkbox",
618
+ className: "bv-check",
619
+ ...common,
620
+ checked: value === true,
621
+ onChange: (e) => set(e.target.checked)
622
+ });
623
+ case "enum":
624
+ return /* @__PURE__ */ jsx_runtime2.jsxs(Select, {
625
+ ...common,
626
+ value: text,
627
+ onChange: (e) => set(e.target.value),
628
+ children: [
629
+ /* @__PURE__ */ jsx_runtime2.jsx("option", {
630
+ value: "",
631
+ children: f.required ? "Choose…" : "(none)"
632
+ }),
633
+ f.options.map((o) => /* @__PURE__ */ jsx_runtime2.jsx("option", {
634
+ value: o,
635
+ children: o
636
+ }, o))
637
+ ]
638
+ });
639
+ case "number":
640
+ case "integer":
641
+ return /* @__PURE__ */ jsx_runtime2.jsx(Input, {
642
+ ...common,
643
+ type: "number",
644
+ step: f.kind === "integer" ? 1 : "any",
645
+ value: text,
646
+ onChange: (e) => set(e.target.value)
647
+ });
648
+ case "list":
649
+ return /* @__PURE__ */ jsx_runtime2.jsx(Textarea, {
650
+ ...common,
651
+ rows: 3,
652
+ placeholder: `one ${f.item} per line`,
653
+ value: text,
654
+ onChange: (e) => set(e.target.value)
655
+ });
656
+ case "json":
657
+ return /* @__PURE__ */ jsx_runtime2.jsx(Textarea, {
658
+ ...common,
659
+ rows: 4,
660
+ className: "bv-mono",
661
+ placeholder: "JSON",
662
+ value: text,
663
+ onChange: (e) => set(e.target.value)
664
+ });
665
+ default:
666
+ return /* @__PURE__ */ jsx_runtime2.jsx(Input, {
667
+ ...common,
668
+ value: text,
669
+ onChange: (e) => set(e.target.value)
670
+ });
671
+ }
672
+ }
673
+ function Form({ name, def }) {
674
+ const scope = useScope();
675
+ const uid = import_react2.useId();
676
+ const p = plan(def.args);
677
+ const [values, setValues] = import_react2.useState({});
678
+ const [outcome, setOutcome] = import_react2.useState({ state: "idle" });
679
+ const run = async (e) => {
680
+ e?.preventDefault();
681
+ let args;
682
+ try {
683
+ args = collect(p, values);
684
+ } catch (err) {
685
+ setOutcome({ state: "failed", error: err });
686
+ return;
687
+ }
688
+ setOutcome({ state: "running" });
689
+ const t = performance.now();
690
+ try {
691
+ const value = await api(def.kind, scope, name, args);
692
+ setOutcome({ state: "done", value, ms: Math.round(performance.now() - t) });
693
+ } catch (err) {
694
+ setOutcome({ state: "failed", error: err });
695
+ }
696
+ };
697
+ const bad = outcome.state === "failed" && outcome.error instanceof FieldError ? outcome.error.field : null;
698
+ return /* @__PURE__ */ jsx_runtime2.jsxs("form", {
699
+ className: "bv-form",
700
+ onSubmit: run,
701
+ children: [
702
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
703
+ className: "bv-form-head",
704
+ children: [
705
+ /* @__PURE__ */ jsx_runtime2.jsx("code", {
706
+ className: "bv-form-name",
707
+ children: name
708
+ }),
709
+ /* @__PURE__ */ jsx_runtime2.jsxs("span", {
710
+ className: "bv-muted",
711
+ children: [
712
+ def.kind === "fire" ? "action" : "query",
713
+ " · ",
714
+ def.summary
715
+ ]
716
+ })
717
+ ]
718
+ }),
719
+ p.fields.length === 0 && /* @__PURE__ */ jsx_runtime2.jsx("p", {
720
+ className: "bv-muted",
721
+ children: "No arguments."
722
+ }),
723
+ p.fields.map((f) => /* @__PURE__ */ jsx_runtime2.jsxs("div", {
724
+ className: f.kind === "boolean" ? "bv-field bv-field-inline" : "bv-field",
725
+ children: [
726
+ /* @__PURE__ */ jsx_runtime2.jsxs("label", {
727
+ htmlFor: `${uid}-${f.name}`,
728
+ children: [
729
+ f.name,
730
+ f.required && /* @__PURE__ */ jsx_runtime2.jsx("span", {
731
+ className: "bv-required",
732
+ title: "required",
733
+ children: "*"
734
+ }),
735
+ /* @__PURE__ */ jsx_runtime2.jsx("span", {
736
+ className: "bv-type",
737
+ children: describeType(f)
738
+ })
739
+ ]
740
+ }),
741
+ /* @__PURE__ */ jsx_runtime2.jsx(Control, {
742
+ id: `${uid}-${f.name}`,
743
+ f,
744
+ value: values[f.name],
745
+ set: (v) => setValues((s2) => ({ ...s2, [f.name]: v })),
746
+ invalid: bad === f.name
747
+ }),
748
+ f.description && /* @__PURE__ */ jsx_runtime2.jsx("small", {
749
+ className: "bv-muted",
750
+ children: f.description
751
+ })
752
+ ]
753
+ }, f.name)),
754
+ /* @__PURE__ */ jsx_runtime2.jsxs("div", {
755
+ className: "bv-form-actions",
756
+ children: [
757
+ /* @__PURE__ */ jsx_runtime2.jsx(Button, {
758
+ type: "submit",
759
+ variant: "primary",
760
+ disabled: outcome.state === "running",
761
+ children: outcome.state === "running" ? "Running…" : def.kind === "fire" ? "Run" : "Read"
762
+ }),
763
+ outcome.state === "done" && /* @__PURE__ */ jsx_runtime2.jsxs("span", {
764
+ className: "bv-muted",
765
+ children: [
766
+ outcome.ms,
767
+ " ms"
768
+ ]
769
+ })
770
+ ]
771
+ }),
772
+ outcome.state === "failed" && /* @__PURE__ */ jsx_runtime2.jsx(ErrorText, {
773
+ error: outcome.error
774
+ }),
775
+ outcome.state === "done" && /* @__PURE__ */ jsx_runtime2.jsx(Output, {
776
+ value: outcome.value
777
+ })
778
+ ]
779
+ });
780
+ }
781
+ function ErrorText({ error }) {
782
+ if (error instanceof ApiError)
783
+ return /* @__PURE__ */ jsx_runtime2.jsxs("p", {
784
+ className: "bv-error",
785
+ role: "alert",
786
+ children: [
787
+ /* @__PURE__ */ jsx_runtime2.jsx("code", {
788
+ className: "bv-code",
789
+ children: error.code
790
+ }),
791
+ " ",
792
+ error.message
793
+ ]
794
+ });
795
+ if (error instanceof FieldError)
796
+ return /* @__PURE__ */ jsx_runtime2.jsx("p", {
797
+ className: "bv-error",
798
+ role: "alert",
799
+ children: error.message
800
+ });
801
+ return /* @__PURE__ */ jsx_runtime2.jsx("p", {
802
+ className: "bv-error",
803
+ role: "alert",
804
+ children: error instanceof Error ? error.message : String(error)
805
+ });
806
+ }
807
+ function Output({ value }) {
808
+ const t = tableOf(value);
809
+ if (t)
810
+ return /* @__PURE__ */ jsx_runtime2.jsx("div", {
811
+ className: "bv-output",
812
+ children: /* @__PURE__ */ jsx_runtime2.jsx(Table, {
813
+ columns: t.columns,
814
+ rows: t.rows
815
+ })
816
+ });
817
+ if (value === undefined || value === null)
818
+ return /* @__PURE__ */ jsx_runtime2.jsx("div", {
819
+ className: "bv-output bv-muted",
820
+ children: "Done; nothing came back."
821
+ });
822
+ return /* @__PURE__ */ jsx_runtime2.jsx("pre", {
823
+ className: "bv-output bv-json",
824
+ children: JSON.stringify(value, null, 2)
825
+ });
826
+ }
827
+
828
+ // src/gui/instructions.tsx
829
+ var import_react3 = __toESM(require_react(), 1);
830
+ var jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
831
+ function Instructions({ manifest }) {
832
+ const { fire, read, ready, round } = useBabaState();
833
+ const [plan2, setPlan] = import_react3.useState(null);
834
+ const [error, setError] = import_react3.useState(null);
835
+ import_react3.useEffect(() => {
836
+ if (ready)
837
+ read("agents-plan").then((p) => setPlan(p), setError);
838
+ }, [ready, round]);
839
+ if (!("pwStatus" in manifest.resources))
840
+ return /* @__PURE__ */ jsx_runtime3.jsx("p", {
841
+ className: "bv-muted",
842
+ children: "This baba has no agents system."
843
+ });
844
+ if (!ready)
845
+ return /* @__PURE__ */ jsx_runtime3.jsx("p", {
846
+ className: "bv-muted",
847
+ children: "Reading the state…"
848
+ });
849
+ const act = (name, args) => {
850
+ setError(null);
851
+ return fire(name, args).catch((e) => {
852
+ setError(e);
853
+ throw e;
854
+ });
855
+ };
856
+ return /* @__PURE__ */ jsx_runtime3.jsxs("div", {
857
+ children: [
858
+ error !== null && /* @__PURE__ */ jsx_runtime3.jsx(ErrorText, {
859
+ error
860
+ }),
861
+ /* @__PURE__ */ jsx_runtime3.jsx(Declared, {
862
+ manifest
863
+ }),
864
+ /* @__PURE__ */ jsx_runtime3.jsxs("section", {
865
+ className: "gui-section",
866
+ children: [
867
+ /* @__PURE__ */ jsx_runtime3.jsx("h2", {
868
+ children: "Outputs"
869
+ }),
870
+ /* @__PURE__ */ jsx_runtime3.jsxs("div", {
871
+ className: "bv-form-actions",
872
+ children: [
873
+ /* @__PURE__ */ jsx_runtime3.jsx(Button, {
874
+ variant: "primary",
875
+ onClick: () => void act("agents-sync", {}).catch(() => {}),
876
+ children: "Sync now"
877
+ }),
878
+ plan2 && /* @__PURE__ */ jsx_runtime3.jsxs("span", {
879
+ className: "bv-muted",
880
+ children: [
881
+ plan2.pending ? `${plan2.pending} to write` : "up to date",
882
+ " · ",
883
+ plan2.auto ? "writes on every change" : "writes on sync",
884
+ plan2.lastSync ? ` · synced ${new Date(plan2.lastSync).toLocaleTimeString()}` : ""
885
+ ]
886
+ })
887
+ ]
888
+ }),
889
+ plan2?.error && /* @__PURE__ */ jsx_runtime3.jsx("p", {
890
+ className: "bv-error",
891
+ children: plan2.error
892
+ }),
893
+ plan2 && plan2.outputs.length > 0 && /* @__PURE__ */ jsx_runtime3.jsx(Table, {
894
+ columns: ["path", "harness", "status", "action", "reason"],
895
+ rows: plan2.outputs.map((o) => ({ ...o, action: o.action ?? "" }))
896
+ })
897
+ ]
898
+ }),
899
+ /* @__PURE__ */ jsx_runtime3.jsx(Harness, {
900
+ manifest
901
+ })
902
+ ]
903
+ });
904
+ }
905
+ function Declared({ manifest }) {
906
+ const pw = manifest.instructions;
907
+ const skills = Object.entries(pw.skills);
908
+ return /* @__PURE__ */ jsx_runtime3.jsxs("section", {
909
+ className: "gui-section",
910
+ children: [
911
+ /* @__PURE__ */ jsx_runtime3.jsx("h2", {
912
+ children: "Instructions"
913
+ }),
914
+ !pw.context && skills.length === 0 && pw.hooks.length === 0 && /* @__PURE__ */ jsx_runtime3.jsx("p", {
915
+ className: "bv-muted",
916
+ children: "Nothing declared yet: give the baba a `context(...)`, a `skill(...)` or a `hook(...)` under `.baba/agents/`."
917
+ }),
918
+ pw.context && /* @__PURE__ */ jsx_runtime3.jsxs("p", {
919
+ className: "bv-muted",
920
+ children: [
921
+ pw.context.length,
922
+ " characters of context, ",
923
+ pw.hooks.length,
924
+ " hook",
925
+ pw.hooks.length === 1 ? "" : "s",
926
+ "."
927
+ ]
928
+ }),
929
+ pw.hooks.length > 0 && /* @__PURE__ */ jsx_runtime3.jsx(Rows, {
930
+ children: pw.hooks.map((h, i) => /* @__PURE__ */ jsx_runtime3.jsx(Row, {
931
+ hint: `${h.on}${h.match ? ` on ${h.match}` : ""}${h.strict ? " · strict" : ""}`,
932
+ children: /* @__PURE__ */ jsx_runtime3.jsx("code", {
933
+ children: h.action
934
+ })
935
+ }, i))
936
+ }),
937
+ skills.length > 0 && /* @__PURE__ */ jsx_runtime3.jsx(Rows, {
938
+ children: skills.map(([k, sk]) => /* @__PURE__ */ jsx_runtime3.jsx(Row, {
939
+ hint: sk.description,
940
+ children: /* @__PURE__ */ jsx_runtime3.jsx("code", {
941
+ children: k
942
+ })
943
+ }, k))
944
+ }),
945
+ /* @__PURE__ */ jsx_runtime3.jsx("p", {
946
+ className: "bv-muted",
947
+ children: "The agentic instructions is the baba's code: edit it under .baba/agents/; the files below follow."
948
+ })
949
+ ]
950
+ });
951
+ }
952
+ function Harness({ manifest }) {
953
+ const h = manifest.harness;
954
+ return /* @__PURE__ */ jsx_runtime3.jsxs("section", {
955
+ className: "gui-section",
956
+ children: [
957
+ /* @__PURE__ */ jsx_runtime3.jsx("h2", {
958
+ children: "Harness"
959
+ }),
960
+ /* @__PURE__ */ jsx_runtime3.jsxs(Rows, {
961
+ children: [
962
+ /* @__PURE__ */ jsx_runtime3.jsx(Row, {
963
+ hint: h.claude.enabled ? `permissions: ${h.claude.permissions.join(", ")}` : "off",
964
+ children: /* @__PURE__ */ jsx_runtime3.jsx("code", {
965
+ children: "claude"
966
+ })
967
+ }),
968
+ /* @__PURE__ */ jsx_runtime3.jsx(Row, {
969
+ hint: h.codex.enabled ? "on" : "off",
970
+ children: /* @__PURE__ */ jsx_runtime3.jsx("code", {
971
+ children: "codex"
972
+ })
973
+ }),
974
+ /* @__PURE__ */ jsx_runtime3.jsx(Row, {
975
+ hint: `${h.auto ? "writes on every change" : "writes on sync"}${h.table ? " · the contract as a table" : ""}`,
976
+ children: /* @__PURE__ */ jsx_runtime3.jsx("code", {
977
+ children: "files"
978
+ })
979
+ })
980
+ ]
981
+ }),
982
+ /* @__PURE__ */ jsx_runtime3.jsxs("p", {
983
+ className: "bv-muted",
984
+ children: [
985
+ "The .mdx files under .baba/agents/, each ",
986
+ /* @__PURE__ */ jsx_runtime3.jsx("code", {
987
+ children: 'export const prompt = { kind: "context" | "skill", … }'
988
+ }),
989
+ "; voss reads them on its own."
990
+ ]
991
+ })
992
+ ]
993
+ });
994
+ }
995
+
996
+ // src/gui/levels.tsx
997
+ var import_react5 = __toESM(require_react(), 1);
998
+
999
+ // src/gui/theme.ts
1000
+ var themeKey = "babavoss.theme";
1001
+ var early = `try{var t=localStorage.getItem(${JSON.stringify(themeKey)});if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}`;
1002
+ function theme() {
1003
+ try {
1004
+ const v = localStorage.getItem(themeKey);
1005
+ if (v === "light" || v === "dark")
1006
+ return v;
1007
+ } catch {}
1008
+ return "system";
1009
+ }
1010
+
1011
+ // src/gui/wizard.tsx
1012
+ var import_react4 = __toESM(require_react(), 1);
1013
+ var jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
1014
+ var getJson = async (path) => {
1015
+ const r = await fetch(path, { headers: apiHeaders() });
1016
+ const j = await r.json();
1017
+ if (!r.ok || j.error)
1018
+ throw new Error(j.error?.message ?? r.statusText);
1019
+ return j;
1020
+ };
1021
+ var postJson = async (path, body) => {
1022
+ const r = await fetch(path, { method: "POST", headers: { "content-type": "application/json", ...apiHeaders() }, body: JSON.stringify(body) });
1023
+ const j = await r.json();
1024
+ if (!r.ok || j.error)
1025
+ throw new Error(j.error?.message ?? r.statusText);
1026
+ return j;
1027
+ };
1028
+ var tilde = (path, home) => home && (path === home || path.startsWith(home + "/")) ? "~" + path.slice(home.length) : path;
1029
+ var short = (path, home, max = 52) => {
1030
+ const t = tilde(path, home);
1031
+ if (t.length <= max)
1032
+ return t;
1033
+ const parts = t.split("/");
1034
+ const head = parts[0] === "" ? "" : parts[0];
1035
+ for (let keep2 = 3;keep2 >= 1; keep2--) {
1036
+ const s2 = `${head}/…/${parts.slice(-keep2).join("/")}`;
1037
+ if (s2.length <= max || keep2 === 1)
1038
+ return s2;
1039
+ }
1040
+ return t;
1041
+ };
1042
+ var slug = (s2) => s2.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1043
+ var isTyping = (e) => e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement;
1044
+ var Svg = ({ children, size = 16 }) => /* @__PURE__ */ jsx_runtime4.jsx("svg", {
1045
+ width: size,
1046
+ height: size,
1047
+ viewBox: "0 0 16 16",
1048
+ fill: "none",
1049
+ stroke: "currentColor",
1050
+ strokeWidth: "1.4",
1051
+ strokeLinecap: "round",
1052
+ strokeLinejoin: "round",
1053
+ "aria-hidden": "true",
1054
+ children
1055
+ });
1056
+ var FolderGlyph = () => /* @__PURE__ */ jsx_runtime4.jsx(Svg, {
1057
+ children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
1058
+ d: "M2 4.5c0-.6.4-1 1-1h3l1.5 1.5H13c.6 0 1 .4 1 1V12c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1z"
1059
+ })
1060
+ });
1061
+ var BabaGlyph = () => /* @__PURE__ */ jsx_runtime4.jsxs(Svg, {
1062
+ children: [
1063
+ /* @__PURE__ */ jsx_runtime4.jsx("rect", {
1064
+ x: "2.5",
1065
+ y: "2.5",
1066
+ width: "11",
1067
+ height: "11",
1068
+ rx: "3"
1069
+ }),
1070
+ /* @__PURE__ */ jsx_runtime4.jsx("circle", {
1071
+ cx: "8",
1072
+ cy: "8",
1073
+ r: "2",
1074
+ fill: "currentColor",
1075
+ stroke: "none"
1076
+ })
1077
+ ]
1078
+ });
1079
+ var Up = () => /* @__PURE__ */ jsx_runtime4.jsx(Svg, {
1080
+ children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
1081
+ d: "M8 13V3.5M4 7l4-4 4 4"
1082
+ })
1083
+ });
1084
+ var Chevron = () => /* @__PURE__ */ jsx_runtime4.jsx(Svg, {
1085
+ size: 14,
1086
+ children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
1087
+ d: "M6 4l4 4-4 4"
1088
+ })
1089
+ });
1090
+ var Home = () => /* @__PURE__ */ jsx_runtime4.jsx(Svg, {
1091
+ size: 14,
1092
+ children: /* @__PURE__ */ jsx_runtime4.jsx("path", {
1093
+ d: "M2.5 7.5L8 3l5.5 4.5V13H10V9.5H6V13H2.5z"
1094
+ })
1095
+ });
1096
+ var Search = () => /* @__PURE__ */ jsx_runtime4.jsxs(Svg, {
1097
+ size: 14,
1098
+ children: [
1099
+ /* @__PURE__ */ jsx_runtime4.jsx("circle", {
1100
+ cx: "7",
1101
+ cy: "7",
1102
+ r: "4"
1103
+ }),
1104
+ /* @__PURE__ */ jsx_runtime4.jsx("path", {
1105
+ d: "M10 10l3 3"
1106
+ })
1107
+ ]
1108
+ });
1109
+ var Spinner = () => /* @__PURE__ */ jsx_runtime4.jsx("span", {
1110
+ className: "pk-spin",
1111
+ "aria-hidden": "true"
1112
+ });
1113
+ function useInView(list, active) {
1114
+ import_react4.useLayoutEffect(() => {
1115
+ list.current?.querySelector(`[data-i="${active}"]`)?.scrollIntoView({ block: "nearest" });
1116
+ }, [list, active]);
1117
+ }
1118
+ function Folders({ home, start, onBack, onOpened, onVerb }) {
1119
+ const [at, setAt] = import_react4.useState(null);
1120
+ const [loading, setLoading] = import_react4.useState(true);
1121
+ const [error, setError] = import_react4.useState(null);
1122
+ const [busy, setBusy] = import_react4.useState(null);
1123
+ const [filter, setFilter] = import_react4.useState("");
1124
+ const [active, setActive] = import_react4.useState(0);
1125
+ const [name, setName] = import_react4.useState("");
1126
+ const [title, setTitle] = import_react4.useState("");
1127
+ const list = import_react4.useRef(null);
1128
+ const filterRef = import_react4.useRef(null);
1129
+ const homeDir = at?.home || home;
1130
+ const walk = (path) => {
1131
+ setLoading(true);
1132
+ getJson(`/api/dirs?path=${encodeURIComponent(path)}`).then((b) => {
1133
+ const from = at?.path;
1134
+ const back = from ? b.dirs.findIndex((d) => d.path === from) : -1;
1135
+ setAt(b);
1136
+ setError(null);
1137
+ setFilter("");
1138
+ setName("");
1139
+ setTitle("");
1140
+ onVerb?.(b.known ? "open" : b.baba ? "remember" : "make");
1141
+ if (!(document.activeElement instanceof HTMLInputElement) || document.activeElement === filterRef.current)
1142
+ filterRef.current?.focus();
1143
+ setActive(back >= 0 ? back + (b.parent ? 1 : 0) : 0);
1144
+ }, (e) => setError(String(e instanceof Error ? e.message : e))).finally(() => setLoading(false));
1145
+ };
1146
+ import_react4.useEffect(() => {
1147
+ walk(start ?? "");
1148
+ filterRef.current?.focus();
1149
+ }, []);
1150
+ const words = filter.toLowerCase().split(/\s+/).filter(Boolean);
1151
+ const dirs = (at?.dirs ?? []).filter((d) => words.every((w) => d.name.toLowerCase().includes(w)));
1152
+ const rows = [
1153
+ ...at?.parent && !words.length ? [{ kind: "up", path: at.parent }] : [],
1154
+ ...dirs.map((d) => ({ kind: "dir", d }))
1155
+ ];
1156
+ const cur = Math.min(active, Math.max(0, rows.length - 1));
1157
+ useInView(list, cur);
1158
+ const act = async () => {
1159
+ if (!at || busy)
1160
+ return;
1161
+ if (at.known) {
1162
+ const p = await getJson("/api/projects");
1163
+ const own = p.projects.find((x) => x.dir === at.path);
1164
+ if (own)
1165
+ onOpened(own.name);
1166
+ return;
1167
+ }
1168
+ const init = !at.baba;
1169
+ setBusy(init ? "Making the baba and installing its packages…" : "Opening the baba…");
1170
+ setError(null);
1171
+ try {
1172
+ const made = await postJson("/api/projects/add", { dir: at.path, init, ...name.trim() ? { name: name.trim() } : {}, ...title.trim() ? { title: title.trim() } : {} });
1173
+ onOpened(made.name);
1174
+ } catch (e) {
1175
+ setError(String(e instanceof Error ? e.message : e));
1176
+ setBusy(null);
1177
+ }
1178
+ };
1179
+ const enter = (i) => {
1180
+ const r = rows[i];
1181
+ if (r)
1182
+ walk(r.kind === "up" ? r.path : r.d.path);
1183
+ };
1184
+ import_react4.useEffect(() => {
1185
+ const key = (e) => {
1186
+ if (busy || document.querySelector(".desktop-launcher"))
1187
+ return;
1188
+ const inField = isTyping(e) && e.target !== filterRef.current;
1189
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
1190
+ e.preventDefault();
1191
+ act();
1192
+ return;
1193
+ }
1194
+ if (e.key === "Escape") {
1195
+ e.preventDefault();
1196
+ if (inField)
1197
+ e.target.blur();
1198
+ else if (filter)
1199
+ setFilter("");
1200
+ else
1201
+ onBack();
1202
+ return;
1203
+ }
1204
+ if (inField)
1205
+ return;
1206
+ if (e.key === "ArrowDown") {
1207
+ e.preventDefault();
1208
+ setActive(Math.min(rows.length - 1, cur + 1));
1209
+ } else if (e.key === "ArrowUp") {
1210
+ e.preventDefault();
1211
+ setActive(Math.max(0, cur - 1));
1212
+ } else if (e.key === "Enter") {
1213
+ e.preventDefault();
1214
+ enter(cur);
1215
+ } else if (e.key === "ArrowRight" && rows[cur]?.kind === "dir") {
1216
+ e.preventDefault();
1217
+ enter(cur);
1218
+ } else if (e.key === "Backspace" && !filter || e.key === "ArrowLeft" && !filter) {
1219
+ if (at?.parent) {
1220
+ e.preventDefault();
1221
+ walk(at.parent);
1222
+ }
1223
+ } else if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey && document.activeElement !== filterRef.current)
1224
+ filterRef.current?.focus();
1225
+ };
1226
+ addEventListener("keydown", key);
1227
+ return () => removeEventListener("keydown", key);
1228
+ });
1229
+ const crumbs = import_react4.useMemo(() => {
1230
+ if (!at)
1231
+ return [];
1232
+ const t = tilde(at.path, homeDir);
1233
+ const parts = t.split("/").filter(Boolean);
1234
+ const root = t.startsWith("~") ? homeDir : "";
1235
+ return parts.map((part, i) => ({ label: part, path: part === "~" && i === 0 ? homeDir : t.startsWith("~") ? root + "/" + parts.slice(1, i + 1).join("/") : "/" + parts.slice(0, i + 1).join("/") }));
1236
+ }, [at, homeDir]);
1237
+ const shown = crumbs.length > 5 ? [crumbs[0], { label: "…", path: crumbs[crumbs.length - 4].path }, ...crumbs.slice(-3)] : crumbs;
1238
+ const here = at ? short(at.path, homeDir, 44) : "";
1239
+ const leaf = at ? at.path.split("/").filter(Boolean).pop() ?? "" : "";
1240
+ return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1241
+ className: "pk-pane pk-fade",
1242
+ children: [
1243
+ /* @__PURE__ */ jsx_runtime4.jsxs("header", {
1244
+ className: "pk-strip",
1245
+ children: [
1246
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1247
+ className: "pk-strip-title",
1248
+ children: "New baba"
1249
+ }),
1250
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1251
+ className: "pk-gap"
1252
+ }),
1253
+ loading && /* @__PURE__ */ jsx_runtime4.jsx(Spinner, {})
1254
+ ]
1255
+ }),
1256
+ /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1257
+ className: "pk-path",
1258
+ children: [
1259
+ /* @__PURE__ */ jsx_runtime4.jsxs("nav", {
1260
+ className: "pk-crumbs",
1261
+ "aria-label": "Folder",
1262
+ children: [
1263
+ !crumbs.length && /* @__PURE__ */ jsx_runtime4.jsx("span", {
1264
+ className: "pk-crumb is-here",
1265
+ children: "/"
1266
+ }),
1267
+ shown.map((c, i) => /* @__PURE__ */ jsx_runtime4.jsxs("span", {
1268
+ className: "pk-crumb-wrap",
1269
+ children: [
1270
+ i > 0 && /* @__PURE__ */ jsx_runtime4.jsx("span", {
1271
+ className: "pk-crumb-sep",
1272
+ children: "/"
1273
+ }),
1274
+ c.label === "…" ? /* @__PURE__ */ jsx_runtime4.jsx("button", {
1275
+ type: "button",
1276
+ className: "pk-crumb",
1277
+ onClick: () => walk(c.path),
1278
+ title: tilde(c.path, homeDir),
1279
+ children: "…"
1280
+ }) : i === shown.length - 1 ? /* @__PURE__ */ jsx_runtime4.jsx("span", {
1281
+ className: "pk-crumb is-here",
1282
+ children: c.label === "~" ? /* @__PURE__ */ jsx_runtime4.jsx(Home, {}) : c.label
1283
+ }) : /* @__PURE__ */ jsx_runtime4.jsx("button", {
1284
+ type: "button",
1285
+ className: "pk-crumb",
1286
+ onClick: () => walk(c.path),
1287
+ title: tilde(c.path, homeDir),
1288
+ children: c.label === "~" ? /* @__PURE__ */ jsx_runtime4.jsx(Home, {}) : c.label
1289
+ })
1290
+ ]
1291
+ }, c.path))
1292
+ ]
1293
+ }),
1294
+ /* @__PURE__ */ jsx_runtime4.jsxs("label", {
1295
+ className: "pk-filter",
1296
+ children: [
1297
+ /* @__PURE__ */ jsx_runtime4.jsx(Search, {}),
1298
+ /* @__PURE__ */ jsx_runtime4.jsx("input", {
1299
+ ref: filterRef,
1300
+ value: filter,
1301
+ placeholder: "Filter",
1302
+ "aria-label": "Filter folders",
1303
+ spellCheck: false,
1304
+ onChange: (e) => {
1305
+ setFilter(e.target.value);
1306
+ setActive(0);
1307
+ }
1308
+ })
1309
+ ]
1310
+ })
1311
+ ]
1312
+ }),
1313
+ /* @__PURE__ */ jsx_runtime4.jsxs("ol", {
1314
+ ref: list,
1315
+ className: loading && at ? "pk-list pk-folders is-loading" : "pk-list pk-folders",
1316
+ role: "listbox",
1317
+ "aria-label": "Folders",
1318
+ children: [
1319
+ !at && !error && [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx_runtime4.jsxs("li", {
1320
+ className: "pk-folder is-ghost",
1321
+ children: [
1322
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1323
+ className: "pk-folder-icon",
1324
+ children: /* @__PURE__ */ jsx_runtime4.jsx(FolderGlyph, {})
1325
+ }),
1326
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1327
+ className: "pk-ghost-line",
1328
+ style: { width: `${30 + i * 17 % 35}%` }
1329
+ })
1330
+ ]
1331
+ }, i)),
1332
+ rows.map((r, i) => r.kind === "up" ? /* @__PURE__ */ jsx_runtime4.jsx("li", {
1333
+ "data-i": i,
1334
+ role: "option",
1335
+ "aria-selected": i === cur,
1336
+ children: /* @__PURE__ */ jsx_runtime4.jsxs("button", {
1337
+ type: "button",
1338
+ className: i === cur ? "pk-folder is-active is-up" : "pk-folder is-up",
1339
+ onClick: () => enter(i),
1340
+ onMouseEnter: () => setActive(i),
1341
+ children: [
1342
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1343
+ className: "pk-folder-icon",
1344
+ children: /* @__PURE__ */ jsx_runtime4.jsx(Up, {})
1345
+ }),
1346
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1347
+ className: "pk-folder-name",
1348
+ children: tilde(r.path, homeDir) === "~" ? "Home" : r.path.split("/").filter(Boolean).pop() ?? "/"
1349
+ }),
1350
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1351
+ className: "pk-folder-note",
1352
+ children: "up"
1353
+ })
1354
+ ]
1355
+ })
1356
+ }, "..") : /* @__PURE__ */ jsx_runtime4.jsx("li", {
1357
+ "data-i": i,
1358
+ role: "option",
1359
+ "aria-selected": i === cur,
1360
+ children: /* @__PURE__ */ jsx_runtime4.jsxs("button", {
1361
+ type: "button",
1362
+ className: `pk-folder${i === cur ? " is-active" : ""}${r.d.baba ? " is-baba" : ""}`,
1363
+ onClick: () => enter(i),
1364
+ onMouseEnter: () => setActive(i),
1365
+ children: [
1366
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1367
+ className: "pk-folder-icon",
1368
+ children: r.d.baba ? /* @__PURE__ */ jsx_runtime4.jsx(BabaGlyph, {}) : /* @__PURE__ */ jsx_runtime4.jsx(FolderGlyph, {})
1369
+ }),
1370
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1371
+ className: "pk-folder-name",
1372
+ children: /* @__PURE__ */ jsx_runtime4.jsx(Marked, {
1373
+ text: r.d.name,
1374
+ words
1375
+ })
1376
+ }),
1377
+ r.d.known ? /* @__PURE__ */ jsx_runtime4.jsx("span", {
1378
+ className: "pk-pill is-known",
1379
+ children: "remembered"
1380
+ }) : r.d.baba ? /* @__PURE__ */ jsx_runtime4.jsx("span", {
1381
+ className: "pk-pill",
1382
+ children: "baba"
1383
+ }) : null,
1384
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1385
+ className: "pk-go",
1386
+ children: /* @__PURE__ */ jsx_runtime4.jsx(Chevron, {})
1387
+ })
1388
+ ]
1389
+ })
1390
+ }, r.d.path)),
1391
+ at && rows.length === 0 && /* @__PURE__ */ jsx_runtime4.jsx("li", {
1392
+ className: "pk-none",
1393
+ children: words.length ? /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1394
+ children: [
1395
+ "No folder matches “",
1396
+ filter,
1397
+ "”."
1398
+ ]
1399
+ }) : /* @__PURE__ */ jsx_runtime4.jsx(jsx_runtime4.Fragment, {
1400
+ children: "No folders in here. You can make the baba right here."
1401
+ })
1402
+ })
1403
+ ]
1404
+ }),
1405
+ error && /* @__PURE__ */ jsx_runtime4.jsx("p", {
1406
+ className: "pk-alert",
1407
+ role: "alert",
1408
+ children: error
1409
+ }),
1410
+ /* @__PURE__ */ jsx_runtime4.jsxs("footer", {
1411
+ className: `pk-dock${at?.known ? " is-known" : at?.baba ? " is-baba" : ""}`,
1412
+ children: [
1413
+ /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1414
+ className: "pk-dock-line is-say",
1415
+ children: [
1416
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1417
+ className: "pk-dock-icon",
1418
+ children: at?.baba ? /* @__PURE__ */ jsx_runtime4.jsx(BabaGlyph, {}) : /* @__PURE__ */ jsx_runtime4.jsx(FolderGlyph, {})
1419
+ }),
1420
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1421
+ className: "pk-dock-text",
1422
+ children: !at ? "…" : at.known ? /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1423
+ children: [
1424
+ "Voss already knows the baba in ",
1425
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1426
+ className: "pk-here",
1427
+ title: at?.path,
1428
+ children: here
1429
+ }),
1430
+ "."
1431
+ ]
1432
+ }) : at.baba ? /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1433
+ children: [
1434
+ "There is a baba in ",
1435
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1436
+ className: "pk-here",
1437
+ title: at?.path,
1438
+ children: here
1439
+ }),
1440
+ ". Remember it to open it here."
1441
+ ]
1442
+ }) : /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1443
+ children: [
1444
+ "A new baba goes in ",
1445
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1446
+ className: "pk-here",
1447
+ title: at?.path,
1448
+ children: here
1449
+ }),
1450
+ ", as ",
1451
+ /* @__PURE__ */ jsx_runtime4.jsx("code", {
1452
+ children: ".baba/"
1453
+ }),
1454
+ " beside what is there."
1455
+ ]
1456
+ })
1457
+ })
1458
+ ]
1459
+ }),
1460
+ /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1461
+ className: "pk-dock-line",
1462
+ children: [
1463
+ at && !at.baba && /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1464
+ children: [
1465
+ /* @__PURE__ */ jsx_runtime4.jsxs("label", {
1466
+ className: "pk-field",
1467
+ children: [
1468
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1469
+ children: "Name"
1470
+ }),
1471
+ /* @__PURE__ */ jsx_runtime4.jsx("input", {
1472
+ value: name,
1473
+ placeholder: slug(leaf) || "baba",
1474
+ spellCheck: false,
1475
+ disabled: !!busy,
1476
+ onChange: (e) => setName(e.target.value),
1477
+ onKeyDown: (e) => {
1478
+ if (e.key === "Enter") {
1479
+ e.preventDefault();
1480
+ act();
1481
+ }
1482
+ }
1483
+ })
1484
+ ]
1485
+ }),
1486
+ /* @__PURE__ */ jsx_runtime4.jsxs("label", {
1487
+ className: "pk-field is-wide",
1488
+ children: [
1489
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1490
+ children: "Title"
1491
+ }),
1492
+ /* @__PURE__ */ jsx_runtime4.jsx("input", {
1493
+ value: title,
1494
+ placeholder: name.trim() || slug(leaf) || "baba",
1495
+ disabled: !!busy,
1496
+ onChange: (e) => setTitle(e.target.value),
1497
+ onKeyDown: (e) => {
1498
+ if (e.key === "Enter") {
1499
+ e.preventDefault();
1500
+ act();
1501
+ }
1502
+ }
1503
+ })
1504
+ ]
1505
+ })
1506
+ ]
1507
+ }),
1508
+ busy && /* @__PURE__ */ jsx_runtime4.jsxs("span", {
1509
+ className: "pk-busy",
1510
+ children: [
1511
+ /* @__PURE__ */ jsx_runtime4.jsx(Spinner, {}),
1512
+ busy
1513
+ ]
1514
+ }),
1515
+ /* @__PURE__ */ jsx_runtime4.jsx("span", {
1516
+ className: "pk-gap"
1517
+ }),
1518
+ /* @__PURE__ */ jsx_runtime4.jsxs("button", {
1519
+ type: "button",
1520
+ className: "pk-primary",
1521
+ disabled: !at || !!busy || loading,
1522
+ onClick: () => void act(),
1523
+ children: [
1524
+ !at ? "Make a baba here" : at.known ? "Open it" : at.baba ? "Remember this baba" : "Make a baba here",
1525
+ /* @__PURE__ */ jsx_runtime4.jsx("kbd", {
1526
+ className: "pk-kbd is-on-primary",
1527
+ children: "⌘↵"
1528
+ })
1529
+ ]
1530
+ })
1531
+ ]
1532
+ })
1533
+ ]
1534
+ })
1535
+ ]
1536
+ });
1537
+ }
1538
+ function Marked({ text, words }) {
1539
+ if (!words.length)
1540
+ return /* @__PURE__ */ jsx_runtime4.jsx(jsx_runtime4.Fragment, {
1541
+ children: text
1542
+ });
1543
+ const lower = text.toLowerCase();
1544
+ const hit = new Array(text.length).fill(false);
1545
+ for (const w of words) {
1546
+ let i2 = lower.indexOf(w);
1547
+ while (i2 >= 0) {
1548
+ for (let k = i2;k < i2 + w.length; k++)
1549
+ hit[k] = true;
1550
+ i2 = lower.indexOf(w, i2 + w.length);
1551
+ }
1552
+ }
1553
+ const out = [];
1554
+ let i = 0;
1555
+ while (i < text.length) {
1556
+ const on = hit[i];
1557
+ let j = i;
1558
+ while (j < text.length && hit[j] === on)
1559
+ j++;
1560
+ out.push(on ? /* @__PURE__ */ jsx_runtime4.jsx("mark", {
1561
+ children: text.slice(i, j)
1562
+ }, i) : text.slice(i, j));
1563
+ i = j;
1564
+ }
1565
+ return /* @__PURE__ */ jsx_runtime4.jsx(jsx_runtime4.Fragment, {
1566
+ children: out
1567
+ });
1568
+ }
1569
+
1570
+ // src/gui/levels.tsx
1571
+ var jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
1572
+ function useProjects() {
1573
+ const [state, setState] = import_react5.useState({ projects: null, home: "" });
1574
+ import_react5.useEffect(() => {
1575
+ let live = true;
1576
+ getJson("/api/projects").then((j) => {
1577
+ if (live)
1578
+ setState({ projects: j.projects, home: j.home ?? "" });
1579
+ }, () => {
1580
+ if (live)
1581
+ setState({ projects: [], home: "" });
1582
+ });
1583
+ return () => {
1584
+ live = false;
1585
+ };
1586
+ }, []);
1587
+ return state;
1588
+ }
1589
+ var href = (name) => `/${encodeURIComponent(name)}/`;
1590
+ var plus = [{ tag: "path", attrs: { d: "M5 12h14" } }, { tag: "path", attrs: { d: "M12 5v14" } }];
1591
+ var eyes = [{ tag: "circle", attrs: { cx: "8.1", cy: "12", r: "4.3" } }, { tag: "circle", attrs: { cx: "17.2", cy: "12", r: "3" } }];
1592
+ var NEW = "new";
1593
+ var babaKey = (name) => `baba:${name}`;
1594
+ function pickerApps(projects) {
1595
+ const byName = new Map(projects.map((p) => [p.name, p]));
1596
+ const rows = projects.map((p, i) => {
1597
+ const parent = p.parent ? byName.get(p.parent) : undefined;
1598
+ return { entity: 10 + i, key: babaKey(p.name), title: p.title, group: parent ? `In ${parent.title}` : "Babas", icon: "", many: false, order: i, drawing: eyes };
1599
+ });
1600
+ rows.push({ entity: 2, key: NEW, title: "New baba", group: "Make", icon: "plus", many: false, order: projects.length, drawing: plus });
1601
+ return rows;
1602
+ }
1603
+ var wizard = { entity: 3, session: 1, app: 2, path: "/", title: "", memory: {} };
1604
+ function Root() {
1605
+ const { projects, home } = useProjects();
1606
+ const [open, setOpen] = import_react5.useState(false);
1607
+ const apps = import_react5.useMemo(() => pickerApps(projects ?? []), [projects]);
1608
+ const picture = {
1609
+ session: { entity: 1, key: "main", selected: open ? wizard.entity : null, theme: "system" },
1610
+ theme: theme(),
1611
+ apps,
1612
+ windows: open ? [wizard] : []
1613
+ };
1614
+ const hand = { fire: (name, args) => {
1615
+ const a = args ?? {};
1616
+ if (name === "desktop-open") {
1617
+ if (a.app === NEW)
1618
+ setOpen(true);
1619
+ else if (a.app?.startsWith("baba:"))
1620
+ location.assign(href(a.app.slice(5)));
1621
+ } else if (name === "desktop-select")
1622
+ setOpen(a.window === wizard.entity);
1623
+ else if (name === "desktop-close")
1624
+ setOpen(false);
1625
+ } };
1626
+ return /* @__PURE__ */ jsx_runtime5.jsx("div", {
1627
+ className: "gui-view",
1628
+ children: /* @__PURE__ */ jsx_runtime5.jsx(DesktopFrame, {
1629
+ picture,
1630
+ title: "voss",
1631
+ hand,
1632
+ render: () => /* @__PURE__ */ jsx_runtime5.jsx(Folders, {
1633
+ home,
1634
+ onBack: () => setOpen(false),
1635
+ onOpened: (name) => location.assign(href(name))
1636
+ })
1637
+ })
1638
+ });
1639
+ }
1640
+
1641
+ // src/kernel/names.ts
1642
+ var buildName = (b) => b.dev ? "source" : b.version || b.sha || "unstamped";
1643
+ function tellApart(a, b) {
1644
+ const na = buildName(a), nb = buildName(b);
1645
+ if (na !== nb)
1646
+ return [na, nb];
1647
+ const when = (x) => x.at ? `${buildName(x)} built ${x.at.slice(11, 19)}Z` : buildName(x);
1648
+ return [when(a), when(b)];
1649
+ }
1650
+
1651
+ // src/gui/gui.tsx
1652
+ var jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
1653
+ var scope = document.querySelector('meta[name="voss-scope"]')?.getAttribute("content") ?? "";
1654
+ async function describe(name, since, signal) {
1655
+ const r = await fetch(`/api/describe/${encodeURIComponent(name)}${since ? `?since=${since}` : ""}`, { signal, headers: apiHeaders() });
1656
+ const d = await r.json();
1657
+ if (!r.ok)
1658
+ throw new Error(d.error?.message ?? r.statusText);
1659
+ return d;
1660
+ }
1661
+ function useDescribed(name) {
1662
+ const [d, setD] = import_react6.useState(null);
1663
+ const [failed, setFailed] = import_react6.useState(null);
1664
+ import_react6.useEffect(() => {
1665
+ const ctl = new AbortController;
1666
+ (async () => {
1667
+ let since = 0;
1668
+ while (!ctl.signal.aborted) {
1669
+ try {
1670
+ const next = await describe(name, since, ctl.signal);
1671
+ since = next.gen;
1672
+ setD(next);
1673
+ setFailed(null);
1674
+ } catch (e) {
1675
+ if (ctl.signal.aborted)
1676
+ return;
1677
+ setFailed(e instanceof Error ? e.message : String(e));
1678
+ since = 0;
1679
+ await new Promise((r) => setTimeout(r, 2000));
1680
+ }
1681
+ }
1682
+ })();
1683
+ return () => ctl.abort();
1684
+ }, [name]);
1685
+ return { d, failed };
1686
+ }
1687
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1688
+ function Stale() {
1689
+ const [b, setB] = import_react6.useState(null);
1690
+ const [restarting, setRestarting] = import_react6.useState(false);
1691
+ import_react6.useEffect(() => {
1692
+ let live = true;
1693
+ const tick = async () => {
1694
+ try {
1695
+ const r = await fetch("/api/voss", { headers: apiHeaders() });
1696
+ if (r.ok) {
1697
+ const j = await r.json();
1698
+ if (live)
1699
+ setB(j);
1700
+ }
1701
+ } catch {}
1702
+ };
1703
+ tick();
1704
+ const t = setInterval(() => void tick(), 1e4);
1705
+ return () => {
1706
+ live = false;
1707
+ clearInterval(t);
1708
+ };
1709
+ }, []);
1710
+ if (!b?.stale || !b.installed)
1711
+ return null;
1712
+ const [installed, running] = tellApart(b.installed, b);
1713
+ const restart = async () => {
1714
+ setRestarting(true);
1715
+ try {
1716
+ await fetch("/api/voss/restart", { method: "POST", headers: { "content-type": "application/json", ...apiHeaders() }, body: "{}" });
1717
+ } catch {}
1718
+ const until = Date.now() + 90000;
1719
+ while (Date.now() < until) {
1720
+ await sleep(1000);
1721
+ try {
1722
+ const r = await fetch("/api/voss", { headers: apiHeaders() });
1723
+ if (r.status === 401) {
1724
+ location.reload();
1725
+ return;
1726
+ }
1727
+ if (r.ok && (await r.json()).pid !== b.pid) {
1728
+ location.reload();
1729
+ return;
1730
+ }
1731
+ } catch {}
1732
+ }
1733
+ setRestarting(false);
1734
+ };
1735
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1736
+ className: "gui-stale",
1737
+ role: "status",
1738
+ children: restarting ? /* @__PURE__ */ jsx_runtime6.jsx("span", {
1739
+ children: "Restarting voss…"
1740
+ }) : /* @__PURE__ */ jsx_runtime6.jsxs(jsx_runtime6.Fragment, {
1741
+ children: [
1742
+ /* @__PURE__ */ jsx_runtime6.jsxs("span", {
1743
+ children: [
1744
+ "voss ",
1745
+ /* @__PURE__ */ jsx_runtime6.jsx("b", {
1746
+ children: installed
1747
+ }),
1748
+ " is installed; this page runs on ",
1749
+ /* @__PURE__ */ jsx_runtime6.jsx("b", {
1750
+ children: running
1751
+ }),
1752
+ ". Changes on disk wait for the restart."
1753
+ ]
1754
+ }),
1755
+ /* @__PURE__ */ jsx_runtime6.jsx(Button, {
1756
+ onClick: () => void restart(),
1757
+ children: "Restart to update"
1758
+ })
1759
+ ]
1760
+ })
1761
+ });
1762
+ }
1763
+ var moved = { inspect: "state", world: "state", run: "contract", proofs: "maker" };
1764
+ function Host() {
1765
+ const { d, failed } = useDescribed(scope);
1766
+ const parts = location.pathname.split("/").filter(Boolean);
1767
+ const to = d && parts.length === 2 && !(d.manifest?.apps ?? []).some((a) => a.key === parts[1]) ? moved[parts[1]] : undefined;
1768
+ const [, moveOn] = import_react6.useState(0);
1769
+ import_react6.useEffect(() => {
1770
+ if (to) {
1771
+ history.replaceState(null, "", `/${encodeURIComponent(scope)}/${to}${location.search}${location.hash}`);
1772
+ moveOn((n) => n + 1);
1773
+ }
1774
+ }, [to]);
1775
+ if (to)
1776
+ return null;
1777
+ if (!d)
1778
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1779
+ className: "gui-page",
1780
+ children: /* @__PURE__ */ jsx_runtime6.jsx("p", {
1781
+ className: "bv-muted",
1782
+ children: failed ? `${scope} did not answer: ${failed}` : "…"
1783
+ })
1784
+ });
1785
+ if (!d.manifest)
1786
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1787
+ className: "gui-page",
1788
+ children: /* @__PURE__ */ jsx_runtime6.jsxs("p", {
1789
+ className: "bv-error",
1790
+ children: [
1791
+ d.title,
1792
+ " is not running.",
1793
+ d.error ? ` ${d.error}` : ""
1794
+ ]
1795
+ })
1796
+ });
1797
+ return /* @__PURE__ */ jsx_runtime6.jsx(ShellPage, {
1798
+ d
1799
+ });
1800
+ }
1801
+ function Failure({ f }) {
1802
+ return /* @__PURE__ */ jsx_runtime6.jsxs("div", {
1803
+ className: "gui-notice",
1804
+ role: "alert",
1805
+ children: [
1806
+ /* @__PURE__ */ jsx_runtime6.jsxs("strong", {
1807
+ children: [
1808
+ "The interface did not ",
1809
+ f.stage === "build" ? "build" : f.stage === "load" ? "load" : "render",
1810
+ "."
1811
+ ]
1812
+ }),
1813
+ /* @__PURE__ */ jsx_runtime6.jsx("pre", {
1814
+ className: "gui-failure",
1815
+ children: formatFailure(f)
1816
+ })
1817
+ ]
1818
+ });
1819
+ }
1820
+ var loaded = new Map;
1821
+ async function loadView(name, v) {
1822
+ const have = loaded.get(v);
1823
+ if (have)
1824
+ return have;
1825
+ const base = `/view/${encodeURIComponent(name)}/${v}/`;
1826
+ const res = await fetch(base, { headers: apiHeaders() });
1827
+ const index = await res.json();
1828
+ if (!res.ok || !index.entry)
1829
+ throw new Error(index.error?.message ?? res.statusText);
1830
+ const mod = await import(base + index.entry);
1831
+ const def = mod.default ?? mod.view;
1832
+ if (!def || !isApps(def) && typeof def.shell !== "function")
1833
+ throw new Error("interface.tsx has no default export made by compose()");
1834
+ const css = (index.css ?? []).map((p) => base + p);
1835
+ const l = isApps(def) ? { View: null, apps: def.apps, css } : { View: def.shell, apps: {}, css };
1836
+ loaded.set(v, l);
1837
+ return l;
1838
+ }
1839
+ function useInterface(d) {
1840
+ const v = d.view && !d.view.error ? d.view.v : undefined;
1841
+ const [view, setView] = import_react6.useState(() => v !== undefined ? loaded.get(v) ?? null : null);
1842
+ const [failure, setFailure] = import_react6.useState(null);
1843
+ import_react6.useEffect(() => {
1844
+ if (v === undefined)
1845
+ return;
1846
+ let live = true;
1847
+ loadView(d.name, v).then((l) => {
1848
+ if (live) {
1849
+ setView(l);
1850
+ setFailure(null);
1851
+ }
1852
+ }, (e) => {
1853
+ if (live)
1854
+ setFailure({ stage: "load", message: e instanceof Error ? e.message : String(e) });
1855
+ });
1856
+ return () => {
1857
+ live = false;
1858
+ };
1859
+ }, [d.name, v]);
1860
+ import_react6.useEffect(() => {
1861
+ if (!view)
1862
+ return;
1863
+ const links = view.css.map((href2) => {
1864
+ const l = document.createElement("link");
1865
+ l.rel = "stylesheet";
1866
+ l.href = href2;
1867
+ document.head.appendChild(l);
1868
+ return l;
1869
+ });
1870
+ return () => {
1871
+ for (const l of links)
1872
+ l.remove();
1873
+ };
1874
+ }, [view]);
1875
+ return { view, failure: d.view?.error ?? failure };
1876
+ }
1877
+ var useShellSystem = (apps) => {
1878
+ const key = JSON.stringify(apps ?? []);
1879
+ return import_react6.useMemo(() => desktop({ apps: apps ?? [] }), [key]);
1880
+ };
1881
+ function ShellPage({ d }) {
1882
+ const { view, failure } = useInterface(d);
1883
+ const system2 = useShellSystem(d.manifest?.apps);
1884
+ const manifest = d.manifest;
1885
+ const components = import_react6.useMemo(() => {
1886
+ const pending = () => failure ? /* @__PURE__ */ jsx_runtime6.jsx(Failure, {
1887
+ f: failure
1888
+ }) : /* @__PURE__ */ jsx_runtime6.jsx("p", {
1889
+ className: "desktop-empty bv-muted",
1890
+ children: "…"
1891
+ });
1892
+ const out = {
1893
+ maker: ({ path, navigate, setTitle }) => /* @__PURE__ */ jsx_runtime6.jsx(Maker, {
1894
+ specs: [],
1895
+ title: d.title,
1896
+ path,
1897
+ navigate,
1898
+ setTitle
1899
+ }),
1900
+ state: () => /* @__PURE__ */ jsx_runtime6.jsxs("section", {
1901
+ className: "gui-app",
1902
+ children: [
1903
+ d.error && /* @__PURE__ */ jsx_runtime6.jsxs("p", {
1904
+ className: "bv-error",
1905
+ children: [
1906
+ "The code did not load; the last generation serves: ",
1907
+ d.error
1908
+ ]
1909
+ }),
1910
+ /* @__PURE__ */ jsx_runtime6.jsx(Inspector, {
1911
+ manifest
1912
+ })
1913
+ ]
1914
+ }),
1915
+ contract: () => /* @__PURE__ */ jsx_runtime6.jsx("section", {
1916
+ className: "gui-app",
1917
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Runner, {
1918
+ manifest
1919
+ })
1920
+ }),
1921
+ instructions: () => /* @__PURE__ */ jsx_runtime6.jsx("section", {
1922
+ className: "gui-app",
1923
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Instructions, {
1924
+ manifest
1925
+ })
1926
+ }),
1927
+ settings: Settings
1928
+ };
1929
+ for (const sc of manifest.apps ?? []) {
1930
+ if (sc.key in out)
1931
+ continue;
1932
+ out[sc.key] = sc.key === "view" && !view?.apps[sc.key] ? view?.View ? view.View : !d.view ? () => /* @__PURE__ */ jsx_runtime6.jsx("p", {
1933
+ className: "desktop-empty bv-muted",
1934
+ children: "This baba has no interface.tsx yet: its apps are voss's."
1935
+ }) : pending : pending;
1936
+ }
1937
+ for (const [k, v] of Object.entries(view?.apps ?? {}))
1938
+ out[k] = v;
1939
+ return out;
1940
+ }, [view, failure, system2, manifest, d.error, d.view, d.title]);
1941
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1942
+ className: "gui-view",
1943
+ children: /* @__PURE__ */ jsx_runtime6.jsx(ViewBoundary, {
1944
+ fallback: (f) => /* @__PURE__ */ jsx_runtime6.jsx("div", {
1945
+ className: "gui-page",
1946
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Failure, {
1947
+ f
1948
+ })
1949
+ }),
1950
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Desktop, {
1951
+ system: system2,
1952
+ apps: components,
1953
+ title: d.title,
1954
+ root: { label: "voss", href: "/" }
1955
+ })
1956
+ })
1957
+ }, view ? "loaded" : "loading");
1958
+ }
1959
+ function Settings() {
1960
+ const system2 = import_react6.useMemo(() => desktop({}), []);
1961
+ const current = useResource(system2.desktopSettings)?.theme ?? "system";
1962
+ const { fire } = useBaba();
1963
+ return /* @__PURE__ */ jsx_runtime6.jsxs("section", {
1964
+ className: "gui-app",
1965
+ children: [
1966
+ /* @__PURE__ */ jsx_runtime6.jsx("h2", {
1967
+ children: "Theme"
1968
+ }),
1969
+ /* @__PURE__ */ jsx_runtime6.jsx("p", {
1970
+ className: "bv-muted",
1971
+ children: "The theme every page of this baba shows, kept in its state."
1972
+ }),
1973
+ /* @__PURE__ */ jsx_runtime6.jsx("div", {
1974
+ className: "gui-choices",
1975
+ role: "radiogroup",
1976
+ "aria-label": "Theme",
1977
+ children: themes.map((t) => /* @__PURE__ */ jsx_runtime6.jsx(Button, {
1978
+ variant: t === current ? "default" : "quiet",
1979
+ role: "radio",
1980
+ "aria-checked": t === current,
1981
+ onClick: () => void fire("desktop-settings", { theme: t }),
1982
+ children: t
1983
+ }, t))
1984
+ }),
1985
+ /* @__PURE__ */ jsx_runtime6.jsx("h2", {
1986
+ children: "voss"
1987
+ }),
1988
+ /* @__PURE__ */ jsx_runtime6.jsx("p", {
1989
+ children: /* @__PURE__ */ jsx_runtime6.jsx("a", {
1990
+ href: "/",
1991
+ children: "All babas"
1992
+ })
1993
+ })
1994
+ ]
1995
+ });
1996
+ }
1997
+
1998
+ class ViewBoundary extends import_react6.Component {
1999
+ state = { failure: null };
2000
+ static getDerivedStateFromError(err) {
2001
+ return { failure: { stage: "render", message: err instanceof Error ? err.message : String(err), detail: err instanceof Error ? err.stack : undefined } };
2002
+ }
2003
+ render() {
2004
+ return this.state.failure ? this.props.fallback(this.state.failure) : this.props.children;
2005
+ }
2006
+ }
2007
+ import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsx_runtime6.jsxs(import_react6.StrictMode, {
2008
+ children: [
2009
+ scope ? /* @__PURE__ */ jsx_runtime6.jsx(ScopeProvider, {
2010
+ value: scope,
2011
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Host, {})
2012
+ }) : /* @__PURE__ */ jsx_runtime6.jsx(Root, {}),
2013
+ /* @__PURE__ */ jsx_runtime6.jsx(Stale, {})
2014
+ ]
2015
+ }));