@oharness/server 0.10.0 → 0.12.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 (74) hide show
  1. package/dist/analytics.d.ts +32 -29
  2. package/dist/analytics.js +225 -278
  3. package/dist/analytics.js.map +1 -1
  4. package/dist/i18n.js +242 -12
  5. package/dist/i18n.js.map +1 -1
  6. package/dist/icons.d.ts +3 -0
  7. package/dist/icons.js +69 -3
  8. package/dist/icons.js.map +1 -1
  9. package/dist/model-order.js +1 -0
  10. package/dist/model-order.js.map +1 -1
  11. package/dist/page/client/account.js +140 -4
  12. package/dist/page/client/account.js.map +1 -1
  13. package/dist/page/client/base.js +42 -16
  14. package/dist/page/client/base.js.map +1 -1
  15. package/dist/page/client/browser.js +1 -1
  16. package/dist/page/client/composer.js +641 -47
  17. package/dist/page/client/composer.js.map +1 -1
  18. package/dist/page/client/dialogs.js +75 -0
  19. package/dist/page/client/dialogs.js.map +1 -1
  20. package/dist/page/client/events.js +25 -2
  21. package/dist/page/client/events.js.map +1 -1
  22. package/dist/page/client/index.js +4 -0
  23. package/dist/page/client/index.js.map +1 -1
  24. package/dist/page/client/panels.js +4 -4
  25. package/dist/page/client/ripple.d.ts +13 -0
  26. package/dist/page/client/ripple.js +247 -0
  27. package/dist/page/client/ripple.js.map +1 -0
  28. package/dist/page/client/routines.js +173 -41
  29. package/dist/page/client/routines.js.map +1 -1
  30. package/dist/page/client/shell.js +200 -54
  31. package/dist/page/client/shell.js.map +1 -1
  32. package/dist/page/client/state.js +7 -2
  33. package/dist/page/client/state.js.map +1 -1
  34. package/dist/page/client/terminal.js +34 -7
  35. package/dist/page/client/terminal.js.map +1 -1
  36. package/dist/page/client/theme.d.ts +17 -0
  37. package/dist/page/client/theme.js +365 -0
  38. package/dist/page/client/theme.js.map +1 -0
  39. package/dist/page/client/transcript.js +63 -7
  40. package/dist/page/client/transcript.js.map +1 -1
  41. package/dist/page/style.js +273 -22
  42. package/dist/page/style.js.map +1 -1
  43. package/dist/page.js +5 -5
  44. package/dist/page.js.map +1 -1
  45. package/dist/prefs.d.ts +17 -0
  46. package/dist/prefs.js +5 -0
  47. package/dist/prefs.js.map +1 -1
  48. package/dist/server.js +710 -45
  49. package/dist/server.js.map +1 -1
  50. package/dist/suggest.d.ts +42 -0
  51. package/dist/suggest.js +215 -0
  52. package/dist/suggest.js.map +1 -0
  53. package/dist/terminal.d.ts +35 -2
  54. package/dist/terminal.js +60 -4
  55. package/dist/terminal.js.map +1 -1
  56. package/dist/theme-assets.d.ts +39 -0
  57. package/dist/theme-assets.js +109 -0
  58. package/dist/theme-assets.js.map +1 -0
  59. package/dist/theme-store.d.ts +50 -0
  60. package/dist/theme-store.js +124 -0
  61. package/dist/theme-store.js.map +1 -0
  62. package/dist/theme-tools.d.ts +25 -0
  63. package/dist/theme-tools.js +243 -0
  64. package/dist/theme-tools.js.map +1 -0
  65. package/dist/tool-icons.d.ts +12 -0
  66. package/dist/tool-icons.js +16 -0
  67. package/dist/tool-icons.js.map +1 -0
  68. package/dist/turn-shots.d.ts +18 -2
  69. package/dist/turn-shots.js +10 -1
  70. package/dist/turn-shots.js.map +1 -1
  71. package/dist/user-theme.d.ts +143 -0
  72. package/dist/user-theme.js +510 -0
  73. package/dist/user-theme.js.map +1 -0
  74. package/package.json +2 -2
@@ -215,6 +215,21 @@ async function showModels() {
215
215
  return go;
216
216
  };
217
217
 
