babavoss 0.12.6 → 0.13.1

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 (62) hide show
  1. package/gui/babavoss-web.js +4 -1
  2. package/gui/{chunk-9gmmm54v.js → chunk-qgzmajan.js} +619 -574
  3. package/gui/gui.js +1538 -277
  4. package/gui/theme.css +2007 -1128
  5. package/index.ts +1 -1
  6. package/package.json +5 -5
  7. package/src/{promptware → agents}/compile.ts +23 -31
  8. package/src/{promptware → agents}/define.ts +2 -2
  9. package/src/{promptware → agents}/disk.ts +7 -4
  10. package/src/{promptware → agents}/sync.ts +2 -2
  11. package/src/{promptware → agents}/system.ts +17 -17
  12. package/src/baba/check.ts +3 -3
  13. package/src/baba/config.ts +5 -2
  14. package/src/baba/init.ts +13 -18
  15. package/src/baba/node.ts +2 -2
  16. package/src/baba/project.ts +2 -2
  17. package/src/baba/requirements.ts +39 -0
  18. package/src/baba/setup.ts +30 -0
  19. package/src/baba/worker.ts +13 -5
  20. package/src/build/mdx.ts +4 -4
  21. package/src/build/project.ts +6 -1
  22. package/src/build/views.ts +3 -3
  23. package/src/desktop/desktop.css +35 -39
  24. package/src/desktop/index.ts +9 -7
  25. package/src/desktop/keys.ts +153 -0
  26. package/src/desktop/view.tsx +174 -130
  27. package/src/door/core.ts +2 -2
  28. package/src/ecs/baba.ts +27 -17
  29. package/src/generated/build.ts +1 -1
  30. package/src/gui/gui.tsx +21 -31
  31. package/src/gui/index.ts +6 -2
  32. package/src/gui/{promptware.tsx → instructions.tsx} +12 -12
  33. package/src/gui/levels.tsx +241 -59
  34. package/src/gui/lockup.ts +11 -0
  35. package/src/gui/settings.tsx +241 -0
  36. package/src/gui/setup.tsx +77 -0
  37. package/src/gui/theme.css +205 -58
  38. package/src/gui/theme.ts +9 -5
  39. package/src/gui/voss-settings.ts +48 -0
  40. package/src/gui/wizard.tsx +17 -40
  41. package/src/guide/add-a-desktop.mdx +7 -7
  42. package/src/guide/index.ts +4 -4
  43. package/src/guide/write-a-system.mdx +3 -3
  44. package/src/guide/{write-promptware.mdx → write-agentic-instructions.mdx} +17 -24
  45. package/src/http/server.ts +140 -17
  46. package/src/{prompt → instructions}/evals.ts +1 -1
  47. package/src/{prompt → instructions}/index.ts +9 -8
  48. package/src/{prompt → instructions}/jsx-runtime.ts +3 -3
  49. package/src/mcp/main.ts +3 -3
  50. package/src/mcp/tools.ts +4 -4
  51. package/src/runtime/harness.ts +2 -2
  52. package/src/server/edge.ts +31 -3
  53. package/src/server/main.ts +2 -1
  54. package/src/server/messages.ts +47 -7
  55. package/src/shell/run.ts +18 -18
  56. package/src/spec/index.ts +1 -1
  57. package/src/web/core.tsx +2 -2
  58. package/src/web/index.tsx +1 -1
  59. package/src/desktop/bob.ts +0 -76
  60. /package/src/{promptware → agents}/markdown.d.ts +0 -0
  61. /package/src/{prompt → instructions}/jsx-dev-runtime.ts +0 -0
  62. /package/src/{prompt → instructions}/mdx.d.ts +0 -0
package/gui/gui.js CHANGED
@@ -6,7 +6,8 @@ import {
6
6
  ApiError,
7
7
  Button,
8
8
  Desktop,
9
- DesktopFrame,
9
+ DesktopSkeleton,
10
+ Eyes,
10
11
  FieldError,
11
12
  Input,
12
13
  Maker,
@@ -19,23 +20,28 @@ import {
19
20
  accept,
20
21
  api,
21
22
  apiHeaders,
23
+ bindingNote,
24
+ bindingOf,
22
25
  canonicalPath,
23
26
  collect,
24
27
  describeType,
25
28
  effect,
29
+ formatBinding,
26
30
  formatFailure,
27
31
  isApps,
32
+ isMac,
33
+ keyCommands,
34
+ keymapOf,
28
35
  matchRoute,
36
+ normalBinding,
29
37
  plan,
30
38
  resource,
31
39
  s,
32
40
  system,
33
41
  tableOf,
34
- useBaba,
35
42
  useBabaState,
36
- useResource,
37
43
  useScope
38
- } from "./chunk-9gmmm54v.js";
44
+ } from "./chunk-qgzmajan.js";
39
45
  import {
40
46
  require_jsx_runtime
41
47
  } from "./chunk-pk09y98y.js";
@@ -47,7 +53,7 @@ import {
47
53
  } from "./chunk-wwqypxre.js";
48
54
 
49
55
  // src/gui/gui.tsx
50
- var import_react6 = __toESM(require_react(), 1);
56
+ var import_react9 = __toESM(require_react(), 1);
51
57
  var import_client = __toESM(require_client(), 1);
52
58
 
53
59
  // src/desktop/icons.ts
@@ -386,7 +392,7 @@ function desktop(options) {
386
392
  }
387
393
  }),
