@oharness/server 0.9.3 → 0.11.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 (93) 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 +500 -64
  5. package/dist/i18n.js.map +1 -1
  6. package/dist/icons.js +1 -0
  7. package/dist/icons.js.map +1 -1
  8. package/dist/main.js +4 -0
  9. package/dist/main.js.map +1 -1
  10. package/dist/model-order.js +1 -0
  11. package/dist/model-order.js.map +1 -1
  12. package/dist/page/client/account.js +570 -27
  13. package/dist/page/client/account.js.map +1 -1
  14. package/dist/page/client/base.js +55 -12
  15. package/dist/page/client/base.js.map +1 -1
  16. package/dist/page/client/browser.js +1 -1
  17. package/dist/page/client/composer.js +802 -59
  18. package/dist/page/client/composer.js.map +1 -1
  19. package/dist/page/client/computer.d.ts +27 -0
  20. package/dist/page/client/computer.js +358 -0
  21. package/dist/page/client/computer.js.map +1 -0
  22. package/dist/page/client/dialogs.js +168 -6
  23. package/dist/page/client/dialogs.js.map +1 -1
  24. package/dist/page/client/events.js +119 -9
  25. package/dist/page/client/events.js.map +1 -1
  26. package/dist/page/client/files.js +86 -1
  27. package/dist/page/client/files.js.map +1 -1
  28. package/dist/page/client/index.js +8 -0
  29. package/dist/page/client/index.js.map +1 -1
  30. package/dist/page/client/panels.js +14 -10
  31. package/dist/page/client/panels.js.map +1 -1
  32. package/dist/page/client/ripple.d.ts +13 -0
  33. package/dist/page/client/ripple.js +247 -0
  34. package/dist/page/client/ripple.js.map +1 -0
  35. package/dist/page/client/routines.js +432 -45
  36. package/dist/page/client/routines.js.map +1 -1
  37. package/dist/page/client/shell.js +386 -104
  38. package/dist/page/client/shell.js.map +1 -1
  39. package/dist/page/client/state.js +278 -19
  40. package/dist/page/client/state.js.map +1 -1
  41. package/dist/page/client/tasks.d.ts +8 -0
  42. package/dist/page/client/tasks.js +293 -0
  43. package/dist/page/client/tasks.js.map +1 -0
  44. package/dist/page/client/terminal.js +34 -7
  45. package/dist/page/client/terminal.js.map +1 -1
  46. package/dist/page/client/theme.d.ts +17 -0
  47. package/dist/page/client/theme.js +365 -0
  48. package/dist/page/client/theme.js.map +1 -0
  49. package/dist/page/client/transcript.js +196 -8
  50. package/dist/page/client/transcript.js.map +1 -1
  51. package/dist/page/client/usage.js +1 -11
  52. package/dist/page/client/usage.js.map +1 -1
  53. package/dist/page/style.js +745 -76
  54. package/dist/page/style.js.map +1 -1
  55. package/dist/page.js +94 -16
  56. package/dist/page.js.map +1 -1
  57. package/dist/prefs.d.ts +31 -0
  58. package/dist/prefs.js +13 -1
  59. package/dist/prefs.js.map +1 -1
  60. package/dist/remote-crypto.d.ts +78 -0
  61. package/dist/remote-crypto.js +216 -0
  62. package/dist/remote-crypto.js.map +1 -0
  63. package/dist/remote.d.ts +103 -0
  64. package/dist/remote.js +498 -0
  65. package/dist/remote.js.map +1 -0
  66. package/dist/server.d.ts +70 -0
  67. package/dist/server.js +1738 -73
  68. package/dist/server.js.map +1 -1
  69. package/dist/starters.d.ts +92 -0
  70. package/dist/starters.js +213 -0
  71. package/dist/starters.js.map +1 -0
  72. package/dist/suggest.d.ts +42 -0
  73. package/dist/suggest.js +215 -0
  74. package/dist/suggest.js.map +1 -0
  75. package/dist/terminal.d.ts +28 -2
  76. package/dist/terminal.js +45 -3
  77. package/dist/terminal.js.map +1 -1
  78. package/dist/theme-assets.d.ts +39 -0
  79. package/dist/theme-assets.js +109 -0
  80. package/dist/theme-assets.js.map +1 -0
  81. package/dist/theme-store.d.ts +50 -0
  82. package/dist/theme-store.js +124 -0
  83. package/dist/theme-store.js.map +1 -0
  84. package/dist/theme-tools.d.ts +25 -0
  85. package/dist/theme-tools.js +243 -0
  86. package/dist/theme-tools.js.map +1 -0
  87. package/dist/turn-shots.d.ts +43 -0
  88. package/dist/turn-shots.js +80 -0
  89. package/dist/turn-shots.js.map +1 -0
  90. package/dist/user-theme.d.ts +143 -0
  91. package/dist/user-theme.js +510 -0
  92. package/dist/user-theme.js.map +1 -0
  93. package/package.json +2 -2