218
+ // What this vendor has cost lately, from this machine's own records — the
219
+ // question a pasted key leaves open, answered where the key is.
220
+ if (provider.spend30d >= 0.01) {
221
+ const spent = el("div", "muted small");
222
+ spent.appendChild(document.createTextNode(t("models.spend30d", { amount: moneyOf(Math.round(provider.spend30d * 100), "usd") }) + " "));
223
+ const more = el("button", "cn-inline", t("models.spendDetail"));
224
+ more.type = "button";
225
+ more.addEventListener("click", (e) => {
226
+ e.stopPropagation();
227
+ document.getElementById("nav-usage").click();
228
+ });
229
+ spent.appendChild(more);
230
+ cardBody.appendChild(spent);
231
+ }
232
+
218
233
  if (provider.readiness === "no-account-system") {
219
234
  cardBody.appendChild(el("div", "muted small", t("models.noAccounts")));
220
235
  cardBody.appendChild(el("div", "muted small", t("models.noAccountsHow")));
@@ -229,6 +244,8 @@ async function showModels() {
229
244
  cardBody.appendChild(signInButton());
230
245
  } else if (provider.signIn && (!provider.configured || String(provider.source || "").startsWith("env:"))) {
231
246
  cardBody.appendChild(signInButton());
247
+ } else if (provider.vendorSignIn && !provider.configured) {
248
+ cardBody.appendChild(vendorSignIn(provider, null, null));
232
249
  }
233
250
 
234
251
  // The key field, for every vendor that takes one — the account plans
@@ -236,7 +253,7 @@ async function showModels() {
236
253
  // and spends the same balance, and CI needs one because it does not
237
254
  // expire. This is where it belongs, next to every other vendor's key
238
255
  // field, rather than inside the sign-in dialog.
239
- if (!provider.configured || String(provider.source || "").startsWith("env:")) {
256
+ if (!keyless(provider) && (!provider.configured || String(provider.source || "").startsWith("env:"))) {
240
257
  const row = el("div", "keyrow");
241
258
  const field = document.createElement("input");
242
259
  // type=password so the key is not left legible on a screen, and
@@ -467,6 +484,12 @@ function openConnect(providerId, thenModel) {
467
484
  document.getElementById("cn-title").textContent = provider.signIn && provider.readiness !== "no-account-system"
468
485
  ? t("models.signIn")
469
486
  : provider.label || provider.id;
487
+ // A vendor's own sign-in is that vendor's page the user is about to visit,
488
+ // so the dialog carries its mark: recognising the logo is most of what
489
+ // makes "sign in with" feel safe to click.
490
+ if (!provider.signIn && provider.vendorSignIn) {
491
+ document.getElementById("cn-title").prepend(providerMark(provider.id, provider.label || provider.id, "cn-title-mark"));
492
+ }
470
493
  // What this dialog is answering. A credential that ran out and one that was
471
494
  // never created both arrive here, and telling someone to "sign in" when what
472
495
  // happened is "your session ended" leaves them wondering whether the first
@@ -594,6 +617,54 @@ function openConnect(providerId, thenModel) {
594
617
  const siteButton = site.querySelector("button");
595
618
  const orCode = el("div", "cn-note", ts("models.orCode"));
596
619
 
620
+ // A vendor's own sign-in, above the key field it makes optional — or, for a
621
+ // vendor with no key at all, in place of it.
622
+ const vendor = mode === "key" && provider.vendorSignIn
623
+ ? vendorSignIn(
624
+ provider,
625
+ () => {
626
+ if (dialog.open) void done("signed in to " + provider.id + " — discovering its models…");
627
+ },
628
+ (reason) => {
629
+ hint.textContent = reason;
630
+ hint.hidden = false;
631
+ },
632
+ )
633
+ : null;
634
+ const noKey = keyless(provider);
635
+ const orKey = vendor && !noKey ? el("div", "cn-note", t("models.orPasteKey")) : null;
636
+ // What signing in hands over, said where the hesitation is: right under the
637
+ // button. Only when the sign-in is the whole dialog — beside a key field the
638
+ // key's own note says the same, and two padlocks in a row read as clutter.
639
+ let vendorLocal = null;
640
+ if (vendor && noKey) {
641
+ vendorLocal = el("div", "cn-note cn-local");
642
+ vendorLocal.appendChild(svgIcon("lock"));
643
+ const words = el("span");
644
+ words.appendChild(el("strong", null, t("models.vendorLocalLead", { provider: provider.label || provider.id })));
645
+ words.appendChild(document.createTextNode(t("models.vendorLocalRest")));
646
+ vendorLocal.appendChild(words);
647
+ }
648
+ // Someone with a Claude plan arrives here looking for a way to use it, and
649
+ // a key field alone says nothing to them. The plan works in Claude Code and
650
+ // not in other apps, so the honest answer is a door to Claude Code — their
651
+ // own install, in this project's terminal — rather than a login of ours.
652
+ let claudePlan = null;
653
+ if (provider.id === "anthropic" && currentCwd) {
654
+ claudePlan = el("div", "cn-note");
655
+ claudePlan.appendChild(document.createTextNode(t("models.claudePlan") + " "));
656
+ const open = el("button", "cn-inline", t("models.claudePlanOpen"));
657
+ open.type = "button";
658
+ open.onclick = () => {
659
+ dialog.close();
660
+ void openClaudeCode(currentCwd);
661
+ };
662
+ claudePlan.appendChild(open);
663
+ }
664
+ // The sign-in is the whole dialog then, and the × already closes it: a
665
+ // Cancel alone in the actions row only read as a button left behind.
666
+ dialog.classList.toggle("cn-keyless", noKey);
667
+
597
668
  /**
598
669
  * What the dialog opens focused.
599
670
  *
@@ -606,7 +677,13 @@ function openConnect(providerId, thenModel) {
606
677
  // no account system it is the one that fetches a key, which is the same
607
678
  // shape of thing and deserves the same treatment. The field is where the
608
679
  // cursor goes only when pasting is what is left.
609
- const opener = mode === "account" && !flow ? siteButton : noAccounts ? keyButton : undefined;
680
+ const opener = mode === "account" && !flow
681
+ ? siteButton
682
+ : noAccounts
683
+ ? keyButton
684
+ : vendor
685
+ ? vendor.querySelector("button")
686
+ : undefined;
610
687
  if (opener) opener.focus();
611
688
  else field.focus();
612
689
  };
@@ -665,7 +742,16 @@ function openConnect(providerId, thenModel) {
665
742
  // button above the field: whichever control is the way in carries the
666
743
  // colour, and the actions row holds the fallback. Two filled buttons make
667
744
  // the reader choose by position, and position would pick the wrong one.
668
- save.classList.toggle("approve", mode === "key" && !noAccounts);
745
+ save.classList.toggle("approve", mode === "key" && !noAccounts && !vendor);
746
+ // Nothing to paste for a vendor that takes no key: the sign-in above is
747
+ // the whole dialog, and a field beside it could only ever be refused.
748
+ field.hidden = noKey;
749
+ save.hidden = noKey;
750
+ if (noKey) {
751
+ why.hidden = true;
752
+ keptLocal.hidden = true;
753
+ docs.hidden = true;
754
+ }
669
755
  field.value = "";
670
756
  focusPrimary();
671
757
  };
@@ -818,10 +904,14 @@ function openConnect(providerId, thenModel) {
818
904
  // somebody expecting a sign-in would ask — and they are exactly who opens
819
905
  // the mark.
820
906
  ...(noAccounts ? [keyLink(), el("div", "cn-note", t("models.orPasteKey"))] : []),
907
+ ...(vendor ? [vendor] : []),
908
+ ...(vendorLocal ? [vendorLocal] : []),
909
+ ...(orKey ? [orKey] : []),
821
910
  site,
822
911
  orCode,
823
912
  field,
824
913
  keptLocal,
914
+ ...(claudePlan ? [claudePlan] : []),
825
915
  );
826
916
  // Re-attached on every open: the actions row is markup, so a link left from
827
917
  // the last vendor would otherwise point at their key page. The link is built
@@ -982,7 +1072,7 @@ async function loadAccount() {
982
1072
  const name = body.name || body.email || t("account.signedIn");
983
1073
  row.appendChild(avatarFor(name, body.avatar));
984
1074
  row.appendChild(el("span", "who-name", name));
985
- row.appendChild(svgIcon("chevronsVertical", "acct-caret"));
1075
+ row.appendChild(svgIcon("caret", "acct-caret"));
986
1076
 
987
1077
  // The balance, once it can be read. Running out mid-turn is the failure this
988
1078
  // prevents: a number that appears only in a paywall is a number nobody sees
@@ -1245,6 +1335,7 @@ async function loadAccount() {
1245
1335
  cls: "accent",
1246
1336
  }),
1247
1337
  themeRow(),
1338
+ ...customThemeRows(),
1248
1339
  menuItem(t("account.signOut"), {
1249
1340
  run: () => {
1250
1341
  closeRowMenu();
@@ -1314,6 +1405,51 @@ function websiteSignIn(provider, onSignedIn, onUnavailable) {
1314
1405
  return row;
1315
1406
  }
1316
1407
 
1408
+ /**
1409
+ * A vendor whose only way in is its browser sign-in — ChatGPT, whose plan has
1410
+ * no key to paste. Offering a key field for it would be a dead end.
1411
+ */
1412
+ function keyless(provider) {
1413
+ return Boolean(provider.vendorSignIn) && !(provider.envVars || []).length;
1414
+ }
1415
+
1416
+ /**
1417
+ * A vendor's own browser sign-in: Sign in with ChatGPT, OpenRouter.
1418
+ *
1419
+ * Like the website sign-in, the answer does not come back to the click. The
1420
+ * vendor redirects to a listener on this machine, the server finishes the
1421
+ * login there, and models_changed is what tells this page — so the waiter is
1422
+ * registered only once the tab has actually been opened.
1423
+ */
1424
+ function vendorSignIn(provider, onSignedIn, onError) {
1425
+ const row = el("div", "keyrow");
1426
+ const go = el("button", "signin-site", provider.vendorSignIn);
1427
+ go.type = "button";
1428
+ go.addEventListener("click", (e) => {
1429
+ e.preventDefault();
1430
+ e.stopPropagation();
1431
+ go.disabled = true;
1432
+ void api("/signin/vendor", { provider: provider.id })
1433
+ .then((result) => {
1434
+ if (!result || result.error) {
1435
+ const message = (result && result.error) || "could not start the sign-in";
1436
+ if (onError) return void onError(message);
1437
+ return void addNotice(message, "err");
1438
+ }
1439
+ signedInWaiters.set(provider.id, onSignedIn || (() => {
1440
+ if (document.getElementById("nav-models").classList.contains("active")) void showModels();
1441
+ }));
1442
+ window.open(result.url, "_blank", "noopener");
1443
+ addNotice(t("models.signInOpened", { site: provider.label || provider.id }));
1444
+ })
1445
+ .finally(() => {
1446
+ go.disabled = false;
1447
+ });
1448
+ });
1449
+ row.appendChild(go);
1450
+ return row;
1451
+ }
1452
+
1317
1453
  /**
1318
1454
  * The sign-in itself, without a button attached to it.
1319
1455
  *
@@ -1 +1 @@
1
- {"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/page/client/account.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqvEhC,CAAC"}
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/page/client/account.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA63EhC,CAAC"}
@@ -117,23 +117,35 @@ const ICON_PATHS = {
117
117
  themeDark: ["M13.2 9.6A5.6 5.6 0 0 1 6.4 2.8a5.6 5.6 0 1 0 6.8 6.8Z"],
118
118
  themeSystemRects: [[1.8, 2.6, 12.4, 8.6, 1.6]],
119
119
  themeLightCircles: [[8, 8, 3.1]],
120
+ // The add menu's marks all sit on one keyline, 2–14, the box the folder was
121
+ // redrawn onto: anything that runs out to 1.33 is the heaviest mark in the
122
+ // column, and anything drawn in 4.5–11.5 is the faintest.
123
+ //
120
124
  // A page with its corner turned back, which is the one mark that reads as
121
125
  // "file" at thirteen pixels without any lines of text inside it.
122
126
  file: [
123
- "M9.33 1.33H4a1.33 1.33 0 0 0-1.33 1.34v10.66A1.33 1.33 0 0 0 4 14.67h8" +
124
- "a1.33 1.33 0 0 0 1.33-1.34V5.33Z",
125
- "M9.33 1.33v4h4",
127
+ "M9.5 2H4.33A1.33 1.33 0 0 0 3 3.33v9.34A1.33 1.33 0 0 0 4.33 14h7.34" +
128
+ "A1.33 1.33 0 0 0 13 12.67V5.5Z",
129
+ "M9.5 2v3.5H13",
130
+ ],
131
+ // A paperclip, for the menu row that attaches files or photos: the mark that
132
+ // row carries everywhere else, and one that covers a photo as well as a
133
+ // document. The chip an attachment becomes keeps the page above, since by
134
+ // then it is a file rather than the act of attaching one.
135
+ attach: [
136
+ "M10.4 4.4 5.35 9.55a1.2 1.2 0 0 0 1.7 1.7l5.05-5.15a2.4 2.4 0 1 0-3.4-3.4" +
137
+ "L3.67 7.83a3.6 3.6 0 1 0 5.1 5.1l5.03-5.13",
126
138
  ],
127
139
  // Two pins and a body: a connector is the thing something else plugs into.
128
- plug: ["M6 1.33v3.34", "M10 1.33v3.34", "M4 4.67h8v3.33a4 4 0 0 1-8 0Z", "M8 12v2.67"],
140
+ plug: ["M6 2v3", "M10 2v3", "M3.5 5h9v2.5a4.5 4.5 0 0 1-9 0Z", "M8 12v2"],
129
141
  // Stacked plates, because a plugin arrives as a bundle of contributions
130
142
  // rather than as one thing.
131
- plugin: ["M8 1.33 1.33 4.67 8 8l6.67-3.33Z", "M1.33 11.33 8 14.67l6.67-3.34", "M1.33 8 8 11.33 14.67 8"],
143
+ plugin: ["M8 2 2 5l6 3 6-3Z", "M2 11l6 3 6-3", "M2 8l6 3 6-3"],
132
144
  /* A bookmark: one path, and the only shape in this menu that is neither a
133
145
  sheet nor a folder. The open book it replaced was two mirrored paths
134
146
  meeting at a spine, which at the 13px these rows use closed into a blob —
135
147
  two strokes a pixel apart read as one thick one. */
136
- skill: ["M4.5 2.5h7v11l-3.5-2.6-3.5 2.6Z"],
148
+ skill: ["M5.33 2h5.34A1.33 1.33 0 0 1 12 3.33V14l-4-3-4 3V3.33A1.33 1.33 0 0 1 5.33 2Z"],
137
149
  /* A prompt caret: what a command line looks like anywhere else. */
138
150
  command: ["M4 5.5 6.5 8 4 10.5", "M8.5 10.5H12"],
139
151
  /* A bare chevron. The other back arrow has a shaft, which is right on a
@@ -144,6 +156,7 @@ const ICON_PATHS = {
144
156
  close: ["M4.5 4.5l7 7", "M11.5 4.5l-7 7"],
145
157
  /* Its counterpart, for adding one. */
146
158
  plus: ["M8 3.5v9", "M3.5 8h9"],
159
+ check: ["M3.5 8.5l3 3 6-7"],
147
160
  /* A picture: a frame, a hill and a sun — what "attach an image" looks like
148
161
  everywhere else. */
149
162
  image: ["M2.5 11.5 6 8l3 3 1.5-1.5 3 3"],
@@ -157,12 +170,17 @@ const ICON_PATHS = {
157
170
  back: ["M12.67 8H3.33", "M7.33 12 3.33 8l4-4"],
158
171
  // An arrow leaving a tray, for the upload that still goes to this machine.
159
172
  upload: ["M8 10.67V2", "M4.67 5.33 8 2l3.33 3.33", "M2 10.67v2A1.33 1.33 0 0 0 3.33 14h9.34A1.33 1.33 0 0 0 14 12.67v-2"],
173
+ // The same tray with the arrow coming in: bringing in what another tool has.
174
+ import: ["M8 2v8.67", "M4.67 7.33 8 10.67l3.33-3.34", "M2 10.67v2A1.33 1.33 0 0 0 3.33 14h9.34A1.33 1.33 0 0 0 14 12.67v-2"],
160
175
  // A trunk with a branch leaving it, which is what forking a turn does to the
161
176
  // transcript: the arc lands on the new node rather than crossing it.
162
177
  fork: ["M5 2.6v7.7", "M11 6a5.7 5.7 0 0 1-4.3 6"],
163
178
  forkCircles: [[11, 4.3], [5, 12]],
164
- // A trunk and the branch that leaves it for a tree of its own.
165
- worktree: ["M4.5 2v12", "M4.5 10.5c0-3.5 7-2.5 7-6", "M11.5 2.5a1 1 0 1 1 0 2 1 1 0 1 1 0-2"],
179
+ // A trunk and the branch that leaves it for a tree of its own. Three nodes
180
+ // where the fork has two: the trunk carries on past the point the branch
181
+ // left it, because a worktree is a second checkout, not a turn that moved.
182
+ worktree: ["M4.5 5.1v5.8", "M11.5 6.1a6.4 6.4 0 0 1-5.4 6.4"],
183
+ worktreeCircles: [[4.5, 3.5, 1.6], [4.5, 12.5, 1.6], [11.5, 4.5, 1.6]],
166
184
  copy: [
167
185
  "M6.67 5.33h6.66a1.33 1.33 0 0 1 1.34 1.34v6.66a1.33 1.33 0 0 1-1.34 1.34" +
168
186
  "H6.67a1.33 1.33 0 0 1-1.34-1.34V6.67a1.33 1.33 0 0 1 1.34-1.34Z",
@@ -204,8 +222,8 @@ const ICON_PATHS = {
204
222
  // Accept edits lets the pencil through.
205
223
  modeEdits: ["M11.3 2.7 13.3 4.7 5.3 12.7H3.3v-2Z", "M9.8 4.2l2 2"],
206
224
  // Plan is read-only: an eye, which looks and does not touch.
207
- modePlan: ["M1.6 8s2.4-4.4 6.4-4.4S14.4 8 14.4 8s-2.4 4.4-6.4 4.4S1.6 8 1.6 8Z"],
208
- modePlanCircles: [[8, 8, 2.1]],
225
+ modePlan: ["M2 8s2.3-4.8 6-4.8S14 8 14 8s-2.3 4.8-6 4.8S2 8 2 8Z"],
226
+ modePlanCircles: [[8, 8, 2.2]],
209
227
  // Auto waves the routine through: a bolt.
210
228
  modeAuto: ["M8.8 1.6 3.6 9h4l-.7 5.4L12.4 7h-4Z"],
211
229
  // Bypass leaves the gate open: a padlock with its shackle swung clear.
@@ -218,9 +236,16 @@ const ICON_PATHS = {
218
236
  // Goal mode: a target, rings closing on the point being aimed at.
219
237
  goal: ["M8 8h.01"],
220
238
  goalCircles: [[8, 8, 6], [8, 8, 3]],
221
- // Computer use: a screen on its stand, with the pointer that drives it.
222
- computer: ["M5.5 14h5M8 11.5V14", "M7.2 4.6l3.6 1.5-1.6.5-.5 1.6Z"],
223
- computerRects: [[1.5, 2, 13, 9.5, 1.6]],
239
+ // Computer use: a screen on its stand, and the pointer that drives it. The
240
+ // pointer used to sit inside the screen at three and a half units, where it
241
+ // closed into a triangle and the whole mark read as a video's play button.
242
+ // Now it is full size and breaks out over the screen's corner, and the frame
243
+ // stops short of it so the two outlines never cross.
244
+ computer: [
245
+ "M8.5 10.5h-5A1.5 1.5 0 0 1 2 9V4A1.5 1.5 0 0 1 3.5 2.5h7A1.5 1.5 0 0 1 12 4v3",
246
+ "M7 10.5V13M5 13h4",
247
+ "M9.5 8.5l4.5 1.9-2 .6-.6 2Z",
248
+ ],
224
249
  chevronLeft: ["M10 3.5 5.5 8l4.5 4.5"],
225
250
  chevronRight: ["M6 3.5 10.5 8 6 12.5"],
226
251
  };
@@ -387,9 +412,10 @@ const api = (path, body, timeoutMs) =>
387
412
  /**
388
413
  * Record that a feature was used. Does nothing unless analytics are on.
389
414
  *
390
- * window.__TRACK exists only when the server was started with a PostHog key —
391
- * see analytics.ts — so the calls below read the same either way and nobody
392
- * has to remember which branch they are writing in.
415
+ * window.__TRACK forwards to this server's /track once it has said analytics
416
+ * are on, and drops everything otherwise — see analytics.ts — so the calls
417
+ * below read the same either way and nobody has to remember which branch they
418
+ * are writing in. The server checks what arrives before anything leaves.
393
419
  *
394
420
  * There is deliberately no way to send text through here: every caller passes
395
421
  * an event name and a handful of enumerable properties — which panel, whether
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/page/client/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Z7B,CAAC"}
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/page/client/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwb7B,CAAC"}
@@ -578,7 +578,7 @@ async function browserReload() {
578
578
 
579
579
  async function browserStop() {
580
580
  if (!activeId || !browserView(activeId).running) return;
581
- if (!confirm(t("browser.stopConfirm"))) return;
581
+ if (!(await askConfirm({ body: t("browser.stopConfirm"), ok: t("dialog.stop"), danger: true }))) return;
582
582
  browserStopBtn.disabled = true;
583
583
  const result = await api("/browser/close", { session: activeId });
584
584
  if (result.error) {