388
394
  p.action("desktop-settings", {
389
- summary: "set the baba's theme, the same on every page",
395
+ summary: "set the baba's own theme, as its frames in the Maker show it; voss's pages follow the theme in voss's settings",
390
396
  args: { ...sessionArg, theme: s.optional(s.enum(...themes)) },
391
397
  run: (w, a) => {
392
398
  sync(w);
@@ -825,21 +831,21 @@ function Output({ value }) {
825
831
  });
826
832
  }
827
833
 
828
- // src/gui/promptware.tsx
834
+ // src/gui/instructions.tsx
829
835
  var import_react3 = __toESM(require_react(), 1);
830
836
  var jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
831
- function Promptware({ manifest }) {
837
+ function Instructions({ manifest }) {
832
838
  const { fire, read, ready, round } = useBabaState();
833
839
  const [plan2, setPlan] = import_react3.useState(null);
834
840
  const [error, setError] = import_react3.useState(null);
835
841
  import_react3.useEffect(() => {
836
842
  if (ready)
837
- read("promptware-plan").then((p) => setPlan(p), setError);
843
+ read("agents-plan").then((p) => setPlan(p), setError);
838
844
  }, [ready, round]);
839
845
  if (!("pwStatus" in manifest.resources))
840
846
  return /* @__PURE__ */ jsx_runtime3.jsx("p", {
841
847
  className: "bv-muted",
842
- children: "This baba has no promptware system."
848
+ children: "This baba has no agents system."
843
849
  });
844
850
  if (!ready)
845
851
  return /* @__PURE__ */ jsx_runtime3.jsx("p", {
@@ -872,7 +878,7 @@ function Promptware({ manifest }) {
872
878
  children: [
873
879
  /* @__PURE__ */ jsx_runtime3.jsx(Button, {
874
880
  variant: "primary",
875
- onClick: () => void act("promptware-sync", {}).catch(() => {}),
881
+ onClick: () => void act("agents-sync", {}).catch(() => {}),
876
882
  children: "Sync now"
877
883
  }),
878
884
  plan2 && /* @__PURE__ */ jsx_runtime3.jsxs("span", {
@@ -903,17 +909,17 @@ function Promptware({ manifest }) {
903
909
  });
904
910
  }
905
911
  function Declared({ manifest }) {
906
- const pw = manifest.promptware;
912
+ const pw = manifest.instructions;
907
913
  const skills = Object.entries(pw.skills);
908
914
  return /* @__PURE__ */ jsx_runtime3.jsxs("section", {
909
915
  className: "gui-section",
910
916
  children: [
911
917
  /* @__PURE__ */ jsx_runtime3.jsx("h2", {
912
- children: "Promptware"
918
+ children: "Instructions"
913
919
  }),
914
920
  !pw.context && skills.length === 0 && pw.hooks.length === 0 && /* @__PURE__ */ jsx_runtime3.jsx("p", {
915
921
  className: "bv-muted",
916
- children: "Nothing declared yet: give the baba a `context(...)`, a `skill(...)` or a `hook(...)` under `promptware`."
922
+ children: "Nothing declared yet: give the baba a `context(...)`, a `skill(...)` or a `hook(...)` under `.baba/agents/`."
917
923
  }),
918
924
  pw.context && /* @__PURE__ */ jsx_runtime3.jsxs("p", {
919
925
  className: "bv-muted",
@@ -944,7 +950,7 @@ function Declared({ manifest }) {
944
950
  }),
945
951
  /* @__PURE__ */ jsx_runtime3.jsx("p", {
946
952
  className: "bv-muted",
947
- children: "The promptware is the baba's code: edit it under .baba/promptware/; the files below follow."
953
+ children: "The agentic instructions is the baba's code: edit it under .baba/agents/; the files below follow."
948
954
  })
949
955
  ]
950
956
  });
@@ -982,11 +988,11 @@ function Harness({ manifest }) {
982
988
  /* @__PURE__ */ jsx_runtime3.jsxs("p", {
983
989
  className: "bv-muted",
984
990
  children: [
985
- "Declared in .baba/index.ts: ",
991
+ "The .mdx files under .baba/agents/, each ",
986
992
  /* @__PURE__ */ jsx_runtime3.jsx("code", {
987
- children: "baba({ systems, promptware, harness })"
993
+ children: 'export const prompt = { kind: "context" | "skill", … }'
988
994
  }),
989
- "."
995
+ "; voss reads them on its own."
990
996
  ]
991
997
  })
992
998
  ]
@@ -994,19 +1000,7 @@ function Harness({ manifest }) {
994
1000
  }
995
1001
 
996
1002
  // 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
- }
1003
+ var import_react7 = __toESM(require_react(), 1);
1010
1004
 
1011
1005
  // src/gui/wizard.tsx
1012
1006
  var import_react4 = __toESM(require_react(), 1);
@@ -1039,7 +1033,6 @@ var short = (path, home, max = 52) => {
1039
1033
  }
1040
1034
  return t;
1041
1035
  };
1042
- var slug = (s2) => s2.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
1043
1036
  var isTyping = (e) => e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement;
1044
1037
  var Svg = ({ children, size = 16 }) => /* @__PURE__ */ jsx_runtime4.jsx("svg", {
1045
1038
  width: size,
@@ -1115,15 +1108,12 @@ function useInView(list, active) {
1115
1108
  list.current?.querySelector(`[data-i="${active}"]`)?.scrollIntoView({ block: "nearest" });
1116
1109
  }, [list, active]);
1117
1110
  }
1118
- function Folders({ home, start, onBack, onOpened, onVerb }) {
1111
+ function Folders({ home, start, onBack, pick }) {
1119
1112
  const [at, setAt] = import_react4.useState(null);
1120
1113
  const [loading, setLoading] = import_react4.useState(true);
1121
1114
  const [error, setError] = import_react4.useState(null);
1122
- const [busy, setBusy] = import_react4.useState(null);
1123
1115
  const [filter, setFilter] = import_react4.useState("");
1124
1116
  const [active, setActive] = import_react4.useState(0);
1125
- const [name, setName] = import_react4.useState("");
1126
- const [title, setTitle] = import_react4.useState("");
1127
1117
  const list = import_react4.useRef(null);
1128
1118
  const filterRef = import_react4.useRef(null);
1129
1119
  const homeDir = at?.home || home;
@@ -1135,9 +1125,6 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1135
1125
  setAt(b);
1136
1126
  setError(null);
1137
1127
  setFilter("");
1138
- setName("");
1139
- setTitle("");
1140
- onVerb?.(b.known ? "open" : b.baba ? "remember" : "make");
1141
1128
  if (!(document.activeElement instanceof HTMLInputElement) || document.activeElement === filterRef.current)
1142
1129
  filterRef.current?.focus();
1143
1130
  setActive(back >= 0 ? back + (b.parent ? 1 : 0) : 0);
@@ -1155,26 +1142,9 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1155
1142
  ];
1156
1143
  const cur = Math.min(active, Math.max(0, rows.length - 1));
1157
1144
  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
- }
1145
+ const act = () => {
1146
+ if (at && !at.known)
1147
+ pick(at.path);
1178
1148
  };
1179
1149
  const enter = (i) => {
1180
1150
  const r = rows[i];
@@ -1183,7 +1153,7 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1183
1153
  };
1184
1154
  import_react4.useEffect(() => {
1185
1155
  const key = (e) => {
1186
- if (busy || document.querySelector(".desktop-launcher"))
1156
+ if (document.querySelector(".desktop-launcher"))
1187
1157
  return;
1188
1158
  const inField = isTyping(e) && e.target !== filterRef.current;
1189
1159
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -1236,7 +1206,6 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1236
1206
  }, [at, homeDir]);
1237
1207
  const shown = crumbs.length > 5 ? [crumbs[0], { label: "…", path: crumbs[crumbs.length - 4].path }, ...crumbs.slice(-3)] : crumbs;
1238
1208
  const here = at ? short(at.path, homeDir, 44) : "";
1239
- const leaf = at ? at.path.split("/").filter(Boolean).pop() ?? "" : "";
1240
1209
  return /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1241
1210
  className: "pk-pane pk-fade",
1242
1211
  children: [
@@ -1245,7 +1214,7 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1245
1214
  children: [
1246
1215
  /* @__PURE__ */ jsx_runtime4.jsx("span", {
1247
1216
  className: "pk-strip-title",
1248
- children: "New baba"
1217
+ children: "Choose a folder"
1249
1218
  }),
1250
1219
  /* @__PURE__ */ jsx_runtime4.jsx("span", {
1251
1220
  className: "pk-gap"
@@ -1437,21 +1406,17 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1437
1406
  title: at?.path,
1438
1407
  children: here
1439
1408
  }),
1440
- ". Remember it to open it here."
1409
+ ". Add it to the list."
1441
1410
  ]
1442
1411
  }) : /* @__PURE__ */ jsx_runtime4.jsxs(jsx_runtime4.Fragment, {
1443
1412
  children: [
1444
- "A new baba goes in ",
1413
+ "There is no baba in ",
1445
1414
  /* @__PURE__ */ jsx_runtime4.jsx("span", {
1446
1415
  className: "pk-here",
1447
1416
  title: at?.path,
1448
1417
  children: here
1449
1418
  }),
1450
- ", as ",
1451
- /* @__PURE__ */ jsx_runtime4.jsx("code", {
1452
- children: ".baba/"
1453
- }),
1454
- " beside what is there."
1419
+ " yet: added, it is set up first."
1455
1420
  ]
1456
1421
  })
1457
1422
  })
@@ -1460,68 +1425,16 @@ function Folders({ home, start, onBack, onOpened, onVerb }) {
1460
1425
  /* @__PURE__ */ jsx_runtime4.jsxs("div", {
1461
1426
  className: "pk-dock-line",
1462
1427
  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
1428
  /* @__PURE__ */ jsx_runtime4.jsx("span", {
1516
1429
  className: "pk-gap"
1517
1430
  }),
1518
1431
  /* @__PURE__ */ jsx_runtime4.jsxs("button", {
1519
1432
  type: "button",
1520
1433
  className: "pk-primary",
1521
- disabled: !at || !!busy || loading,
1522
- onClick: () => void act(),
1434
+ disabled: !at || loading || at.known,
1435
+ onClick: act,
1523
1436
  children: [
1524
- !at ? "Make a baba here" : at.known ? "Open it" : at.baba ? "Remember this baba" : "Make a baba here",
1437
+ at?.known ? "Already in the list" : "Add this folder",
1525
1438
  /* @__PURE__ */ jsx_runtime4.jsx("kbd", {
1526
1439
  className: "pk-kbd is-on-primary",
1527
1440
  children: "⌘↵"
@@ -1567,76 +1480,1448 @@ function Marked({ text, words }) {
1567
1480
  });
1568
1481
  }
1569
1482
 
1570
- // src/gui/levels.tsx
1483
+ // src/gui/setup.tsx
1484
+ var import_react5 = __toESM(require_react(), 1);
1485
+
1486
+ // src/baba/requirements.ts
1487
+ var PAGE_WORDS = new Set(["api", "assets", "view", "ws"]);
1488
+ var requirements = [
1489
+ {
1490
+ key: "name",
1491
+ title: "Name",
1492
+ summary: "The baba's address and its name at the shell: voss serves it at /NAME/, and the CLI reaches it as voss NAME from anywhere."
1493
+ }
1494
+ ];
1495
+ var NAME = /^[a-z][a-z0-9-]*$/;
1496
+ function requirementError(key, value, taken = []) {
1497
+ if (key === "name") {
1498
+ if (!value)
1499
+ return "A baba needs a name.";
1500
+ if (!NAME.test(value))
1501
+ return "Lowercase letters, digits and dashes, starting with a letter.";
1502
+ if (PAGE_WORDS.has(value))
1503
+ return `/${value}/ is the page's own address; choose another.`;
1504
+ if (taken.includes(value))
1505
+ return `Another baba is already called ${value}.`;
1506
+ }
1507
+ return null;
1508
+ }
1509
+ var nameFrom = (dir) => (dir.split("/").filter(Boolean).pop() ?? "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
1510
+
1511
+ // src/gui/setup.tsx
1571
1512
  var jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
1572
- function useProjects() {
1573
- const [state, setState] = import_react5.useState({ projects: null, home: "" });
1513
+ function SetupWizard({ entry, home, taken, onCancel, onDone }) {
1514
+ const asks = requirements.filter((r) => entry.missing.includes(r.key));
1515
+ const steps = [...asks.map((r) => ({ kind: "ask", r })), { kind: "done" }];
1516
+ const [at, setAt] = import_react5.useState(0);
1517
+ const [values, setValues] = import_react5.useState(() => ({ name: nameFrom(entry.dir) }));
1518
+ const [busy, setBusy] = import_react5.useState(false);
1519
+ const [error, setError] = import_react5.useState(null);
1520
+ const field = import_react5.useRef(null), primary = import_react5.useRef(null);
1521
+ const step = steps[at];
1522
+ const why = (k) => requirementError(k, values[k] ?? "", taken);
1523
+ const blocked = step.kind === "ask" && why(step.r.key) !== null;
1574
1524
  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: "" });
1525
+ if (field.current) {
1526
+ field.current.focus();
1527
+ field.current.select();
1528
+ } else
1529
+ primary.current?.focus();
1530
+ }, [at]);
1531
+ const next = async () => {
1532
+ if (busy || blocked)
1533
+ return;
1534
+ if (step.kind === "ask") {
1535
+ setAt(at + 1);
1536
+ return;
1537
+ }
1538
+ setBusy(true);
1539
+ setError(null);
1540
+ try {
1541
+ const j = await postJson("/api/projects/setup", { dir: entry.dir, values });
1542
+ if (j.name)
1543
+ onDone(j.name);
1544
+ else
1545
+ throw new Error("voss kept it to set up: it still lacks something");
1546
+ } catch (e) {
1547
+ setError(String(e instanceof Error ? e.message : e));
1548
+ setBusy(false);
1549
+ }
1550
+ };
1551
+ const back = () => {
1552
+ if (busy)
1553
+ return;
1554
+ if (at > 0)
1555
+ setAt(at - 1);
1556
+ else
1557
+ onCancel();
1558
+ };
1559
+ import_react5.useEffect(() => {
1560
+ const key = (e) => {
1561
+ if (e.key === "Escape") {
1562
+ e.preventDefault();
1563
+ back();
1564
+ }
1565
+ };
1566
+ addEventListener("keydown", key);
1567
+ return () => removeEventListener("keydown", key);
1568
+ });
1569
+ const where = tilde(entry.dir, home);
1570
+ return /* @__PURE__ */ jsx_runtime5.jsxs("form", {
1571
+ className: "sw-card",
1572
+ "aria-label": "Set up a baba",
1573
+ onSubmit: (e) => {
1574
+ e.preventDefault();
1575
+ next();
1576
+ },
1577
+ children: [
1578
+ /* @__PURE__ */ jsx_runtime5.jsxs("header", {
1579
+ className: "sw-head",
1580
+ children: [
1581
+ /* @__PURE__ */ jsx_runtime5.jsx(Eyes, {
1582
+ size: 28
1583
+ }),
1584
+ /* @__PURE__ */ jsx_runtime5.jsxs("div", {
1585
+ className: "sw-head-text",
1586
+ children: [
1587
+ /* @__PURE__ */ jsx_runtime5.jsx("h1", {
1588
+ children: "Set up a baba"
1589
+ }),
1590
+ /* @__PURE__ */ jsx_runtime5.jsx("p", {
1591
+ title: entry.dir,
1592
+ children: where
1593
+ })
1594
+ ]
1595
+ })
1596
+ ]
1597
+ }),
1598
+ /* @__PURE__ */ jsx_runtime5.jsx("ol", {
1599
+ className: "sw-steps",
1600
+ "aria-label": "Steps",
1601
+ children: steps.map((s2, i) => /* @__PURE__ */ jsx_runtime5.jsxs("li", {
1602
+ className: i === at ? "is-here" : i < at ? "is-done" : undefined,
1603
+ "aria-current": i === at ? "step" : undefined,
1604
+ children: [
1605
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
1606
+ className: "sw-step-dot",
1607
+ children: i + 1
1608
+ }),
1609
+ s2.kind === "ask" ? s2.r.title : entry.baba ? "Save" : "Create"
1610
+ ]
1611
+ }, i))
1612
+ }),
1613
+ /* @__PURE__ */ jsx_runtime5.jsxs("div", {
1614
+ className: "sw-body",
1615
+ children: [
1616
+ step.kind === "ask" ? /* @__PURE__ */ jsx_runtime5.jsxs(jsx_runtime5.Fragment, {
1617
+ children: [
1618
+ /* @__PURE__ */ jsx_runtime5.jsx("label", {
1619
+ className: "sw-label",
1620
+ htmlFor: "sw-field",
1621
+ children: step.r.title
1622
+ }),
1623
+ /* @__PURE__ */ jsx_runtime5.jsx("p", {
1624
+ className: "sw-summary",
1625
+ children: step.r.summary
1626
+ }),
1627
+ /* @__PURE__ */ jsx_runtime5.jsx("input", {
1628
+ id: "sw-field",
1629
+ ref: field,
1630
+ className: `sw-input${why(step.r.key) ? " is-wrong" : ""}`,
1631
+ value: values[step.r.key] ?? "",
1632
+ spellCheck: false,
1633
+ autoComplete: "off",
1634
+ disabled: busy,
1635
+ onChange: (e) => setValues({ ...values, [step.r.key]: e.target.value.toLowerCase() })
1636
+ }),
1637
+ /* @__PURE__ */ jsx_runtime5.jsx("p", {
1638
+ className: `sw-hint${why(step.r.key) ? " is-wrong" : ""}`,
1639
+ children: why(step.r.key) ?? /* @__PURE__ */ jsx_runtime5.jsxs(jsx_runtime5.Fragment, {
1640
+ children: [
1641
+ "It opens at ",
1642
+ /* @__PURE__ */ jsx_runtime5.jsxs("code", {
1643
+ children: [
1644
+ "/",
1645
+ values[step.r.key],
1646
+ "/"
1647
+ ]
1648
+ }),
1649
+ "."
1650
+ ]
1651
+ })
1652
+ })
1653
+ ]
1654
+ }) : /* @__PURE__ */ jsx_runtime5.jsxs(jsx_runtime5.Fragment, {
1655
+ children: [
1656
+ /* @__PURE__ */ jsx_runtime5.jsx("p", {
1657
+ className: "sw-summary",
1658
+ children: entry.baba ? /* @__PURE__ */ jsx_runtime5.jsxs(jsx_runtime5.Fragment, {
1659
+ children: [
1660
+ "voss writes what it needs into ",
1661
+ /* @__PURE__ */ jsx_runtime5.jsx("code", {
1662
+ children: ".baba/package.json"
1663
+ }),
1664
+ " and leaves the rest of it as it is."
1665
+ ]
1666
+ }) : /* @__PURE__ */ jsx_runtime5.jsxs(jsx_runtime5.Fragment, {
1667
+ children: [
1668
+ "voss makes a baba here: ",
1669
+ /* @__PURE__ */ jsx_runtime5.jsx("code", {
1670
+ children: ".baba/"
1671
+ }),
1672
+ " beside what the folder holds, with a first system and its spec, then installs its packages. It takes a moment."
1673
+ ]
1674
+ })
1675
+ }),
1676
+ /* @__PURE__ */ jsx_runtime5.jsxs("dl", {
1677
+ className: "sw-review",
1678
+ children: [
1679
+ asks.map((r) => /* @__PURE__ */ jsx_runtime5.jsxs("div", {
1680
+ children: [
1681
+ /* @__PURE__ */ jsx_runtime5.jsx("dt", {
1682
+ children: r.title
1683
+ }),
1684
+ /* @__PURE__ */ jsx_runtime5.jsx("dd", {
1685
+ children: /* @__PURE__ */ jsx_runtime5.jsx("code", {
1686
+ children: values[r.key]
1687
+ })
1688
+ })
1689
+ ]
1690
+ }, r.key)),
1691
+ /* @__PURE__ */ jsx_runtime5.jsxs("div", {
1692
+ children: [
1693
+ /* @__PURE__ */ jsx_runtime5.jsx("dt", {
1694
+ children: "Folder"
1695
+ }),
1696
+ /* @__PURE__ */ jsx_runtime5.jsx("dd", {
1697
+ children: /* @__PURE__ */ jsx_runtime5.jsx("code", {
1698
+ title: entry.dir,
1699
+ children: where
1700
+ })
1701
+ })
1702
+ ]
1703
+ })
1704
+ ]
1705
+ })
1706
+ ]
1707
+ }),
1708
+ error && /* @__PURE__ */ jsx_runtime5.jsx("p", {
1709
+ className: "sw-error",
1710
+ role: "alert",
1711
+ children: error
1712
+ })
1713
+ ]
1714
+ }),
1715
+ /* @__PURE__ */ jsx_runtime5.jsxs("footer", {
1716
+ className: "sw-foot",
1717
+ children: [
1718
+ /* @__PURE__ */ jsx_runtime5.jsx("button", {
1719
+ type: "button",
1720
+ className: "sw-button is-quiet",
1721
+ disabled: busy,
1722
+ onClick: back,
1723
+ children: at > 0 ? "Back" : "Cancel"
1724
+ }),
1725
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
1726
+ className: "sw-gap"
1727
+ }),
1728
+ busy && /* @__PURE__ */ jsx_runtime5.jsxs("span", {
1729
+ className: "sw-busy",
1730
+ children: [
1731
+ /* @__PURE__ */ jsx_runtime5.jsx("span", {
1732
+ className: "pk-spin",
1733
+ "aria-hidden": "true"
1734
+ }),
1735
+ entry.baba ? "Saving…" : "Installing packages…"
1736
+ ]
1737
+ }),
1738
+ /* @__PURE__ */ jsx_runtime5.jsx("button", {
1739
+ ref: primary,
1740
+ type: "submit",
1741
+ className: "sw-button is-primary",
1742
+ disabled: busy || blocked,
1743
+ children: step.kind === "ask" ? "Continue" : entry.baba ? "Save and open" : "Create and open"
1744
+ })
1745
+ ]
1746
+ })
1747
+ ]
1748
+ });
1749
+ }
1750
+
1751
+ // src/gui/voss-settings.ts
1752
+ var import_react6 = __toESM(require_react(), 1);
1753
+
1754
+ // src/gui/theme.ts
1755
+ var themeKey = "babavoss.theme";
1756
+ var early = `try{var t=localStorage.getItem(${JSON.stringify(themeKey)});if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}`;
1757
+ var deskColors = { light: "#ECE4D8", dark: "#151312" };
1758
+ var firstPaint = `html{background:${deskColors.light};color-scheme:light dark}@media (prefers-color-scheme:dark){html:not([data-theme="light"]){background:${deskColors.dark}}}html[data-theme="dark"]{background:${deskColors.dark}}`;
1759
+ function setTheme(next) {
1760
+ try {
1761
+ if (next === "system")
1762
+ localStorage.removeItem(themeKey);
1763
+ else
1764
+ localStorage.setItem(themeKey, next);
1765
+ } catch {}
1766
+ if (next === "system")
1767
+ delete document.documentElement.dataset.theme;
1768
+ else
1769
+ document.documentElement.dataset.theme = next;
1770
+ }
1771
+
1772
+ // src/gui/voss-settings.ts
1773
+ var current = null;
1774
+ var saves = 0;
1775
+ var listeners = new Set;
1776
+ var publish = (s2) => {
1777
+ current = s2;
1778
+ setTheme(s2.theme);
1779
+ for (const l of listeners)
1780
+ l();
1781
+ };
1782
+ async function fetchSettings() {
1783
+ const at = saves;
1784
+ try {
1785
+ const r = await fetch("/api/settings", { headers: apiHeaders() });
1786
+ if (r.ok) {
1787
+ const s2 = await r.json();
1788
+ if (at === saves)
1789
+ publish(s2);
1790
+ }
1791
+ } catch {}
1792
+ }
1793
+ async function saveSettings(change) {
1794
+ saves++;
1795
+ const r = await fetch("/api/settings", { method: "POST", headers: { "content-type": "application/json", ...apiHeaders() }, body: JSON.stringify(change) });
1796
+ const j = await r.json();
1797
+ if (!r.ok || j.error)
1798
+ throw new Error(j.error?.message ?? r.statusText);
1799
+ publish(j);
1800
+ return j;
1801
+ }
1802
+ var watching = 0;
1803
+ function useVossSettings() {
1804
+ import_react6.useEffect(() => {
1805
+ if (watching++ === 0) {
1806
+ fetchSettings();
1807
+ addEventListener("focus", onFront);
1808
+ document.addEventListener("visibilitychange", onFront);
1809
+ }
1810
+ return () => {
1811
+ if (--watching === 0) {
1812
+ removeEventListener("focus", onFront);
1813
+ document.removeEventListener("visibilitychange", onFront);
1814
+ }
1815
+ };
1816
+ }, []);
1817
+ return import_react6.useSyncExternalStore((l) => {
1818
+ listeners.add(l);
1819
+ return () => listeners.delete(l);
1820
+ }, () => current);
1821
+ }
1822
+ var onFront = () => {
1823
+ if (document.visibilityState === "visible")
1824
+ fetchSettings();
1825
+ };
1826
+
1827
+ // src/gui/lockup.ts
1828
+ var lockup = {
1829
+ viewBox: "0 0 745 160",
1830
+ tile: { x: 50, y: 30, width: 100, height: 100, rx: 50 },
1831
+ symbol: { transform: "translate(50.00 30.00) scale(1.0000)", eyes: [{ cx: 39.772, cy: 50, r: 18 }, { cx: 77.5, cy: 50, r: 12.728 }] },
1832
+ word: { transform: "translate(195.00 114.90)", d: "M8 0L8-69.80L32.30-69.80Q42.20-69.80 47.55-64.85Q52.90-59.90 52.90-51.40Q52.90-45.40 49.95-41.80Q47-38.20 41.60-36.60L41.60-36.10Q47.90-34.60 51.55-30.65Q55.20-26.70 55.20-19.50Q55.20-15.10 53.75-11.55Q52.30-8 49.65-5.40Q47-2.80 43.25-1.40Q39.50 0 34.80 0L8 0M20.80-9.50L30.80-9.50Q36.30-9.50 39.05-11.55Q41.80-13.60 41.80-18.60L41.80-21.90Q41.80-26.80 39.05-28.85Q36.30-30.90 30.80-30.90L20.80-30.90L20.80-9.50M20.80-40L29.10-40Q34.40-40 36.95-41.90Q39.50-43.80 39.50-48.50L39.50-51.80Q39.50-56.50 36.95-58.40Q34.40-60.30 29.10-60.30L20.80-60.30 M116.50 0L109 0Q104.30 0 101.75-2.35Q99.20-4.70 98.70-8.90L98.20-8.90Q96.80-4.10 92.70-1.45Q88.60 1.20 82.60 1.20Q74.80 1.20 70.20-2.90Q65.60-7 65.60-14.30Q65.60-29.90 88.50-29.90L97.60-29.90L97.60-33.30Q97.60-38.20 95.20-40.70Q92.80-43.20 87.40-43.20Q82.50-43.20 79.50-41.30Q76.50-39.40 74.40-36.40L67.10-42.60Q69.50-46.90 74.85-49.85Q80.20-52.80 88.70-52.80Q98.90-52.80 104.65-48.05Q110.40-43.30 110.40-33.90L110.40-9.60L116.50-9.60L116.50 0M86.90-7.60Q91.50-7.60 94.55-9.75Q97.60-11.90 97.60-15.60L97.60-22.50L88.80-22.50Q78.30-22.50 78.30-15.90L78.30-13.90Q78.30-10.80 80.60-9.20Q82.90-7.60 86.90-7.60 M126.90 0L126.90-74L139.70-74L139.70-42.50L140.40-42.50Q142.50-47.20 146.15-50Q149.80-52.80 155.80-52.80Q160.20-52.80 163.85-51.15Q167.50-49.50 170.10-46.15Q172.70-42.80 174.10-37.70Q175.50-32.60 175.50-25.80Q175.50-19 174.10-13.90Q172.70-8.80 170.10-5.45Q167.50-2.10 163.85-0.45Q160.20 1.20 155.80 1.20Q149.80 1.20 146.15-1.60Q142.50-4.40 140.40-9.10L139.70-9.10L139.70 0L126.90 0M150.40-9Q156-9 159.05-12.40Q162.10-15.80 162.10-21.70L162.10-29.90Q162.10-35.80 159.05-39.20Q156-42.60 150.40-42.60Q146-42.60 142.85-40.45Q139.70-38.30 139.70-33.90L139.70-17.70Q139.70-13.30 142.85-11.15Q146-9 150.40-9 M236.50 0L229 0Q224.30 0 221.75-2.35Q219.20-4.70 218.70-8.90L218.20-8.90Q216.80-4.10 212.70-1.45Q208.60 1.20 202.60 1.20Q194.80 1.20 190.20-2.90Q185.60-7 185.60-14.30Q185.60-29.90 208.50-29.90L217.60-29.90L217.60-33.30Q217.60-38.20 215.20-40.70Q212.80-43.20 207.40-43.20Q202.50-43.20 199.50-41.30Q196.50-39.40 194.40-36.40L187.10-42.60Q189.50-46.90 194.85-49.85Q200.20-52.80 208.70-52.80Q218.90-52.80 224.65-48.05Q230.40-43.30 230.40-33.90L230.40-9.60L236.50-9.60L236.50 0M206.90-7.60Q211.50-7.60 214.55-9.75Q217.60-11.90 217.60-15.60L217.60-22.50L208.80-22.50Q198.30-22.50 198.30-15.90L198.30-13.90Q198.30-10.80 200.60-9.20Q202.90-7.60 206.90-7.60 M299.20 0L280.80 0L261.70-69.80L276.10-69.80L284.80-34.50L289.90-12.50L290.60-12.50L295.80-34.50L304.60-69.80L318.30-69.80 M350 1.20Q344.10 1.20 339.45-0.70Q334.80-2.60 331.55-6.10Q328.30-9.60 326.55-14.60Q324.80-19.60 324.80-25.80Q324.80-32 326.55-37Q328.30-42 331.55-45.50Q334.80-49 339.45-50.90Q344.10-52.80 350-52.80Q355.80-52.80 360.50-50.90Q365.20-49 368.45-45.50Q371.70-42 373.45-37Q375.20-32 375.20-25.80Q375.20-19.60 373.45-14.60Q371.70-9.60 368.45-6.10Q365.20-2.60 360.50-0.70Q355.80 1.20 350 1.20M350-8.60Q355.50-8.60 358.70-11.95Q361.90-15.30 361.90-21.80L361.90-29.80Q361.90-36.30 358.70-39.65Q355.50-43 350-43Q344.50-43 341.30-39.65Q338.10-36.30 338.10-29.80L338.10-21.80Q338.10-15.30 341.30-11.95Q344.50-8.60 350-8.60 M409.80 1.20Q401.30 1.20 395.10-1.45Q388.90-4.10 385.40-8.60L392.90-15.40Q396.10-11.90 400.25-10Q404.40-8.10 409.90-8.10Q414.70-8.10 417.60-9.70Q420.50-11.30 420.50-14.60Q420.50-17.40 418.55-18.40Q416.60-19.40 413.40-19.90L405.10-21.20Q401.90-21.70 398.85-22.65Q395.80-23.60 393.45-25.30Q391.10-27 389.70-29.65Q388.30-32.30 388.30-36.10Q388.30-44.20 394.35-48.50Q400.40-52.80 411-52.80Q418.50-52.80 423.75-50.70Q429-48.60 432.40-44.80L425.70-37.40Q423.50-39.80 419.85-41.65Q416.20-43.50 410.60-43.50Q400.80-43.50 400.80-37.40Q400.80-34.60 402.80-33.55Q404.80-32.50 408-32L416.20-30.70Q419.50-30.20 422.55-29.25Q425.60-28.30 427.95-26.60Q430.30-24.90 431.70-22.30Q433.10-19.70 433.10-15.90Q433.10-7.90 426.95-3.35Q420.80 1.20 409.80 1.20 M469.80 1.20Q461.30 1.20 455.10-1.45Q448.90-4.10 445.40-8.60L452.90-15.40Q456.10-11.90 460.25-10Q464.40-8.10 469.90-8.10Q474.70-8.10 477.60-9.70Q480.50-11.30 480.50-14.60Q480.50-17.40 478.55-18.40Q476.60-19.40 473.40-19.90L465.10-21.20Q461.90-21.70 458.85-22.65Q455.80-23.60 453.45-25.30Q451.10-27 449.70-29.65Q448.30-32.30 448.30-36.10Q448.30-44.20 454.35-48.50Q460.40-52.80 471-52.80Q478.50-52.80 483.75-50.70Q489-48.60 492.40-44.80L485.70-37.40Q483.50-39.80 479.85-41.65Q476.20-43.50 470.60-43.50Q460.80-43.50 460.80-37.40Q460.80-34.60 462.80-33.55Q464.80-32.50 468-32L476.20-30.70Q479.50-30.20 482.55-29.25Q485.60-28.30 487.95-26.60Q490.30-24.90 491.70-22.30Q493.10-19.70 493.10-15.90Q493.10-7.90 486.95-3.35Q480.80 1.20 469.80 1.20" }
1833
+ };
1834
+
1835
+ // src/gui/levels.tsx
1836
+ var jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
1837
+ function useProjects() {
1838
+ const [listed, setListed] = import_react7.useState(null);
1839
+ const reload = async () => {
1840
+ const j = await getJson("/api/projects");
1841
+ const l = { projects: j.projects, pending: j.pending ?? [], home: j.home ?? "" };
1842
+ setListed(l);
1843
+ return l;
1844
+ };
1845
+ import_react7.useEffect(() => {
1846
+ reload().catch(() => setListed({ projects: [], pending: [], home: "" }));
1847
+ }, []);
1848
+ return { listed, reload, set: setListed };
1849
+ }
1850
+ function pickerRows(projects, pending) {
1851
+ const all = [
1852
+ ...projects.map((p) => ({ kind: "baba", p, dir: p.dir ?? "", depth: 0 })),
1853
+ ...pending.map((e) => ({ kind: "pending", e, dir: e.dir, depth: 0 }))
1854
+ ];
1855
+ const parentOf = (r) => r.kind === "baba" ? r.p.parent : r.e.parent;
1856
+ const names = new Set(projects.map((p) => p.name));
1857
+ const out = [];
1858
+ const place = (r, depth) => {
1859
+ out.push({ ...r, depth });
1860
+ if (r.kind === "baba")
1861
+ for (const c of all.filter((x) => parentOf(x) === r.p.name))
1862
+ place(c, depth + 1);
1863
+ };
1864
+ for (const r of all) {
1865
+ const up = parentOf(r);
1866
+ if (!up || !names.has(up))
1867
+ place(r, 0);
1868
+ }
1869
+ return out;
1870
+ }
1871
+ var href = (name) => `/${encodeURIComponent(name)}/`;
1872
+ var setupHref = (dir) => `/?setup=${encodeURIComponent(dir)}`;
1873
+ var setupAsked = () => new URLSearchParams(location.search).get("setup");
1874
+ function Root() {
1875
+ const { listed, reload, set } = useProjects();
1876
+ useVossSettings();
1877
+ const [asked, setAsked] = import_react7.useState(setupAsked);
1878
+ import_react7.useEffect(() => {
1879
+ const back = () => setAsked(setupAsked());
1880
+ addEventListener("popstate", back);
1881
+ return () => removeEventListener("popstate", back);
1882
+ }, []);
1883
+ const go = (dir) => {
1884
+ history.pushState(null, "", dir ? setupHref(dir) : "/");
1885
+ setAsked(dir);
1886
+ };
1887
+ import_react7.useEffect(() => {
1888
+ document.title = asked ? "Set up · voss" : "voss";
1889
+ }, [asked]);
1890
+ const ready = asked ? listed?.projects.find((p) => p.dir === asked) : undefined;
1891
+ import_react7.useEffect(() => {
1892
+ if (ready)
1893
+ location.replace(href(ready.name));
1894
+ }, [ready]);
1895
+ if (!listed || ready)
1896
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1897
+ className: "gui-view rt"
1582
1898
  });
1899
+ if (asked) {
1900
+ const entry = listed.pending.find((e) => e.dir === asked);
1901
+ return /* @__PURE__ */ jsx_runtime6.jsx("div", {
1902
+ className: "gui-view rt rt-center",
1903
+ children: entry && !entry.error ? /* @__PURE__ */ jsx_runtime6.jsx(SetupWizard, {
1904
+ entry,
1905
+ home: listed.home,
1906
+ taken: listed.projects.map((p) => p.name),
1907
+ onCancel: () => go(null),
1908
+ onDone: (name) => location.assign(href(name))
1909
+ }) : /* @__PURE__ */ jsx_runtime6.jsxs("div", {
1910
+ className: "sw-card sw-gone",
1911
+ children: [
1912
+ /* @__PURE__ */ jsx_runtime6.jsx("p", {
1913
+ children: entry?.error ? /* @__PURE__ */ jsx_runtime6.jsxs(jsx_runtime6.Fragment, {
1914
+ children: [
1915
+ "The baba in ",
1916
+ /* @__PURE__ */ jsx_runtime6.jsx("code", {
1917
+ children: tilde(asked, listed.home)
1918
+ }),
1919
+ " did not open: ",
1920
+ entry.error
1921
+ ]
1922
+ }) : /* @__PURE__ */ jsx_runtime6.jsxs(jsx_runtime6.Fragment, {
1923
+ children: [
1924
+ "voss does not remember ",
1925
+ /* @__PURE__ */ jsx_runtime6.jsx("code", {
1926
+ children: tilde(asked, listed.home)
1927
+ }),
1928
+ "."
1929
+ ]
1930
+ })
1931
+ }),
1932
+ /* @__PURE__ */ jsx_runtime6.jsx("button", {
1933
+ type: "button",
1934
+ className: "sw-button",
1935
+ onClick: () => go(null),
1936
+ children: "Back to the babas"
1937
+ })
1938
+ ]
1939
+ })
1940
+ });
1941
+ }
1942
+ return /* @__PURE__ */ jsx_runtime6.jsx(Picker, {
1943
+ listed,
1944
+ reload,
1945
+ set,
1946
+ setup: go
1947
+ });
1948
+ }
1949
+ function VossVersion() {
1950
+ const [v, setV] = import_react7.useState(null);
1951
+ import_react7.useEffect(() => {
1952
+ getJson("/api/voss").then(setV, () => {});
1953
+ }, []);
1954
+ if (!v)
1955
+ return null;
1956
+ return /* @__PURE__ */ jsx_runtime6.jsxs("span", {
1957
+ className: "rt-version",
1958
+ title: v.sha ? `voss ${v.version}, ${v.sha.slice(0, 12)}` : `voss ${v.version}`,
1959
+ children: [
1960
+ /* @__PURE__ */ jsx_runtime6.jsxs("span", {
1961
+ className: "rt-version-text",
1962
+ children: [
1963
+ "v",
1964
+ v.version
1965
+ ]
1966
+ }),
1967
+ v.dev && /* @__PURE__ */ jsx_runtime6.jsx("span", {
1968
+ className: "rt-tag",
1969
+ children: /* @__PURE__ */ jsx_runtime6.jsx("span", {
1970
+ className: "rt-version-text",
1971
+ children: "checkout"
1972
+ })
1973
+ })
1974
+ ]
1975
+ });
1976
+ }
1977
+ function Lockup() {
1978
+ const t = lockup.tile, w = Number(lockup.viewBox.split(" ")[2]);
1979
+ return /* @__PURE__ */ jsx_runtime6.jsxs("svg", {
1980
+ className: "rt-lockup",
1981
+ viewBox: `${t.x} ${t.y} ${w - 2 * t.x} ${t.height}`,
1982
+ role: "img",
1983
+ "aria-label": "Baba Voss",
1984
+ children: [
1985
+ /* @__PURE__ */ jsx_runtime6.jsxs("mask", {
1986
+ id: "rt-lockup-eyes",
1987
+ maskUnits: "userSpaceOnUse",
1988
+ x: t.x,
1989
+ y: t.y,
1990
+ width: t.width,
1991
+ height: t.height,
1992
+ children: [
1993
+ /* @__PURE__ */ jsx_runtime6.jsx("rect", {
1994
+ x: t.x,
1995
+ y: t.y,
1996
+ width: t.width,
1997
+ height: t.height,
1998
+ fill: "#fff"
1999
+ }),
2000
+ /* @__PURE__ */ jsx_runtime6.jsx("g", {
2001
+ transform: lockup.symbol.transform,
2002
+ fill: "#000",
2003
+ children: lockup.symbol.eyes.map((e, i) => /* @__PURE__ */ jsx_runtime6.jsx("circle", {
2004
+ ...e
2005
+ }, i))
2006
+ })
2007
+ ]
2008
+ }),
2009
+ /* @__PURE__ */ jsx_runtime6.jsx("rect", {
2010
+ ...t,
2011
+ fill: "currentColor",
2012
+ mask: "url(#rt-lockup-eyes)"
2013
+ }),
2014
+ /* @__PURE__ */ jsx_runtime6.jsx("path", {
2015
+ fill: "currentColor",
2016
+ transform: lockup.word.transform,
2017
+ d: lockup.word.d
2018
+ })
2019
+ ]
2020
+ });
2021
+ }
2022
+ var Cross = () => /* @__PURE__ */ jsx_runtime6.jsx("svg", {
2023
+ viewBox: "0 0 16 16",
2024
+ width: "16",
2025
+ height: "16",
2026
+ "aria-hidden": "true",
2027
+ children: /* @__PURE__ */ jsx_runtime6.jsx("path", {
2028
+ d: "M4.5 4.5l7 7M11.5 4.5l-7 7",
2029
+ stroke: "currentColor",
2030
+ strokeWidth: "1.5",
2031
+ strokeLinecap: "round"
2032
+ })
2033
+ });
2034
+ var Glyph = ({ children }) => /* @__PURE__ */ jsx_runtime6.jsx("svg", {
2035
+ width: "16",
2036
+ height: "16",
2037
+ viewBox: "0 0 16 16",
2038
+ fill: "none",
2039
+ stroke: "currentColor",
2040
+ strokeWidth: "1.4",
2041
+ strokeLinecap: "round",
2042
+ strokeLinejoin: "round",
2043
+ "aria-hidden": "true",
2044
+ children
2045
+ });
2046
+ var FolderGlyph2 = () => /* @__PURE__ */ jsx_runtime6.jsx(Glyph, {
2047
+ children: /* @__PURE__ */ jsx_runtime6.jsx("path", {
2048
+ 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"
2049
+ })
2050
+ });
2051
+ var Plus = () => /* @__PURE__ */ jsx_runtime6.jsx("svg", {
2052
+ viewBox: "0 0 16 16",
2053
+ width: "16",
2054
+ height: "16",
2055
+ "aria-hidden": "true",
2056
+ children: /* @__PURE__ */ jsx_runtime6.jsx("path", {
2057
+ d: "M8 3.5v9M3.5 8h9",
2058
+ stroke: "currentColor",
2059
+ strokeWidth: "1.6",
2060
+ strokeLinecap: "round"
2061
+ })
2062
+ });
2063
+ var SetupGlyph = () => /* @__PURE__ */ jsx_runtime6.jsx(Glyph, {
2064
+ children: /* @__PURE__ */ jsx_runtime6.jsx("circle", {
2065
+ cx: "8",
2066
+ cy: "8",
2067
+ r: "5.5",
2068
+ strokeDasharray: "2.2 2"
2069
+ })
2070
+ });
2071
+ var WarnGlyph = () => /* @__PURE__ */ jsx_runtime6.jsxs(Glyph, {
2072
+ children: [
2073
+ /* @__PURE__ */ jsx_runtime6.jsx("path", {
2074
+ d: "M8 2.5l6 10.5H2z"
2075
+ }),
2076
+ /* @__PURE__ */ jsx_runtime6.jsx("path", {
2077
+ d: "M8 6.5v3M8 11.2v.1"
2078
+ })
2079
+ ]
2080
+ });
2081
+ var message = (e) => String(e instanceof Error ? e.message : e);
2082
+ function Picker({ listed, reload, set, setup }) {
2083
+ const { home } = listed;
2084
+ const rows = import_react7.useMemo(() => pickerRows(listed.projects, listed.pending), [listed]);
2085
+ const [browsing, setBrowsing] = import_react7.useState(false);
2086
+ const [error, setError] = import_react7.useState(null);
2087
+ const [opened, setOpened] = import_react7.useState(null);
2088
+ const [forgot, setForgot] = import_react7.useState(null);
2089
+ import_react7.useEffect(() => {
2090
+ if (!forgot)
2091
+ return;
2092
+ const t = setTimeout(() => setForgot(null), 6000);
2093
+ return () => clearTimeout(t);
2094
+ }, [forgot]);
2095
+ const list = import_react7.useRef(null);
2096
+ const answer = (j) => set({ projects: j.projects, pending: j.pending, home });
2097
+ const add = async (dir) => {
2098
+ setError(null);
2099
+ try {
2100
+ const j = await postJson("/api/projects/add", { dir });
2101
+ answer(j);
2102
+ setBrowsing(false);
2103
+ const waiting = j.pending.find((e) => e.dir === j.dir);
2104
+ if (waiting && !waiting.error)
2105
+ setup(j.dir);
2106
+ return true;
2107
+ } catch (e) {
2108
+ setError(message(e));
2109
+ return false;
2110
+ }
2111
+ };
2112
+ const forget = async (r) => {
2113
+ setError(null);
2114
+ try {
2115
+ answer(await postJson("/api/projects/remove", { dir: r.dir }));
2116
+ setForgot({ dir: r.dir, label: r.kind === "baba" ? r.p.title : tilde(r.dir, home) });
2117
+ } catch (e) {
2118
+ setError(message(e));
2119
+ reload();
2120
+ }
2121
+ };
2122
+ const undo = async () => {
2123
+ const f = forgot;
2124
+ setForgot(null);
2125
+ if (f)
2126
+ try {
2127
+ answer(await postJson("/api/projects/add", { dir: f.dir }));
2128
+ } catch (e) {
2129
+ setError(message(e));
2130
+ }
2131
+ };
2132
+ const open = (r) => {
2133
+ if (r.kind === "baba")
2134
+ location.assign(href(r.p.name));
2135
+ else if (!r.e.error)
2136
+ setup(r.dir);
2137
+ else
2138
+ setOpened(opened === r.dir ? null : r.dir);
2139
+ };
2140
+ const move = (e) => {
2141
+ if (e.key !== "ArrowDown" && e.key !== "ArrowUp")
2142
+ return;
2143
+ const all = [...list.current?.querySelectorAll("[data-row]") ?? []];
2144
+ const field = document.querySelector(".rt-add input");
2145
+ if (field)
2146
+ all.push(field);
2147
+ const i = all.indexOf(document.activeElement);
2148
+ if (i < 0)
2149
+ return;
2150
+ e.preventDefault();
2151
+ all[(i + (e.key === "ArrowDown" ? 1 : -1) + all.length) % all.length]?.focus();
2152
+ };
2153
+ return /* @__PURE__ */ jsx_runtime6.jsxs("div", {
2154
+ className: "gui-view rt",
2155
+ onKeyDown: move,
2156
+ children: [
2157
+ /* @__PURE__ */ jsx_runtime6.jsxs("div", {
2158
+ className: "rt-column",
2159
+ children: [
2160
+ /* @__PURE__ */ jsx_runtime6.jsxs("header", {
2161
+ className: "rt-mark",
2162
+ children: [
2163
+ /* @__PURE__ */ jsx_runtime6.jsxs("h1", {
2164
+ children: [
2165
+ /* @__PURE__ */ jsx_runtime6.jsx(Lockup, {}),
2166
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2167
+ className: "rt-hidden",
2168
+ children: "voss"
2169
+ })
2170
+ ]
2171
+ }),
2172
+ /* @__PURE__ */ jsx_runtime6.jsx(VossVersion, {})
2173
+ ]
2174
+ }),
2175
+ browsing ? /* @__PURE__ */ jsx_runtime6.jsx("div", {
2176
+ className: "rt-browse",
2177
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Folders, {
2178
+ home,
2179
+ pick: (dir) => void add(dir),
2180
+ onBack: () => setBrowsing(false)
2181
+ })
2182
+ }) : /* @__PURE__ */ jsx_runtime6.jsxs(jsx_runtime6.Fragment, {
2183
+ children: [
2184
+ /* @__PURE__ */ jsx_runtime6.jsxs("ul", {
2185
+ ref: list,
2186
+ className: "rt-list",
2187
+ "aria-label": "Babas",
2188
+ children: [
2189
+ rows.map((r) => {
2190
+ const label = r.kind === "baba" ? r.p.title : r.dir.split("/").filter(Boolean).pop() || r.dir;
2191
+ const failed = r.kind === "pending" && !!r.e.error;
2192
+ return /* @__PURE__ */ jsx_runtime6.jsxs("li", {
2193
+ className: `rt-row${failed ? " is-failed" : ""}`,
2194
+ style: { "--depth": r.depth },
2195
+ children: [
2196
+ /* @__PURE__ */ jsx_runtime6.jsxs("button", {
2197
+ type: "button",
2198
+ "data-row": true,
2199
+ className: "rt-row-main",
2200
+ title: r.kind === "baba" ? `Open ${r.p.title}` : failed ? "Show why it did not open" : `Set up the baba in ${tilde(r.dir, home)}`,
2201
+ onClick: () => open(r),
2202
+ children: [
2203
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2204
+ className: "rt-row-icon",
2205
+ children: r.kind === "baba" ? /* @__PURE__ */ jsx_runtime6.jsx(Eyes, {
2206
+ size: 16
2207
+ }) : failed ? /* @__PURE__ */ jsx_runtime6.jsx(WarnGlyph, {}) : /* @__PURE__ */ jsx_runtime6.jsx(SetupGlyph, {})
2208
+ }),
2209
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2210
+ className: "rt-row-label",
2211
+ children: label
2212
+ }),
2213
+ r.kind === "pending" && /* @__PURE__ */ jsx_runtime6.jsx("span", {
2214
+ className: `rt-tag${failed ? " is-failed" : ""}`,
2215
+ children: failed ? "Did not open" : "Set up"
2216
+ }),
2217
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2218
+ className: "rt-row-note",
2219
+ children: short(r.dir, home, 44)
2220
+ })
2221
+ ]
2222
+ }),
2223
+ /* @__PURE__ */ jsx_runtime6.jsx("button", {
2224
+ type: "button",
2225
+ className: "rt-row-forget",
2226
+ "aria-label": `Forget ${label}`,
2227
+ title: `Forget ${label}: voss stops running it; its files stay`,
2228
+ onClick: () => void forget(r),
2229
+ children: /* @__PURE__ */ jsx_runtime6.jsx(Cross, {})
2230
+ }),
2231
+ failed && opened === r.dir && r.kind === "pending" && /* @__PURE__ */ jsx_runtime6.jsx("p", {
2232
+ className: "rt-row-error",
2233
+ children: r.e.error
2234
+ })
2235
+ ]
2236
+ }, r.dir);
2237
+ }),
2238
+ !rows.length && /* @__PURE__ */ jsx_runtime6.jsx("li", {
2239
+ className: "rt-empty",
2240
+ children: "No babas yet. Add a folder to start one."
2241
+ })
2242
+ ]
2243
+ }),
2244
+ /* @__PURE__ */ jsx_runtime6.jsx(AddField, {
2245
+ home,
2246
+ onAdd: add,
2247
+ onBrowse: () => {
2248
+ setError(null);
2249
+ setBrowsing(true);
2250
+ }
2251
+ })
2252
+ ]
2253
+ }),
2254
+ error && /* @__PURE__ */ jsx_runtime6.jsx("p", {
2255
+ className: "rt-error",
2256
+ role: "alert",
2257
+ children: error
2258
+ })
2259
+ ]
2260
+ }),
2261
+ forgot && /* @__PURE__ */ jsx_runtime6.jsxs("div", {
2262
+ className: "rt-toast",
2263
+ role: "status",
2264
+ children: [
2265
+ "Forgot ",
2266
+ forgot.label,
2267
+ ". Its files stay.",
2268
+ /* @__PURE__ */ jsx_runtime6.jsx("button", {
2269
+ type: "button",
2270
+ onClick: () => void undo(),
2271
+ children: "Undo"
2272
+ })
2273
+ ]
2274
+ })
2275
+ ]
2276
+ });
2277
+ }
2278
+ function AddField({ home, onAdd, onBrowse }) {
2279
+ const [text, setText] = import_react7.useState("");
2280
+ const [dirs, setDirs] = import_react7.useState([]);
2281
+ const [at, setAt] = import_react7.useState(-1);
2282
+ const [busy, setBusy] = import_react7.useState(false);
2283
+ const expand = (t) => t === "~" ? home : t.startsWith("~/") ? home + t.slice(1) : t;
2284
+ const full = expand(text.trim());
2285
+ const cut = full.lastIndexOf("/");
2286
+ const parent = cut < 0 ? "" : full.slice(0, cut) || "/", leaf = cut < 0 ? full : full.slice(cut + 1);
2287
+ import_react7.useEffect(() => {
2288
+ if (!parent.startsWith("/")) {
2289
+ setDirs([]);
2290
+ return;
2291
+ }
2292
+ let live = true;
2293
+ const t = setTimeout(() => {
2294
+ getJson(`/api/dirs?path=${encodeURIComponent(parent)}`).then((b) => {
2295
+ if (live)
2296
+ setDirs(b.dirs);
2297
+ }, () => {
2298
+ if (live)
2299
+ setDirs([]);
2300
+ });
2301
+ }, 100);
1583
2302
  return () => {
1584
2303
  live = false;
2304
+ clearTimeout(t);
1585
2305
  };
2306
+ }, [parent]);
2307
+ const shown = dirs.filter((d) => d.name.toLowerCase().startsWith(leaf.toLowerCase()) && d.path !== full).slice(0, 6);
2308
+ const take = (d) => {
2309
+ setText(`${tilde(d.path, home)}/`);
2310
+ setAt(-1);
2311
+ };
2312
+ const submit = async () => {
2313
+ const dir = full.replace(/\/+$/, "") || "/";
2314
+ if (!dir.startsWith("/") || busy)
2315
+ return;
2316
+ setBusy(true);
2317
+ if (await onAdd(dir))
2318
+ setText("");
2319
+ setBusy(false);
2320
+ };
2321
+ return /* @__PURE__ */ jsx_runtime6.jsxs("div", {
2322
+ className: "rt-add",
2323
+ children: [
2324
+ /* @__PURE__ */ jsx_runtime6.jsxs("label", {
2325
+ className: "rt-add-field",
2326
+ children: [
2327
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2328
+ className: "rt-row-icon",
2329
+ children: /* @__PURE__ */ jsx_runtime6.jsx(FolderGlyph2, {})
2330
+ }),
2331
+ /* @__PURE__ */ jsx_runtime6.jsx("input", {
2332
+ value: text,
2333
+ placeholder: "Add a folder: type its path, ~/…",
2334
+ spellCheck: false,
2335
+ autoComplete: "off",
2336
+ "aria-label": "Add a folder by its path",
2337
+ disabled: busy,
2338
+ onChange: (e) => {
2339
+ setText(e.target.value);
2340
+ setAt(-1);
2341
+ },
2342
+ onKeyDown: (e) => {
2343
+ if (e.key === "Tab" && !e.shiftKey && shown.length && text.trim()) {
2344
+ e.preventDefault();
2345
+ take(shown[Math.max(0, at)]);
2346
+ } else if ((e.key === "ArrowDown" || e.key === "ArrowUp") && shown.length && text.trim()) {
2347
+ e.preventDefault();
2348
+ e.stopPropagation();
2349
+ const next = at + (e.key === "ArrowDown" ? 1 : -1);
2350
+ setAt(next < -1 ? shown.length - 1 : next >= shown.length ? -1 : next);
2351
+ } else if (e.key === "Enter") {
2352
+ e.preventDefault();
2353
+ if (at >= 0 && shown[at])
2354
+ take(shown[at]);
2355
+ else
2356
+ submit();
2357
+ } else if (e.key === "Escape" && text) {
2358
+ e.preventDefault();
2359
+ setText("");
2360
+ }
2361
+ }
2362
+ }),
2363
+ /* @__PURE__ */ jsx_runtime6.jsx("button", {
2364
+ type: "button",
2365
+ className: "rt-add-browse",
2366
+ onClick: onBrowse,
2367
+ children: "Browse"
2368
+ }),
2369
+ /* @__PURE__ */ jsx_runtime6.jsx("button", {
2370
+ type: "button",
2371
+ className: "rt-add-go",
2372
+ "aria-label": busy ? "Adding the folder" : "Add the folder",
2373
+ title: "Add the folder (↵)",
2374
+ disabled: busy || !full.startsWith("/"),
2375
+ onClick: () => void submit(),
2376
+ children: busy ? /* @__PURE__ */ jsx_runtime6.jsx("span", {
2377
+ className: "pk-spin",
2378
+ "aria-hidden": "true"
2379
+ }) : /* @__PURE__ */ jsx_runtime6.jsx(Plus, {})
2380
+ })
2381
+ ]
2382
+ }),
2383
+ shown.length > 0 && text.trim() && /* @__PURE__ */ jsx_runtime6.jsx("ul", {
2384
+ className: "rt-suggest",
2385
+ role: "listbox",
2386
+ "aria-label": "Folders",
2387
+ children: shown.map((d, i) => /* @__PURE__ */ jsx_runtime6.jsx("li", {
2388
+ role: "option",
2389
+ "aria-selected": i === at,
2390
+ children: /* @__PURE__ */ jsx_runtime6.jsxs("button", {
2391
+ type: "button",
2392
+ className: i === at ? "is-active" : undefined,
2393
+ onMouseDown: (e) => e.preventDefault(),
2394
+ onClick: () => take(d),
2395
+ children: [
2396
+ /* @__PURE__ */ jsx_runtime6.jsx("span", {
2397
+ className: "rt-row-icon",
2398
+ children: d.baba ? /* @__PURE__ */ jsx_runtime6.jsx(Eyes, {
2399
+ size: 14
2400
+ }) : /* @__PURE__ */ jsx_runtime6.jsx(FolderGlyph2, {})
2401
+ }),
2402
+ d.name,
2403
+ d.known ? /* @__PURE__ */ jsx_runtime6.jsx("span", {
2404
+ className: "rt-tag",
2405
+ children: "in the list"
2406
+ }) : d.baba ? /* @__PURE__ */ jsx_runtime6.jsx("span", {
2407
+ className: "rt-tag",
2408
+ children: "baba"
2409
+ }) : null
2410
+ ]
2411
+ })
2412
+ }, d.path))
2413
+ })
2414
+ ]
2415
+ });
2416
+ }
2417
+
2418
+ // src/gui/settings.tsx
2419
+ var import_react8 = __toESM(require_react(), 1);
2420
+ var jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
2421
+ var pages = [
2422
+ { level: "baba", path: "/", title: "Profile" },
2423
+ { level: "voss", path: "/appearance", title: "Appearance" },
2424
+ { level: "voss", path: "/keyboard", title: "Keyboard" },
2425
+ { level: "voss", path: "/about", title: "About" }
2426
+ ];
2427
+ var SettingsBaba = import_react8.createContext(undefined);
2428
+ function Settings({ path, navigate, setTitle }) {
2429
+ const baba = import_react8.useContext(SettingsBaba);
2430
+ const page = pages.find((p) => p.path === path) ?? pages[0];
2431
+ import_react8.useEffect(() => {
2432
+ setTitle("Settings");
1586
2433
  }, []);
1587
- return state;
2434
+ const link = (p) => /* @__PURE__ */ jsx_runtime7.jsx("button", {
2435
+ type: "button",
2436
+ className: `gui-settings-link${p === page ? " is-active" : ""}`,
2437
+ "aria-current": p === page ? "page" : undefined,
2438
+ onClick: () => navigate(p.path),
2439
+ children: p.title
2440
+ }, p.path);
2441
+ return /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2442
+ className: "gui-settings",
2443
+ children: [
2444
+ /* @__PURE__ */ jsx_runtime7.jsxs("nav", {
2445
+ className: "gui-settings-nav",
2446
+ "aria-label": "Settings",
2447
+ children: [
2448
+ /* @__PURE__ */ jsx_runtime7.jsx("h2", {
2449
+ className: "gui-settings-level",
2450
+ children: "Baba"
2451
+ }),
2452
+ pages.filter((p) => p.level === "baba").map(link),
2453
+ /* @__PURE__ */ jsx_runtime7.jsx("h2", {
2454
+ className: "gui-settings-level",
2455
+ children: "voss"
2456
+ }),
2457
+ pages.filter((p) => p.level === "voss").map(link)
2458
+ ]
2459
+ }),
2460
+ /* @__PURE__ */ jsx_runtime7.jsxs("section", {
2461
+ className: "gui-settings-page",
2462
+ "aria-labelledby": "gui-settings-title",
2463
+ children: [
2464
+ /* @__PURE__ */ jsx_runtime7.jsx("header", {
2465
+ className: "gui-settings-head",
2466
+ children: /* @__PURE__ */ jsx_runtime7.jsx("h1", {
2467
+ id: "gui-settings-title",
2468
+ children: page.title
2469
+ })
2470
+ }),
2471
+ page.path === "/" ? /* @__PURE__ */ jsx_runtime7.jsx(Profile, {
2472
+ baba
2473
+ }) : page.path === "/appearance" ? /* @__PURE__ */ jsx_runtime7.jsx(Appearance, {}) : page.path === "/keyboard" ? /* @__PURE__ */ jsx_runtime7.jsx(Keyboard, {}) : /* @__PURE__ */ jsx_runtime7.jsx(About, {
2474
+ baba
2475
+ })
2476
+ ]
2477
+ })
2478
+ ]
2479
+ });
1588
2480
  }
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 };
2481
+ function Setting({ title, summary, children }) {
2482
+ return /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2483
+ className: "gui-setting",
2484
+ children: [
2485
+ /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2486
+ className: "gui-setting-text",
2487
+ children: [
2488
+ /* @__PURE__ */ jsx_runtime7.jsx("div", {
2489
+ className: "gui-setting-title",
2490
+ children: title
2491
+ }),
2492
+ summary && /* @__PURE__ */ jsx_runtime7.jsx("div", {
2493
+ className: "gui-setting-summary",
2494
+ children: summary
2495
+ })
2496
+ ]
2497
+ }),
2498
+ /* @__PURE__ */ jsx_runtime7.jsx("div", {
2499
+ className: "gui-setting-control",
2500
+ children
2501
+ })
2502
+ ]
1599
2503
  });
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
2504
  }
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] : []
2505
+ var message2 = (e) => String(e instanceof Error ? e.message : e).replace(/^Error: /, "");
2506
+ function Appearance() {
2507
+ const settings = useVossSettings();
2508
+ const current2 = settings?.theme ?? "system";
2509
+ const [error, setError] = import_react8.useState(null);
2510
+ return /* @__PURE__ */ jsx_runtime7.jsx("div", {
2511
+ className: "gui-settings-list",
2512
+ children: /* @__PURE__ */ jsx_runtime7.jsx(Setting, {
2513
+ title: "Theme",
2514
+ summary: /* @__PURE__ */ jsx_runtime7.jsxs(jsx_runtime7.Fragment, {
2515
+ children: [
2516
+ "System follows this computer's light or dark appearance.",
2517
+ error && /* @__PURE__ */ jsx_runtime7.jsx("span", {
2518
+ className: "gui-setting-note is-error",
2519
+ children: error
2520
+ })
2521
+ ]
2522
+ }),
2523
+ children: /* @__PURE__ */ jsx_runtime7.jsx("div", {
2524
+ className: "gui-choices",
2525
+ role: "radiogroup",
2526
+ "aria-label": "Theme",
2527
+ children: themes.map((t) => /* @__PURE__ */ jsx_runtime7.jsx(Button, {
2528
+ variant: t === current2 ? "default" : "quiet",
2529
+ role: "radio",
2530
+ "aria-checked": t === current2,
2531
+ disabled: !settings,
2532
+ onClick: () => {
2533
+ setError(null);
2534
+ saveSettings({ theme: t }).catch((e) => setError(message2(e)));
2535
+ },
2536
+ children: t[0].toUpperCase() + t.slice(1)
2537
+ }, t))
2538
+ })
2539
+ })
2540
+ });
2541
+ }
2542
+ function Keyboard() {
2543
+ const settings = useVossSettings();
2544
+ const keymap = keymapOf(settings?.keys);
2545
+ const remapped = Object.keys(settings?.keys ?? {}).length > 0;
2546
+ const [error, setError] = import_react8.useState(null);
2547
+ const change = (keys, id) => {
2548
+ setError(null);
2549
+ saveSettings({ keys }).catch((e) => setError({ id, message: message2(e) }));
1613
2550
  };
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))
2551
+ return /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2552
+ className: "gui-settings-list",
2553
+ children: [
2554
+ keyCommands.map((c) => {
2555
+ const binding = keymap[c.id], own = settings?.keys?.[c.id] !== undefined;
2556
+ const note = error?.id === c.id ? error.message : binding && bindingNote(binding);
2557
+ return /* @__PURE__ */ jsx_runtime7.jsxs(Setting, {
2558
+ title: c.title,
2559
+ summary: /* @__PURE__ */ jsx_runtime7.jsxs(jsx_runtime7.Fragment, {
2560
+ children: [
2561
+ c.summary,
2562
+ note && /* @__PURE__ */ jsx_runtime7.jsx("span", {
2563
+ className: `gui-setting-note${error?.id === c.id ? " is-error" : ""}`,
2564
+ children: note
2565
+ })
2566
+ ]
2567
+ }),
2568
+ children: [
2569
+ /* @__PURE__ */ jsx_runtime7.jsx(KeyRecorder, {
2570
+ label: c.title,
2571
+ binding,
2572
+ onRecord: (b) => {
2573
+ const other = keyCommands.find((o) => o.id !== c.id && keymap[o.id] === b);
2574
+ if (other)
2575
+ setError({ id: c.id, message: `${formatBinding(b)} already runs ${other.title}.` });
2576
+ else
2577
+ change({ [c.id]: b }, c.id);
2578
+ },
2579
+ onInvalid: (m) => setError({ id: c.id, message: m })
2580
+ }),
2581
+ /* @__PURE__ */ jsx_runtime7.jsx(Button, {
2582
+ variant: "quiet",
2583
+ className: own ? "" : "is-hidden",
2584
+ disabled: !own,
2585
+ title: c.default ? `Back to ${formatBinding(c.default)}` : "Back to no key",
2586
+ onClick: () => change({ [c.id]: null }, c.id),
2587
+ children: "Reset"
2588
+ })
2589
+ ]
2590
+ }, c.id);
2591
+ }),
2592
+ /* @__PURE__ */ jsx_runtime7.jsxs("p", {
2593
+ className: "gui-settings-foot bv-muted",
2594
+ children: [
2595
+ "Click a key, then press the new one; Escape cancels. The launcher's arrows, Enter and Escape are fixed.",
2596
+ remapped && /* @__PURE__ */ jsx_runtime7.jsxs(jsx_runtime7.Fragment, {
2597
+ children: [
2598
+ " ",
2599
+ /* @__PURE__ */ jsx_runtime7.jsx(Button, {
2600
+ variant: "quiet",
2601
+ onClick: () => change(Object.fromEntries(keyCommands.map((c) => [c.id, null])), null),
2602
+ children: "Reset all"
2603
+ })
2604
+ ]
2605
+ }),
2606
+ error?.id === null && /* @__PURE__ */ jsx_runtime7.jsx("span", {
2607
+ className: "gui-setting-note is-error",
2608
+ children: error.message
2609
+ })
2610
+ ]
1636
2611
  })
2612
+ ]
2613
+ });
2614
+ }
2615
+ function Profile({ baba }) {
2616
+ const [name, setName] = import_react8.useState(baba?.name ?? "");
2617
+ const [taken, setTaken] = import_react8.useState([]);
2618
+ const [busy, setBusy] = import_react8.useState(false);
2619
+ const [error, setError] = import_react8.useState(null);
2620
+ import_react8.useEffect(() => {
2621
+ fetch("/api/projects", { headers: apiHeaders() }).then((r) => r.json()).then((j) => setTaken(j.projects.map((p) => p.name).filter((n) => n !== baba?.name)), () => {});
2622
+ }, [baba?.name]);
2623
+ if (!baba)
2624
+ return /* @__PURE__ */ jsx_runtime7.jsx("p", {
2625
+ className: "bv-muted",
2626
+ children: "…"
2627
+ });
2628
+ const why = name === baba.name ? null : requirementError("name", name, taken);
2629
+ const changed = name !== baba.name && !why;
2630
+ const save = async () => {
2631
+ if (!changed || busy)
2632
+ return;
2633
+ setBusy(true);
2634
+ setError(null);
2635
+ try {
2636
+ const r = await fetch("/api/projects/rename", { method: "POST", headers: { "content-type": "application/json", ...apiHeaders() }, body: JSON.stringify({ name: baba.name, to: name }) });
2637
+ const j = await r.json();
2638
+ if (!r.ok || j.error || !j.name)
2639
+ throw new Error(j.error?.message ?? "voss did not open it again");
2640
+ location.assign(`/${encodeURIComponent(j.name)}/settings`);
2641
+ } catch (e) {
2642
+ setError(message2(e));
2643
+ setBusy(false);
2644
+ }
2645
+ };
2646
+ return /* @__PURE__ */ jsx_runtime7.jsx("form", {
2647
+ className: "gui-settings-list",
2648
+ onSubmit: (e) => {
2649
+ e.preventDefault();
2650
+ save();
2651
+ },
2652
+ children: /* @__PURE__ */ jsx_runtime7.jsxs(Setting, {
2653
+ title: "Name",
2654
+ summary: /* @__PURE__ */ jsx_runtime7.jsxs(jsx_runtime7.Fragment, {
2655
+ children: [
2656
+ "What voss calls this baba, at ",
2657
+ /* @__PURE__ */ jsx_runtime7.jsxs("code", {
2658
+ children: [
2659
+ "/",
2660
+ baba.name,
2661
+ "/"
2662
+ ]
2663
+ }),
2664
+ " and as ",
2665
+ /* @__PURE__ */ jsx_runtime7.jsxs("code", {
2666
+ children: [
2667
+ "voss ",
2668
+ baba.name
2669
+ ]
2670
+ }),
2671
+ " on the CLI; not its folder's name, which stays as it is.",
2672
+ why || error ? /* @__PURE__ */ jsx_runtime7.jsx("span", {
2673
+ className: "gui-setting-note is-error",
2674
+ children: why ?? error
2675
+ }) : changed && /* @__PURE__ */ jsx_runtime7.jsxs("span", {
2676
+ className: "gui-setting-note",
2677
+ children: [
2678
+ "Renamed, it moves to ",
2679
+ /* @__PURE__ */ jsx_runtime7.jsxs("code", {
2680
+ children: [
2681
+ "/",
2682
+ name,
2683
+ "/"
2684
+ ]
2685
+ }),
2686
+ "; links to ",
2687
+ /* @__PURE__ */ jsx_runtime7.jsxs("code", {
2688
+ children: [
2689
+ "/",
2690
+ baba.name,
2691
+ "/"
2692
+ ]
2693
+ }),
2694
+ " stop working."
2695
+ ]
2696
+ })
2697
+ ]
2698
+ }),
2699
+ children: [
2700
+ /* @__PURE__ */ jsx_runtime7.jsx("input", {
2701
+ className: "gui-name",
2702
+ value: name,
2703
+ spellCheck: false,
2704
+ autoComplete: "off",
2705
+ "aria-label": "Name",
2706
+ disabled: busy,
2707
+ onChange: (e) => setName(e.target.value.toLowerCase())
2708
+ }),
2709
+ /* @__PURE__ */ jsx_runtime7.jsx(Button, {
2710
+ variant: changed ? "primary" : "quiet",
2711
+ type: "submit",
2712
+ disabled: !changed || busy,
2713
+ children: busy ? "Renaming…" : "Rename"
2714
+ })
2715
+ ]
2716
+ })
2717
+ });
2718
+ }
2719
+ function KeyRecorder({ label, binding, onRecord, onInvalid }) {
2720
+ const [recording, setRecording] = import_react8.useState(false);
2721
+ const ref = import_react8.useRef(null);
2722
+ return /* @__PURE__ */ jsx_runtime7.jsx("button", {
2723
+ ref,
2724
+ type: "button",
2725
+ className: `gui-key${recording ? " is-recording" : ""}`,
2726
+ "aria-label": recording ? `Press the new key for ${label}` : `${label}: ${binding ? formatBinding(binding) : "no key"}, click to ${binding ? "change" : "set one"}`,
2727
+ onClick: () => setRecording((r) => !r),
2728
+ onBlur: () => setRecording(false),
2729
+ onKeyDown: (e) => {
2730
+ if (!recording)
2731
+ return;
2732
+ e.preventDefault();
2733
+ e.stopPropagation();
2734
+ if (e.key === "Escape" && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) {
2735
+ setRecording(false);
2736
+ return;
2737
+ }
2738
+ const pressed = bindingOf(e.nativeEvent);
2739
+ if (!pressed)
2740
+ return;
2741
+ const b = normalBinding(pressed);
2742
+ setRecording(false);
2743
+ if (!b)
2744
+ onInvalid(`${formatBinding(pressed)} is typing, not a command: hold ${isMac() ? "⌘, ⌥ or ⌃" : "Ctrl or Alt"} with it.`);
2745
+ else if (b !== binding)
2746
+ onRecord(b);
2747
+ },
2748
+ children: recording ? "Press keys…" : binding ? formatBinding(binding) : /* @__PURE__ */ jsx_runtime7.jsx("span", {
2749
+ className: "gui-key-none",
2750
+ children: "Not set"
1637
2751
  })
1638
2752
  });
1639
2753
  }