@@ -7,6 +7,51 @@
7
7
  export const EVENTS = String.raw `
8
8
  // --- events ---------------------------------------------------------------
9
9
 
10
+ /**
11
+ * Streamed text, revealed at a steady pace rather than as it arrives.
12
+ *
13
+ * Models send text in pieces of very different sizes: measured through the
14
+ * official plan, GPT-5.6 Sol came as ~5 characters a piece, many to the same
15
+ * millisecond, and Claude Opus 5.5 as four pieces of ~260 characters some
16
+ * 280ms apart. Drawn as received, a reply lurched a sentence at a time. Each
17
+ * frame now shows a share of what is still waiting — a tenth of it, at least
18
+ * two characters — so a large piece unrolls over a few hundred milliseconds
19
+ * and a trickle is shown as it comes. Nothing is held back for long: the
20
+ * backlog is never more than a fraction of a second behind.
21
+ *
22
+ * A stream the pane has finished with (P.streaming cleared) still runs to the
23
+ * end of its text here; it only ever shows what it was sent.
24
+ */
25
+ const streamsRevealing = new Set();
26
+ let streamFrame = 0;
27
+
28
+ function revealStream(stream) {
29
+ streamsRevealing.add(stream);
30
+ if (!streamFrame) streamFrame = requestAnimationFrame(revealStreams);
31
+ }
32
+
33
+ function revealStreams() {
34
+ streamFrame = 0;
35
+ for (const stream of streamsRevealing) {
36
+ const waiting = stream.text.length - stream.shown;
37
+ if (waiting <= 0) {
38
+ streamsRevealing.delete(stream);
39
+ continue;
40
+ }
41
+ let next = Math.min(stream.text.length, stream.shown + Math.max(2, Math.ceil(waiting / 10)));
42
+ // Never between the halves of a surrogate pair: an emoji cut in two draws
43
+ // as a box for a frame.
44
+ const code = stream.text.charCodeAt(next - 1);
45
+ if (code >= 0xd800 && code <= 0xdbff && next < stream.text.length) next++;
46
+ stream.shown = next;
47
+ // The whole block again: markdown is not incremental, and a half fence
48
+ // rendered as literal text then rewritten looks worse than this.
49
+ stream.node.replaceChildren(renderMarkdown(stream.text.slice(0, next)));
50
+ }
51
+ follow();
52
+ if (streamsRevealing.size) streamFrame = requestAnimationFrame(revealStreams);
53
+ }
54
+
10
55
  const events = new EventSource(q("/events"));
11
56
  events.onerror = () => { ctxText.textContent = "offline"; };
12
57
  events.onmessage = (message) => {
@@ -29,6 +74,8 @@ events.onmessage = (message) => {
29
74
  // been written yet, so there is nothing at /attachment to ask for.
30
75
  const sent = sentPreviews.get(event.session) || [];
31
76
  sentPreviews.delete(event.session);
77
+ P.suggestion = null;
78
+ if (event.session === activeId) paintSuggestion();
32
79
  addUser(event.text || "", sent);
33
80
  startWorking(t("work.thinking"));
34
81
  const unshown = (event.attachments || []).filter(
@@ -44,15 +91,27 @@ events.onmessage = (message) => {
44
91
  break;
45
92
  }
46
93
 
94
+ // Background tasks ended and their results were just read in. When that
95
+ // is what started the turn — nobody typed anything — this is also the
96
+ // only sign the conversation is working again.
97
+ // A background task started, moved on or ended.
98
+ case "tasks": tasksChanged(event.session, event.tasks); break;
99
+ case "task_notification":
100
+ P.turn = null;
101
+ taskCard(event.tasks || []);
102
+ if (!P.busy) {
103
+ startWorking(t("work.thinking"));
104
+ setBusy(true);
105
+ }
106
+ break;
107
+
47
108
  case "text_delta":
48
109
  sealThinking();
49
110
  stopWorking();
50
- if (!P.streaming) { P.streaming = { text: "", node: el("div") }; assistantTurn().appendChild(P.streaming.node); }
111
+ if (!P.streaming) { P.streaming = { text: "", shown: 0, node: el("div") }; assistantTurn().appendChild(P.streaming.node); }
51
112
  P.streaming.text += event.text;
52
- // Re-render the whole block: markdown is not incremental, and a half
53
- // fence rendered as literal text then rewritten looks worse than this.
54
- P.streaming.node.replaceChildren(renderMarkdown(P.streaming.text));
55
- follow();
113
+ // Not drawn here: revealed at a steady pace by revealStreams.
114
+ revealStream(P.streaming);
56
115
  break;
57
116
 
58
117
  case "thinking_delta": {
@@ -87,7 +146,21 @@ events.onmessage = (message) => {
87
146
  renderQueued();
88
147
  break;
89
148
 
90
- case "assistant_message": P.streaming = null; break;
149
+ case "assistant_message": {
150
+ P.streaming = null;
151
+ const meta = event.message && event.message.meta;
152
+ if (meta && typeof meta.agent === "string") {
153
+ const session = meta.acpSession || {};
154
+ agentReply(P.turn, { name: meta.agent, session: session.id, cwd: session.cwd });
155
+ }
156
+ break;
157
+ }
158
+ // After the turn ended: under its last reply, not a new turn of its own.
159
+ case "turn_shots": {
160
+ const replies = P.node.querySelectorAll(":scope > .turn.assistant");
161
+ turnShots(replies[replies.length - 1], event.shots);
162
+ break;
163
+ }
91
164
  case "turn_start": dropPendingTools(); break;
92
165
  // A session named from its first prompt, or renamed: the tab and the
93
166
  // sidebar row both carry the name.
@@ -162,7 +235,14 @@ events.onmessage = (message) => {
162
235
  }
163
236
 
164
237
  case "permission_request": showAsk(event); break;
238
+ case "remote_pair_request": showPairing(event); break;
239
+ case "remote_pair_closed": closePairing(event.id); break;
165
240
  case "question_request": questionCard(event); break;
241
+ // A theme proposed by set_theme, and what became of it; then the theme in
242
+ // use, from this window or another.
243
+ case "theme_preview": themePreviewCard(event.preview); break;
244
+ case "theme_preview_closed": closeThemePreview(event.id, event.outcome); break;
245
+ case "theme": setThemeState(event.state); break;
166
246
  case "question_result": settleQuestion(event); break;
167
247
  // Sent once per (re)connect, after the replay: a card this page still
168
248
  // shows as open but the server no longer holds closed while we were away.
@@ -177,7 +257,10 @@ events.onmessage = (message) => {
177
257
  closeQuestion(event.id);
178
258
  break;
179
259
  case "permission_result":
180
- if (!event.allowed) addNotice("denied: " + (event.reason || ""), "err");
260
+ // Plan mode holding a step back is the mode working, not something
261
+ // going wrong: said plainly, and not in the colour of an error.
262
+ if (!event.allowed && event.plan) addNotice(t("plan.held"), "notice");
263
+ else if (!event.allowed) addNotice("denied: " + (event.reason || ""), "err");
181
264
  else if (event.remembered) addNotice("remembered: " + event.remembered);
182
265
  break;
183
266
 
@@ -200,6 +283,18 @@ events.onmessage = (message) => {
200
283
  void loadSessions();
201
284
  if (shownPanel === "nav-routines") void showRoutines();
202
285
  break;
286
+ // A worktree came or went: which session directories nest under which
287
+ // project is part of the project list.
288
+ case "projects":
289
+ void loadProjects();
290
+ break;
291
+ // A routine run started or ended, here or from the clock.
292
+ case "routines":
293
+ if (shownPanel === "nav-routines") void showRoutines();
294
+ break;
295
+ case "routine_activity":
296
+ if (shownPanel === "nav-routines") paintRoutineActivity(event);
297
+ break;
203
298
  case "error":
204
299
  P.streaming = null;
205
300
  // A 402 is not an error to print, it is a screen to open. What the
@@ -242,13 +337,28 @@ events.onmessage = (message) => {
242
337
  }
243
338
  case "idle":
244
339
  applyState(event.state);
245
- renderChanges(event.changes);
340
+ // The bar above the transcript is the tab on screen's; a turn ending in
341
+ // a background tab (a worktree session, a second conversation) must not
342
+ // put its files there.
343
+ if (!event.session || event.session === activeId) renderChanges(event.changes);
246
344
  void loadSessions();
247
345
  break;
248
346
  case "state": applyState(event.state); break;
347
+ case "suggestion":
348
+ P.suggestion = event.text;
349
+ if (event.session === activeId) paintSuggestion();
350
+ break;
351
+ case "suggestions":
352
+ if (!event.enabled) for (const entry of panes.values()) entry.suggestion = null;
353
+ paintSuggestion();
354
+ break;
355
+ // What a conversation is doing on the computer, for the live view.
356
+ case "computer": computerEvent(event.session, event.event); break;
357
+ // The project's starters, written in the background after the fixed three.
358
+ case "starters": startersArrived(event); break;
249
359
  case "connectors_changed":
250
360
  if (authorizing === event.connector) authorizing = null;
251
- offer = { connectors: event.connectors, plugins: event.plugins || [], skills: event.skills || [] };
361
+ offer = { connectors: event.connectors, plugins: event.plugins || [], skills: event.skills || [], agents: event.agents || offer.agents || [] };
252
362
  // The failure already said what went wrong; this only announces the
253
363
  // half that is worth celebrating.
254
364
  if (event.authorized) addNotice(t("market.authorized", { name: event.connector }));
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/page/client/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwR/B,CAAC"}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/page/client/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsY/B,CAAC"}
@@ -15,6 +15,7 @@ const explorer = document.getElementById("explorer");
15
15
  const explorerTitle = document.getElementById("explorer-title");
16
16
  const explorerTree = document.getElementById("explorer-tree");
17
17
  const explorerView = document.getElementById("explorer-view");
18
+ const explorerQuery = document.getElementById("explorer-q");
18
19
 
19
20
  /** The project folder on show, absolute; null while the pane has never opened. */
20
21
  let explorerCwd = null;
@@ -65,6 +66,8 @@ async function openExplorer(cwd, force) {
65
66
  explorerChildren = new Map();
66
67
  explorerSelected = null;
67
68
  explorerSource = false;
69
+ explorerQuery.value = "";
70
+ explorerHits = null;
68
71
  }
69
72
  explorerTitle.textContent = projectName(cwd);
70
73
  explorerTitle.title = cwd;
@@ -96,6 +99,7 @@ async function explorerLoad() {
96
99
  else explorerChildren.set(dir, body.entries || []);
97
100
  });
98
101
  paintExplorerTree();
102
+ if (explorerQuery.value.trim()) void explorerSearch();
99
103
  if (explorerSelected) await explorerShow(explorerSelected);
100
104
  else explorerView.replaceChildren(el("p", "muted explorer-hint", t("files.pick")));
101
105
  }
@@ -125,6 +129,7 @@ async function explorerToggleDir(dir) {
125
129
  }
126
130
 
127
131
  function paintExplorerTree() {
132
+ if (explorerHits) return paintExplorerHits();
128
133
  const scroll = explorerTree.scrollTop;
129
134
  explorerTree.replaceChildren();
130
135
  const root = explorerChildren.get("") || [];
@@ -285,6 +290,86 @@ function tabProjectButtons(id, cwd) {
285
290
  return [files, term];
286
291
  }
287
292
 
293
+ // --- searching the tree ------------------------------------------------------
294
+ // A name typed here is looked for through the whole project, not only the
295
+ // folders unfolded; while there is one, the matches stand in for the tree and
296
+ // clearing it brings the tree back exactly as it was.
297
+
298
+ /** The matches on show, or null while the tree is. */
299
+ let explorerHits = null;
300
+ let explorerSearchTimer = 0;
301
+ let explorerSearchSeq = 0;
302
+
303
+ async function explorerSearch() {
304
+ const query = explorerQuery.value.trim();
305
+ const seq = ++explorerSearchSeq;
306
+ if (!query) {
307
+ explorerHits = null;
308
+ return paintExplorerTree();
309
+ }
310
+ const cwd = explorerCwd;
311
+ const body = await getJson("/files/search?cwd=" + encodeURIComponent(cwd) + "&q=" + encodeURIComponent(query));
312
+ if (seq !== explorerSearchSeq || cwd !== explorerCwd) return;
313
+ explorerHits = body.error ? [] : (body.entries || []);
314
+ explorerTree.scrollTop = 0;
315
+ paintExplorerTree();
316
+ }
317
+
318
+ function paintExplorerHits() {
319
+ explorerTree.replaceChildren();
320
+ if (!explorerHits.length) return void explorerTree.appendChild(el("p", "muted explorer-hint", t("files.noMatch")));
321
+ for (const entry of explorerHits) {
322
+ const row = el("button", "explorer-row" + (entry.path === explorerSelected ? " on" : ""));
323
+ row.type = "button";
324
+ row.setAttribute("role", "treeitem");
325
+ row.title = entry.path;
326
+ row.appendChild(svgIcon(entry.dir ? "folder" : "file"));
327
+ row.appendChild(el("span", "explorer-name", entry.name));
328
+ const parent = entry.path.slice(0, Math.max(0, entry.path.length - entry.name.length - 1));
329
+ if (parent) row.appendChild(el("span", "explorer-dir", parent));
330
+ row.addEventListener("click", () => void (entry.dir ? explorerReveal(entry.path) : explorerShow(entry.path)));
331
+ explorerTree.appendChild(row);
332
+ }
333
+ }
334
+
335
+ /** A folder picked from the matches: back to the tree, unfolded down to it. */
336
+ async function explorerReveal(dir) {
337
+ const parts = dir.split("/");
338
+ const dirs = explorerDirs();
339
+ for (let i = 1; i <= parts.length; i++) {
340
+ const at = parts.slice(0, i).join("/");
341
+ dirs.add(at);
342
+ if (!explorerChildren.has(at)) {
343
+ const body = await explorerFetch(at);
344
+ if (body.error) { dirs.delete(at); break; }
345
+ explorerChildren.set(at, body.entries || []);
346
+ }
347
+ }
348
+ explorerQuery.value = "";
349
+ explorerHits = null;
350
+ explorerSearchSeq++;
351
+ paintExplorerTree();
352
+ const row = [...explorerTree.children].find((node) => node.title === dir);
353
+ if (row) row.scrollIntoView({ block: "nearest" });
354
+ }
355
+
356
+ explorerQuery.addEventListener("input", () => {
357
+ clearTimeout(explorerSearchTimer);
358
+ explorerSearchTimer = setTimeout(() => void explorerSearch(), 120);
359
+ });
360
+ explorerQuery.addEventListener("keydown", (event) => {
361
+ if (event.key === "Escape" && explorerQuery.value) {
362
+ event.preventDefault();
363
+ event.stopPropagation();
364
+ explorerQuery.value = "";
365
+ void explorerSearch();
366
+ } else if (event.key === "Enter" && explorerHits && explorerHits.length) {
367
+ event.preventDefault();
368
+ const first = explorerHits[0];
369
+ void (first.dir ? explorerReveal(first.path) : explorerShow(first.path));
370
+ }
371
+ });
372
+
288
373
  document.getElementById("explorer-close").addEventListener("click", closeExplorer);
289
374
  document.getElementById("explorer-refresh").addEventListener("click", () => void explorerLoad());
290
375
  document.getElementById("explorer-terminal").addEventListener("click", () => void openTerminalIn(explorerCwd, true));
@@ -398,7 +483,7 @@ let explorerTreeDragged;
398
483
  dragHandle(explorerSplit, {
399
484
  get axis() { return isPhone() ? "y" : "x"; },
400
485
  start: () => {
401
- const rect = explorerTree.getBoundingClientRect();
486
+ const rect = document.getElementById("explorer-side").getBoundingClientRect();
402
487
  return isPhone() ? rect.height : rect.width;
403
488
  },
404
489
  move: (dx, dy, start) => { explorerTreeDragged = explorerSetTree(start + (isPhone() ? dy : dx)); },
@@ -1 +1 @@
1
- {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/page/client/files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoZ9B,CAAC"}
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/page/client/files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAye9B,CAAC"}
@@ -5,15 +5,19 @@ import { STATE } from "./state.js";
5
5
  import { EVENTS } from "./events.js";
6
6
  import { DIALOGS } from "./dialogs.js";
7
7
  import { COMPOSER } from "./composer.js";
8
+ import { TASKS } from "./tasks.js";
8
9
  import { ARTIFACTS } from "./artifacts.js";
9
10
  import { ACCOUNT } from "./account.js";
10
11
  import { ROUTINES } from "./routines.js";
11
12
  import { USAGE } from "./usage.js";
12
13
  import { PANELS } from "./panels.js";
13
14
  import { BROWSER } from "./browser.js";
15
+ import { COMPUTER } from "./computer.js";
14
16
  import { FILES } from "./files.js";
15
17
  import { TERMINAL } from "./terminal.js";
16
18
  import { SHELL } from "./shell.js";
19
+ import { RIPPLE } from "./ripple.js";
20
+ import { THEME } from "./theme.js";
17
21
  /**
18
22
  * The client script, assembled.
19
23
  *
@@ -37,6 +41,7 @@ export const APP_SCRIPT = [
37
41
  EVENTS,
38
42
  DIALOGS,
39
43
  COMPOSER,
44
+ TASKS,
40
45
  ARTIFACTS,
41
46
  ACCOUNT,
42
47
  ROUTINES,
@@ -45,6 +50,9 @@ export const APP_SCRIPT = [
45
50
  FILES,
46
51
  TERMINAL,
47
52
  BROWSER,
53
+ COMPUTER,
48
54
  SHELL,
55
+ RIPPLE,
56
+ THEME,
49
57
  ].join("") + "\n";
50
58
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/page/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GACrB;IACA,IAAI;IACJ,OAAO;IACP,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,KAAK;IACL,QAAQ;IACR,OAAO;IACP,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/page/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GACrB;IACA,IAAI;IACJ,OAAO;IACP,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,KAAK;IACL,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,KAAK;IACL,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
@@ -168,7 +168,7 @@ async function refreshRoutineCount() {
168
168
  }
169
169
 
170
170
  /** Sidebar entries that open a view of their own rather than a conversation. */
171
- const PANEL_NAV = new Set(["nav-artifacts", "nav-sessions", "nav-models", "nav-routines", "nav-settings"]);
171
+ const PANEL_NAV = new Set(["nav-artifacts", "nav-sessions", "nav-usage", "nav-models", "nav-routines", "nav-settings"]);
172
172
  const pluginPages = new Map();
173
173
  async function refreshPluginPages() {
174
174
  const session = activeId;
@@ -384,7 +384,7 @@ async function showMemoryPage(id) {
384
384
  form.appendChild(mergeActions);
385
385
  form.onsubmit = async (event) => {
386
386
  event.preventDefault();
387
- if (!confirm(t("memory.mergeConfirm"))) return;
387
+ if (!(await askConfirm({ body: t("memory.mergeConfirm"), ok: t("memory.mergeApply") }))) return;
388
388
  apply.disabled = true;
389
389
  await act({ action: "merge", note: { summary: summary.value.trim(), body: body.value.trim(), tags: tags.value.split(/[,,\s]+/).filter(Boolean), type: proposal.draft.type || "" } });
390
390
  apply.disabled = false;
@@ -563,9 +563,9 @@ async function showMemoryPage(id) {
563
563
  row.appendChild(line);
564
564
  }
565
565
  const remove = el("button", "ghost", t("memory.delete")); remove.type = "button";
566
- remove.onclick = () => {
566
+ remove.onclick = async () => {
567
567
  const linked = links.in.length ? t("memory.confirmDeleteLinked", { count: links.in.length }) : "";
568
- if (confirm(linked + t("memory.confirmDelete"))) void act({ action: "delete", scope: note.scope, id: note.id, expectedHash: note.hash });
568
+ if (await askConfirm({ body: linked + t("memory.confirmDelete"), ok: t("memory.delete"), danger: true })) void act({ action: "delete", scope: note.scope, id: note.id, expectedHash: note.hash });
569
569
  };
570
570
  row.appendChild(editor(note, remove));
571
571
  list.appendChild(row);
@@ -588,7 +588,7 @@ async function showMemoryPage(id) {
588
588
  text.appendChild(el("div", "muted small", [new Date(backup.backedUpAt).toLocaleString(LOCALE), backup.reason ? t("memory.backupReason." + backup.reason) : "", scopeLabel, backup.id].filter(Boolean).join(" · ")));
589
589
  row.appendChild(text);
590
590
  const restore = el("button", "ghost", t("memory.restore")); restore.type = "button";
591
- restore.onclick = () => { if (confirm(t("memory.confirmRestore"))) void act({ action: "restore", file: backup.file }); };
591
+ restore.onclick = async () => { if (await askConfirm({ body: t("memory.confirmRestore"), ok: t("memory.restore") })) void act({ action: "restore", file: backup.file }); };
592
592
  row.appendChild(restore);
593
593
  backups.appendChild(row);
594
594
  }
@@ -599,12 +599,12 @@ async function showMemoryPage(id) {
599
599
  * Panels reached from a row inside another panel rather than from the sidebar.
600
600
  *
601
601
  * A full page, drawn where Models and Routines are drawn, but without a
602
- * permanent destination in the sidebar: usage is looked at now and then, and a
603
- * row in the sidebar for it would be a standing invitation to a page most
604
- * sessions have no reason to open. It keeps the row it came from lit, so the
605
- * sidebar still says where in the app this is.
602
+ * permanent destination in the sidebar. It keeps the row it came from lit, so
603
+ * the sidebar still says where in the app this is. Usage is the exception that
604
+ * has a row of its own on the rail as well as Settings', and lights that one
605
+ * whichever way it was reached.
606
606
  */
607
- const SUB_PANELS = { usage: "nav-settings", audit: "nav-settings", permissions: "nav-settings" };
607
+ const SUB_PANELS = { usage: "nav-usage", audit: "nav-settings", permissions: "nav-settings" };
608
608
 
609
609
  /** Which panel the panel view is currently showing, sub-panels included. */
610
610
  let shownPanel = null;
@@ -652,6 +652,10 @@ document.getElementById("nav-sessions").addEventListener("click", () => {
652
652
  setNav("nav-sessions");
653
653
  void showUsageSessions();
654
654
  });
655
+ document.getElementById("nav-usage").addEventListener("click", () => {
656
+ setNav("usage");
657
+ void showUsage();
658
+ });
655
659
  document.getElementById("nav-routines").addEventListener("click", () => {
656
660
  setNav("nav-routines");
657
661
  void showRoutines();
@@ -1 +1 @@
1
- {"version":3,"file":"panels.js","sourceRoot":"","sources":["../../../src/page/client/panels.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAipB/B,CAAC"}
1
+ {"version":3,"file":"panels.js","sourceRoot":"","sources":["../../../src/page/client/panels.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqpB/B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The field behind a new session: a grid of dots a click sends a shockwave
3
+ * through.
4
+ *
5
+ * Only while the empty state is on screen. The canvas sits under everything
6
+ * in #main and takes no pointer, so it can never eat a click meant for the
7
+ * composer or a starter; it only hears them go by. Nothing animates at rest —
8
+ * a frame is drawn when a wave is travelling or the pointer moves, and the
9
+ * loop stops once the last wave has died out, so an idle new tab costs nothing.
10
+ *
11
+ * One part of the client script; see ./index.ts for how the parts join.
12
+ */
13
+ export declare const RIPPLE: string;