2754
+ var tilde2 = (path, home) => home && (path === home || path.startsWith(`${home}/`)) ? `~${path.slice(home.length)}` : path;
2755
+ function Fact({ name, value, copy, mono }) {
2756
+ const [copied, setCopied] = import_react8.useState(false);
2757
+ return /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2758
+ className: "gui-fact",
2759
+ children: [
2760
+ /* @__PURE__ */ jsx_runtime7.jsx("dt", {
2761
+ children: name
2762
+ }),
2763
+ /* @__PURE__ */ jsx_runtime7.jsxs("dd", {
2764
+ className: mono ? "is-mono" : undefined,
2765
+ children: [
2766
+ /* @__PURE__ */ jsx_runtime7.jsx("span", {
2767
+ className: "gui-fact-value",
2768
+ title: copy,
2769
+ children: value
2770
+ }),
2771
+ copy && /* @__PURE__ */ jsx_runtime7.jsx("button", {
2772
+ type: "button",
2773
+ className: "gui-fact-copy",
2774
+ "aria-label": `Copy ${name}`,
2775
+ title: copied ? "Copied" : "Copy",
2776
+ onClick: () => {
2777
+ navigator.clipboard?.writeText(copy).then(() => {
2778
+ setCopied(true);
2779
+ setTimeout(() => setCopied(false), 1200);
2780
+ });
2781
+ },
2782
+ children: copied ? "Copied" : "Copy"
2783
+ })
2784
+ ]
2785
+ })
2786
+ ]
2787
+ });
2788
+ }
2789
+ function Facts({ title, rows }) {
2790
+ return /* @__PURE__ */ jsx_runtime7.jsxs("section", {
2791
+ className: "gui-facts",
2792
+ children: [
2793
+ /* @__PURE__ */ jsx_runtime7.jsx("h2", {
2794
+ children: title
2795
+ }),
2796
+ /* @__PURE__ */ jsx_runtime7.jsx("dl", {
2797
+ children: rows.filter((r) => !!r).map((r) => /* @__PURE__ */ jsx_runtime7.jsx(Fact, {
2798
+ ...r
2799
+ }, r.name))
2800
+ })
2801
+ ]
2802
+ });
2803
+ }
2804
+ function About({ baba }) {
2805
+ const [p, setP] = import_react8.useState(null);
2806
+ const [failed, setFailed] = import_react8.useState(null);
2807
+ const [copied, setCopied] = import_react8.useState(false);
2808
+ import_react8.useEffect(() => {
2809
+ let live = true;
2810
+ fetch("/api/projects", { headers: apiHeaders() }).then(async (r) => {
2811
+ if (!r.ok)
2812
+ throw new Error(r.statusText);
2813
+ return r.json();
2814
+ }).then((x) => {
2815
+ if (live)
2816
+ setP(x);
2817
+ }, (e) => {
2818
+ if (live)
2819
+ setFailed(String(e));
2820
+ });
2821
+ return () => {
2822
+ live = false;
2823
+ };
2824
+ }, []);
2825
+ if (failed)
2826
+ return /* @__PURE__ */ jsx_runtime7.jsxs("p", {
2827
+ className: "bv-error",
2828
+ children: [
2829
+ "voss did not answer: ",
2830
+ failed
2831
+ ]
2832
+ });
2833
+ if (!p)
2834
+ return /* @__PURE__ */ jsx_runtime7.jsx("p", {
2835
+ className: "bv-muted",
2836
+ children: "…"
2837
+ });
2838
+ const { voss, home } = p, a = voss.about;
2839
+ const here = baba && p.projects.find((x) => x.name === baba.name);
2840
+ const path = (name, full) => ({ name, value: tilde2(full, home), copy: full, mono: true });
2841
+ const m = baba?.manifest;
2842
+ const sections = [
2843
+ { title: "voss", rows: [
2844
+ { name: "Version", value: /* @__PURE__ */ jsx_runtime7.jsxs(jsx_runtime7.Fragment, {
2845
+ children: [
2846
+ voss.version,
2847
+ voss.dev && /* @__PURE__ */ jsx_runtime7.jsx("span", {
2848
+ className: "gui-fact-tag",
2849
+ children: "checkout"
2850
+ }),
2851
+ voss.stale && voss.installed && /* @__PURE__ */ jsx_runtime7.jsxs("span", {
2852
+ className: "gui-fact-tag is-warn",
2853
+ children: [
2854
+ voss.installed.version,
2855
+ " installed, restart to update"
2856
+ ]
2857
+ })
2858
+ ]
2859
+ }), copy: voss.version },
2860
+ { name: "Commit", value: voss.sha ? voss.sha.slice(0, 12) : voss.dev ? "the working tree" : "not stamped", ...voss.sha ? { copy: voss.sha, mono: true } : {} },
2861
+ voss.at && { name: "Packed", value: new Date(voss.at).toLocaleString() },
2862
+ { name: "Address", value: location.origin, copy: location.origin, mono: true },
2863
+ { name: "Process", value: `pid ${voss.pid}${a ? `, since ${new Date(a.started).toLocaleString()}` : ""}` },
2864
+ { name: "Runs as", value: voss.kept ? "the user service" : "a command, until it stops" }
2865
+ ] },
2866
+ ...a ? [
2867
+ { title: "Runtime", rows: [
2868
+ { name: "Bun", value: a.runtime.bun, copy: a.runtime.bun },
2869
+ { name: "Platform", value: `${a.runtime.platform} ${a.runtime.arch}` },
2870
+ path("Executable", a.runtime.executable)
2871
+ ] },
2872
+ { title: "Installation", rows: [
2873
+ path("Framework", a.framework),
2874
+ a.cli && path("CLI", a.cli),
2875
+ path("State", a.root),
2876
+ path("Registry", a.registry),
2877
+ path("Page token", a.token),
2878
+ a.service && path("User service", a.service.file),
2879
+ a.service && path("Service log", a.service.log)
2880
+ ] }
2881
+ ] : [],
2882
+ ...baba ? [{ title: "This baba", rows: [
2883
+ { name: "Name", value: baba.name, copy: baba.name, mono: true },
2884
+ { name: "Title", value: baba.title },
2885
+ here && path("Directory", here.dir),
2886
+ here && path("Code", `${here.dir}/.baba`),
2887
+ here?.parent && { name: "Inside", value: here.parent },
2888
+ { name: "Generation", value: String(baba.gen) },
2889
+ m && { name: "Contract", value: `${m.systems.length} systems, ${Object.keys(m.actions).length} actions, ${Object.keys(m.queries).length} queries, ${(m.apps ?? []).length} apps` },
2890
+ baba.error && { name: "Load", value: /* @__PURE__ */ jsx_runtime7.jsx("span", {
2891
+ className: "gui-fact-error",
2892
+ children: baba.error
2893
+ }) }
2894
+ ] }] : []
2895
+ ];
2896
+ const report = sections.map((s2) => [`# ${s2.title}`, ...s2.rows.filter((r) => !!r).map((r) => `${r.name}: ${r.copy ?? (typeof r.value === "string" ? r.value : "")}`)].join(`
2897
+ `)).join(`
2898
+
2899
+ `);
2900
+ return /* @__PURE__ */ jsx_runtime7.jsxs("div", {
2901
+ className: "gui-about",
2902
+ children: [
2903
+ sections.map((s2) => /* @__PURE__ */ jsx_runtime7.jsx(Facts, {
2904
+ ...s2
2905
+ }, s2.title)),
2906
+ /* @__PURE__ */ jsx_runtime7.jsxs("p", {
2907
+ className: "gui-settings-foot bv-muted",
2908
+ children: [
2909
+ /* @__PURE__ */ jsx_runtime7.jsx(Button, {
2910
+ variant: "quiet",
2911
+ onClick: () => {
2912
+ navigator.clipboard?.writeText(report).then(() => {
2913
+ setCopied(true);
2914
+ setTimeout(() => setCopied(false), 1200);
2915
+ });
2916
+ },
2917
+ children: copied ? "Copied" : "Copy all"
2918
+ }),
2919
+ "As plain text, for a bug report."
2920
+ ]
2921
+ })
2922
+ ]
2923
+ });
2924
+ }
1640
2925
 
1641
2926
  // src/kernel/names.ts
1642
2927
  var buildName = (b) => b.dev ? "source" : b.version || b.sha || "unstamped";
@@ -1649,7 +2934,7 @@ function tellApart(a, b) {
1649
2934
  }
1650
2935
 
1651
2936
  // src/gui/gui.tsx
1652
- var jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
2937
+ var jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
1653
2938
  var scope = document.querySelector('meta[name="voss-scope"]')?.getAttribute("content") ?? "";
1654
2939
  async function describe(name, since, signal) {
1655
2940
  const r = await fetch(`/api/describe/${encodeURIComponent(name)}${since ? `?since=${since}` : ""}`, { signal, headers: apiHeaders() });
@@ -1659,9 +2944,9 @@ async function describe(name, since, signal) {
1659
2944
  return d;
1660
2945
  }
1661
2946
  function useDescribed(name) {
1662
- const [d, setD] = import_react6.useState(null);
1663
- const [failed, setFailed] = import_react6.useState(null);
1664
- import_react6.useEffect(() => {
2947
+ const [d, setD] = import_react9.useState(null);
2948
+ const [failed, setFailed] = import_react9.useState(null);
2949
+ import_react9.useEffect(() => {
1665
2950
  const ctl = new AbortController;
1666
2951
  (async () => {
1667
2952
  let since = 0;
@@ -1686,9 +2971,9 @@ function useDescribed(name) {
1686
2971
  }
1687
2972
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1688
2973
  function Stale() {
1689
- const [b, setB] = import_react6.useState(null);
1690
- const [restarting, setRestarting] = import_react6.useState(false);
1691
- import_react6.useEffect(() => {
2974
+ const [b, setB] = import_react9.useState(null);
2975
+ const [restarting, setRestarting] = import_react9.useState(false);
2976
+ import_react9.useEffect(() => {
1692
2977
  let live = true;
1693
2978
  const tick = async () => {
1694
2979
  try {
@@ -1732,27 +3017,27 @@ function Stale() {
1732
3017
  }
1733
3018
  setRestarting(false);
1734
3019
  };
1735
- return /* @__PURE__ */ jsx_runtime6.jsx("div", {
3020
+ return /* @__PURE__ */ jsx_runtime8.jsx("div", {
1736
3021
  className: "gui-stale",
1737
3022
  role: "status",
1738
- children: restarting ? /* @__PURE__ */ jsx_runtime6.jsx("span", {
3023
+ children: restarting ? /* @__PURE__ */ jsx_runtime8.jsx("span", {
1739
3024
  children: "Restarting voss…"
1740
- }) : /* @__PURE__ */ jsx_runtime6.jsxs(jsx_runtime6.Fragment, {
3025
+ }) : /* @__PURE__ */ jsx_runtime8.jsxs(jsx_runtime8.Fragment, {
1741
3026
  children: [
1742
- /* @__PURE__ */ jsx_runtime6.jsxs("span", {
3027
+ /* @__PURE__ */ jsx_runtime8.jsxs("span", {
1743
3028
  children: [
1744
3029
  "voss ",
1745
- /* @__PURE__ */ jsx_runtime6.jsx("b", {
3030
+ /* @__PURE__ */ jsx_runtime8.jsx("b", {
1746
3031
  children: installed
1747
3032
  }),
1748
3033
  " is installed; this page runs on ",
1749
- /* @__PURE__ */ jsx_runtime6.jsx("b", {
3034
+ /* @__PURE__ */ jsx_runtime8.jsx("b", {
1750
3035
  children: running
1751
3036
  }),
1752
3037
  ". Changes on disk wait for the restart."
1753
3038
  ]
1754
3039
  }),
1755
- /* @__PURE__ */ jsx_runtime6.jsx(Button, {
3040
+ /* @__PURE__ */ jsx_runtime8.jsx(Button, {
1756
3041
  onClick: () => void restart(),
1757
3042
  children: "Restart to update"
1758
3043
  })
@@ -1765,8 +3050,8 @@ function Host() {
1765
3050
  const { d, failed } = useDescribed(scope);
1766
3051
  const parts = location.pathname.split("/").filter(Boolean);
1767
3052
  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(() => {
3053
+ const [, moveOn] = import_react9.useState(0);
3054
+ import_react9.useEffect(() => {
1770
3055
  if (to) {
1771
3056
  history.replaceState(null, "", `/${encodeURIComponent(scope)}/${to}${location.search}${location.hash}`);
1772
3057
  moveOn((n) => n + 1);
@@ -1775,17 +3060,24 @@ function Host() {
1775
3060
  if (to)
1776
3061
  return null;
1777
3062
  if (!d)
1778
- return /* @__PURE__ */ jsx_runtime6.jsx("div", {
3063
+ return failed ? /* @__PURE__ */ jsx_runtime8.jsx("div", {
1779
3064
  className: "gui-page",
1780
- children: /* @__PURE__ */ jsx_runtime6.jsx("p", {
3065
+ children: /* @__PURE__ */ jsx_runtime8.jsxs("p", {
1781
3066
  className: "bv-muted",
1782
- children: failed ? `${scope} did not answer: ${failed}` : "…"
3067
+ children: [
3068
+ scope,
3069
+ " did not answer: ",
3070
+ failed
3071
+ ]
1783
3072
  })
3073
+ }) : /* @__PURE__ */ jsx_runtime8.jsx("div", {
3074
+ className: "gui-view",
3075
+ children: /* @__PURE__ */ jsx_runtime8.jsx(DesktopSkeleton, {})
1784
3076
  });
1785
3077
  if (!d.manifest)
1786
- return /* @__PURE__ */ jsx_runtime6.jsx("div", {
3078
+ return /* @__PURE__ */ jsx_runtime8.jsx("div", {
1787
3079
  className: "gui-page",
1788
- children: /* @__PURE__ */ jsx_runtime6.jsxs("p", {
3080
+ children: /* @__PURE__ */ jsx_runtime8.jsxs("p", {
1789
3081
  className: "bv-error",
1790
3082
  children: [
1791
3083
  d.title,
@@ -1794,23 +3086,23 @@ function Host() {
1794
3086
  ]
1795
3087
  })
1796
3088
  });
1797
- return /* @__PURE__ */ jsx_runtime6.jsx(ShellPage, {
3089
+ return /* @__PURE__ */ jsx_runtime8.jsx(ShellPage, {
1798
3090
  d
1799
3091
  });
1800
3092
  }
1801
3093
  function Failure({ f }) {
1802
- return /* @__PURE__ */ jsx_runtime6.jsxs("div", {
3094
+ return /* @__PURE__ */ jsx_runtime8.jsxs("div", {
1803
3095
  className: "gui-notice",
1804
3096
  role: "alert",
1805
3097
  children: [
1806
- /* @__PURE__ */ jsx_runtime6.jsxs("strong", {
3098
+ /* @__PURE__ */ jsx_runtime8.jsxs("strong", {
1807
3099
  children: [
1808
3100
  "The interface did not ",
1809
3101
  f.stage === "build" ? "build" : f.stage === "load" ? "load" : "render",
1810
3102
  "."
1811
3103
  ]
1812
3104
  }),
1813
- /* @__PURE__ */ jsx_runtime6.jsx("pre", {
3105
+ /* @__PURE__ */ jsx_runtime8.jsx("pre", {
1814
3106
  className: "gui-failure",
1815
3107
  children: formatFailure(f)
1816
3108
  })
@@ -1838,9 +3130,9 @@ async function loadView(name, v) {
1838
3130
  }
1839
3131
  function useInterface(d) {
1840
3132
  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(() => {
3133
+ const [view, setView] = import_react9.useState(() => v !== undefined ? loaded.get(v) ?? null : null);
3134
+ const [failure, setFailure] = import_react9.useState(null);
3135
+ import_react9.useEffect(() => {
1844
3136
  if (v === undefined)
1845
3137
  return;
1846
3138
  let live = true;
@@ -1857,7 +3149,7 @@ function useInterface(d) {
1857
3149
  live = false;
1858
3150
  };
1859
3151
  }, [d.name, v]);
1860
- import_react6.useEffect(() => {
3152
+ import_react9.useEffect(() => {
1861
3153
  if (!view)
1862
3154
  return;
1863
3155
  const links = view.css.map((href2) => {
@@ -1876,51 +3168,49 @@ function useInterface(d) {
1876
3168
  }
1877
3169
  var useShellSystem = (apps) => {
1878
3170
  const key = JSON.stringify(apps ?? []);
1879
- return import_react6.useMemo(() => desktop({ apps: apps ?? [] }), [key]);
3171
+ return import_react9.useMemo(() => desktop({ apps: apps ?? [] }), [key]);
1880
3172
  };
1881
3173
  function ShellPage({ d }) {
1882
3174
  const { view, failure } = useInterface(d);
1883
3175
  const system2 = useShellSystem(d.manifest?.apps);
3176
+ const person = useVossSettings();
1884
3177
  const manifest = d.manifest;
1885
- const components = import_react6.useMemo(() => {
1886
- const pending = () => failure ? /* @__PURE__ */ jsx_runtime6.jsx(Failure, {
3178
+ const components = import_react9.useMemo(() => {
3179
+ const pending = () => failure ? /* @__PURE__ */ jsx_runtime8.jsx(Failure, {
1887
3180
  f: failure
1888
- }) : /* @__PURE__ */ jsx_runtime6.jsx("p", {
1889
- className: "desktop-empty bv-muted",
1890
- children: "…"
1891
- });
3181
+ }) : null;
1892
3182
  const out = {
1893
- maker: ({ path, navigate, setTitle }) => /* @__PURE__ */ jsx_runtime6.jsx(Maker, {
3183
+ maker: ({ path, navigate, setTitle }) => /* @__PURE__ */ jsx_runtime8.jsx(Maker, {
1894
3184
  specs: [],
1895
3185
  title: d.title,
1896
3186
  path,
1897
3187
  navigate,
1898
3188
  setTitle
1899
3189
  }),
1900
- state: () => /* @__PURE__ */ jsx_runtime6.jsxs("section", {
3190
+ state: () => /* @__PURE__ */ jsx_runtime8.jsxs("section", {
1901
3191
  className: "gui-app",
1902
3192
  children: [
1903
- d.error && /* @__PURE__ */ jsx_runtime6.jsxs("p", {
3193
+ d.error && /* @__PURE__ */ jsx_runtime8.jsxs("p", {
1904
3194
  className: "bv-error",
1905
3195
  children: [
1906
3196
  "The code did not load; the last generation serves: ",
1907
3197
  d.error
1908
3198
  ]
1909
3199
  }),
1910
- /* @__PURE__ */ jsx_runtime6.jsx(Inspector, {
3200
+ /* @__PURE__ */ jsx_runtime8.jsx(Inspector, {
1911
3201
  manifest
1912
3202
  })
1913
3203
  ]
1914
3204
  }),
1915
- contract: () => /* @__PURE__ */ jsx_runtime6.jsx("section", {
3205
+ contract: () => /* @__PURE__ */ jsx_runtime8.jsx("section", {
1916
3206
  className: "gui-app",
1917
- children: /* @__PURE__ */ jsx_runtime6.jsx(Runner, {
3207
+ children: /* @__PURE__ */ jsx_runtime8.jsx(Runner, {
1918
3208
  manifest
1919
3209
  })
1920
3210
  }),
1921
- promptware: () => /* @__PURE__ */ jsx_runtime6.jsx("section", {
3211
+ instructions: () => /* @__PURE__ */ jsx_runtime8.jsx("section", {
1922
3212
  className: "gui-app",
1923
- children: /* @__PURE__ */ jsx_runtime6.jsx(Promptware, {
3213
+ children: /* @__PURE__ */ jsx_runtime8.jsx(Instructions, {
1924
3214
  manifest
1925
3215
  })
1926
3216
  }),
@@ -1929,7 +3219,7 @@ function ShellPage({ d }) {
1929
3219
  for (const sc of manifest.apps ?? []) {
1930
3220
  if (sc.key in out)
1931
3221
  continue;
1932
- out[sc.key] = sc.key === "view" && !view?.apps[sc.key] ? view?.View ? view.View : !d.view ? () => /* @__PURE__ */ jsx_runtime6.jsx("p", {
3222
+ out[sc.key] = sc.key === "view" && !view?.apps[sc.key] ? view?.View ? view.View : !d.view ? () => /* @__PURE__ */ jsx_runtime8.jsx("p", {
1933
3223
  className: "desktop-empty bv-muted",
1934
3224
  children: "This baba has no interface.tsx yet: its apps are voss's."
1935
3225
  }) : pending : pending;
@@ -1938,65 +3228,36 @@ function ShellPage({ d }) {
1938
3228
  out[k] = v;
1939
3229
  return out;
1940
3230
  }, [view, failure, system2, manifest, d.error, d.view, d.title]);
1941
- return /* @__PURE__ */ jsx_runtime6.jsx("div", {
3231
+ return /* @__PURE__ */ jsx_runtime8.jsx("div", {
1942
3232
  className: "gui-view",
1943
- children: /* @__PURE__ */ jsx_runtime6.jsx(ViewBoundary, {
1944
- fallback: (f) => /* @__PURE__ */ jsx_runtime6.jsx("div", {
3233
+ children: /* @__PURE__ */ jsx_runtime8.jsx(ViewBoundary, {
3234
+ reset: view,
3235
+ fallback: (f) => /* @__PURE__ */ jsx_runtime8.jsx("div", {
1945
3236
  className: "gui-page",
1946
- children: /* @__PURE__ */ jsx_runtime6.jsx(Failure, {
3237
+ children: /* @__PURE__ */ jsx_runtime8.jsx(Failure, {
1947
3238
  f
1948
3239
  })
1949
3240
  }),
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"
3241
+ children: /* @__PURE__ */ jsx_runtime8.jsx(SettingsBaba.Provider, {
3242
+ value: d,
3243
+ children: /* @__PURE__ */ jsx_runtime8.jsx(Desktop, {
3244
+ system: system2,
3245
+ apps: components,
3246
+ title: d.title,
3247
+ root: { label: "voss", href: "/" },
3248
+ settings: person
1992
3249
  })
1993
3250
  })
1994
- ]
3251
+ })
1995
3252
  });
1996
3253
  }
1997
3254
 
1998
- class ViewBoundary extends import_react6.Component {
3255
+ class ViewBoundary extends import_react9.Component {
1999
3256
  state = { failure: null };
3257
+ componentDidUpdate(before) {
3258
+ if (before.reset !== this.props.reset && this.state.failure)
3259
+ this.setState({ failure: null });
3260
+ }
2000
3261
  static getDerivedStateFromError(err) {
2001
3262
  return { failure: { stage: "render", message: err instanceof Error ? err.message : String(err), detail: err instanceof Error ? err.stack : undefined } };
2002
3263
  }
@@ -2004,12 +3265,12 @@ class ViewBoundary extends import_react6.Component {
2004
3265
  return this.state.failure ? this.props.fallback(this.state.failure) : this.props.children;
2005
3266
  }
2006
3267
  }
2007
- import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsx_runtime6.jsxs(import_react6.StrictMode, {
3268
+ import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsx_runtime8.jsxs(import_react9.StrictMode, {
2008
3269
  children: [
2009
- scope ? /* @__PURE__ */ jsx_runtime6.jsx(ScopeProvider, {
3270
+ scope ? /* @__PURE__ */ jsx_runtime8.jsx(ScopeProvider, {
2010
3271
  value: scope,
2011
- children: /* @__PURE__ */ jsx_runtime6.jsx(Host, {})
2012
- }) : /* @__PURE__ */ jsx_runtime6.jsx(Root, {}),
2013
- /* @__PURE__ */ jsx_runtime6.jsx(Stale, {})
3272
+ children: /* @__PURE__ */ jsx_runtime8.jsx(Host, {})
3273
+ }) : /* @__PURE__ */ jsx_runtime8.jsx(Root, {}),
3274
+ /* @__PURE__ */ jsx_runtime8.jsx(Stale, {})
2014
3275
  ]
2015
3276
  }));