@monster-orange/den 0.4.22 → 0.4.24

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.
@@ -0,0 +1,150 @@
1
+ /**
2
+ * A searchable picker (D30): a text field (role=combobox) over a listbox.
3
+ * Type to filter (the command palette's ranking, rankCommands), ↑/↓ to
4
+ * move, Enter (or a click) picks, Esc closes. What is picked is an id; what
5
+ * is shown is its name. One shared control, so a form never asks a person
6
+ * to type an id.
7
+ *
8
+ * comboHtml({ id, label, placeholder }) the markup (a <label>, the
9
+ * field, its list)
10
+ * wireCombo(root, id, { items, onPick }) makes it work; items() is read
11
+ * each time it opens, so it
12
+ * follows live data. Returns
13
+ * { value(), set(id), clear() }.
14
+ *
15
+ * An item: { id, label, sub?, keywords?, off? } (off: shown, not pickable).
16
+ */
17
+ import { rankCommands } from "./topbar.js";
18
+
19
+ const esc = (s) =>
20
+ String(s ?? "").replace(
21
+ /[&<>"']/g,
22
+ (c) =>
23
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[
24
+ c
25
+ ],
26
+ );
27
+
28
+ export function comboHtml({ id, label, placeholder = "" }) {
29
+ return (
30
+ `<span class="den-combo" data-combo="${esc(id)}">` +
31
+ `<label for="${esc(id)}">${esc(label)}</label>` +
32
+ `<span class="den-combo-box">` +
33
+ `<input type="text" id="${esc(id)}" role="combobox" aria-expanded="false" aria-controls="${esc(id)}-list" aria-autocomplete="list" autocomplete="off" spellcheck="false" placeholder="${esc(placeholder)}">` +
34
+ `<ul id="${esc(id)}-list" role="listbox" aria-label="${esc(label)}" class="den-combo-list" hidden></ul>` +
35
+ `</span></span>`
36
+ );
37
+ }
38
+
39
+ /** Rows ranked for `q` (every item when it is empty). */
40
+ export function comboMatches(items, q) {
41
+ const list = items.map((x) => ({
42
+ ...x,
43
+ keywords: [x.id, x.sub ?? "", ...(x.keywords ?? [])],
44
+ }));
45
+ return rankCommands(list, q, { limit: 50, emptyLimit: 50 });
46
+ }
47
+
48
+ export function wireCombo(root, id, { items, onPick }) {
49
+ const input = root.querySelector(`#${CSS.escape(id)}`);
50
+ const ul = root.querySelector(`#${CSS.escape(id)}-list`);
51
+ if (!input || !ul) return null;
52
+ let picked = "";
53
+ let shown = [];
54
+ let sel = 0;
55
+ const close = () => {
56
+ ul.hidden = true;
57
+ input.setAttribute("aria-expanded", "false");
58
+ input.removeAttribute("aria-activedescendant");
59
+ };
60
+ const draw = () => {
61
+ shown = comboMatches(
62
+ items(),
63
+ input.value === labelOf(picked) ? "" : input.value,
64
+ );
65
+ sel = Math.min(sel, Math.max(0, shown.length - 1));
66
+ ul.innerHTML = shown.length
67
+ ? shown
68
+ .map(
69
+ (x, i) =>
70
+ `<li role="option" id="${esc(id)}-o-${i}" data-i="${i}" aria-selected="${i === sel}"${x.off ? ' aria-disabled="true"' : ""}>` +
71
+ `<span class="den-combo-l">${esc(x.label)}</span>` +
72
+ (x.sub ? `<span class="den-combo-s">${esc(x.sub)}</span>` : "") +
73
+ `</li>`,
74
+ )
75
+ .join("")
76
+ : `<li class="den-combo-none" aria-disabled="true">No match</li>`;
77
+ ul.hidden = false;
78
+ input.setAttribute("aria-expanded", "true");
79
+ if (shown.length)
80
+ input.setAttribute("aria-activedescendant", `${id}-o-${sel}`);
81
+ ul.querySelector(`[data-i="${sel}"]`)?.scrollIntoView?.({
82
+ block: "nearest",
83
+ });
84
+ };
85
+ const labelOf = (v) => items().find((x) => x.id === v)?.label ?? "";
86
+ const pick = (x) => {
87
+ if (!x || x.off) return;
88
+ picked = x.id;
89
+ input.value = x.label;
90
+ input.dataset.value = x.id;
91
+ close();
92
+ onPick?.(x);
93
+ };
94
+ input.addEventListener("focus", () => {
95
+ sel = 0;
96
+ draw();
97
+ });
98
+ input.addEventListener("input", () => {
99
+ picked = "";
100
+ delete input.dataset.value;
101
+ sel = 0;
102
+ draw();
103
+ });
104
+ input.addEventListener("keydown", (e) => {
105
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
106
+ e.preventDefault();
107
+ if (ul.hidden) return draw();
108
+ const n = shown.length;
109
+ if (!n) return;
110
+ sel = (sel + (e.key === "ArrowDown" ? 1 : n - 1)) % n;
111
+ draw();
112
+ } else if (e.key === "Enter") {
113
+ if (ul.hidden) return;
114
+ e.preventDefault();
115
+ pick(shown[sel]);
116
+ } else if (e.key === "Escape") {
117
+ if (!ul.hidden) {
118
+ e.preventDefault();
119
+ e.stopPropagation();
120
+ close();
121
+ }
122
+ }
123
+ });
124
+ // mousedown, not click: the field keeps its focus (no blur close first).
125
+ ul.addEventListener("mousedown", (e) => {
126
+ const li = e.target.closest?.("[data-i]");
127
+ if (!li) return;
128
+ e.preventDefault();
129
+ pick(shown[Number(li.dataset.i)]);
130
+ });
131
+ input.addEventListener("blur", () => {
132
+ close();
133
+ // Leaving with a half-typed name: back to what was picked.
134
+ if (input.value !== labelOf(picked)) input.value = labelOf(picked);
135
+ });
136
+ return {
137
+ value: () => picked,
138
+ set: (v) => {
139
+ picked = v;
140
+ input.value = labelOf(v);
141
+ if (v) input.dataset.value = v;
142
+ else delete input.dataset.value;
143
+ },
144
+ clear: () => {
145
+ picked = "";
146
+ input.value = "";
147
+ delete input.dataset.value;
148
+ },
149
+ };
150
+ }
@@ -305,10 +305,15 @@ function githubBlock(s, t) {
305
305
 
306
306
  /**
307
307
  * D17: a Den with no owner yet that a rule lets someone claim. It leads the
308
- * page: Connect your GitHub account. "This is my computer" (with
309
- * web.allowed_github_org) goes to localhost and back, which only works in a
310
- * browser on the machine Den runs on, then starts the GitHub sign-in with
311
- * that proof; "Sign in as @login" (web.owner_github) works from anywhere.
308
+ * page: Connect your GitHub account.
309
+ *
310
+ * - "Sign in as @login" (web.owner_github) works from any browser: when
311
+ * it is there, it is the primary button.
312
+ * - The localhost trip (web.allowed_github_org) only works in a browser
313
+ * on the machine Den runs on, so its button says so ("On <host>
314
+ * itself: connect GitHub"), never a footnote (D29).
315
+ * - Only the org rule: the page says to open it on <host>, or to set
316
+ * web.owner_github to sign in from anywhere.
312
317
  */
313
318
  function claimBlock(s, t) {
314
319
  const g = s.github;
@@ -316,24 +321,31 @@ function claimBlock(s, t) {
316
321
  const host = s.machine || "";
317
322
  const back = location.origin + location.pathname + location.search;
318
323
  const here = g.attest ? `${g.attest}?return=${encodeURIComponent(back)}` : "";
324
+ const as = g.ownerGithub
325
+ ? `<button type="button" class="btn primary" id="den-gh-start">${esc(t("err.signin.claimAs", { login: g.ownerGithub }))}</button>`
326
+ : "";
327
+ const hereBtn = here
328
+ ? `<a class="btn${as ? "" : " primary"}" id="den-claim-here" href="${esc(here)}">${
329
+ host
330
+ ? withHost(t, "err.signin.claimHereOn", { host })
331
+ : esc(t("err.signin.claimHere"))
332
+ }</a>`
333
+ : "";
319
334
  return (
320
335
  `<div id="den-gh" class="den-claim">` +
321
336
  `<h2 class="den-h" id="den-claim-title">${esc(t("err.signin.claimTitle"))}</h2>` +
322
337
  `<p class="lead" id="den-claim-lead">${esc(t("err.signin.claimLead", { name: s.name || "Den" }))}</p>` +
323
338
  (g.org
324
- ? `<p id="den-claim-org">${withHost(t, "err.signin.claimOrg", { org: g.org, host })}</p>`
339
+ ? `<p id="den-claim-org">${withHost(
340
+ t,
341
+ g.ownerGithub ? "err.signin.claimOrg" : "err.signin.claimOrgOnly",
342
+ { org: g.org, host },
343
+ )}</p>`
325
344
  : "") +
326
345
  `<div class="acts" style="margin-top:0">` +
327
- (here
328
- ? `<a class="btn primary" id="den-claim-here" href="${esc(here)}">${esc(t("err.signin.claimHere"))}</a>`
329
- : "") +
330
- (g.ownerGithub
331
- ? `<button type="button" class="btn${here ? "" : " primary"}" id="den-gh-start">${esc(t("err.signin.claimAs", { login: g.ownerGithub }))}</button>`
332
- : "") +
346
+ as +
347
+ hereBtn +
333
348
  `</div><div id="den-gh-flow" aria-live="polite"></div>` +
334
- (here
335
- ? `<p class="den-gh-note" id="den-claim-note">${withHost(t, "err.signin.claimHereNote", { host })}</p>`
336
- : "") +
337
349
  `</div>`
338
350
  );
339
351
  }
@@ -311,9 +311,10 @@
311
311
  "err.signin.claimOrg":
312
312
  "Los miembros de la organización {org} de GitHub pueden reclamarlo, en un navegador en {host}.",
313
313
  "err.signin.claimHere": "Este es mi ordenador: conectar GitHub",
314
+ "err.signin.claimHereOn": "En {host}: conectar GitHub",
315
+ "err.signin.claimOrgOnly":
316
+ "Los miembros de la organización {org} de GitHub pueden reclamarlo desde un navegador en {host}. Abre esta página en {host}, o define web.owner_github para iniciar sesión desde cualquier sitio.",
314
317
  "err.signin.claimAs": "Iniciar sesión como @{login}",
315
- "err.signin.claimHereNote":
316
- "«Este es mi ordenador» solo funciona en un navegador en {host}. En otro sitio no abre nada.",
317
318
  "err.signin.claimChecking": "Comprobando que esto es {host}…",
318
319
  "err.signin.githubRefused":
319
320
  "@{login} no es el propietario de este {name}. Solo la cuenta del propietario puede usar Iniciar sesión con GitHub. Usa «Aprueba este ordenador» en su lugar.",
@@ -487,9 +488,50 @@
487
488
  "Su sesión rotó ({why}), pero no escribió handoff.md. Reanúdalo para que lo escriba, o empieza de cero.",
488
489
  "ag.rot.badHandoff":
489
490
  "Su sesión rotó ({why}), pero su handoff.md no está completo. Reanúdalo para arreglarlo, o empieza de cero.",
490
- "ag.relaunch": "Relanzar desde el traspaso",
491
- "ag.relaunchName": "Relanzar {id} desde el traspaso",
491
+ "ag.relaunch": "Relanzar ahora",
492
+ "ag.relaunchName": "Relanzar {id} ahora",
492
493
  "ag.relaunched": "{id} relanzado desde su traspaso",
494
+ "ag.rc.autoAsk":
495
+ "Toca rotar ({why}). Den le pide el traspaso cuando esté libre y luego lo relanza.",
496
+ "ag.rc.autoReady":
497
+ "Toca rotar ({why}). Su traspaso está listo: Den lo relanza cuando esté libre.",
498
+ "ag.rc.ready": "Toca rotar ({why}). Su traspaso está listo.",
499
+ "ag.rc.missing": "Toca rotar ({why}). Aún no tiene traspaso.",
500
+ "ag.rc.bad": "Toca rotar ({why}). Su traspaso no está completo.",
501
+ "ag.rc.fellBack":
502
+ "Toca rotar ({why}). No escribió el traspaso tras pedírselo 2 veces.",
503
+ "ag.rc.wanted":
504
+ "Toca rotar ({why}). Den lo relanza en cuanto escriba su traspaso.",
505
+ "ag.rc.write": "Escribir traspaso",
506
+ "ag.rc.writeName": "Pedir a {id} que escriba su traspaso",
507
+ "ag.rc.keep": "Mantener esta conversación",
508
+ "ag.rc.keepName": "Mantener la conversación de {id}",
509
+ "ag.rc.never": "No volver a preguntar para este agente",
510
+ "ag.rc.settings": "Ajustes",
511
+ "ag.rc.from":
512
+ "Rota tras {n} compactaciones ({src}); al tocar: {mode} ({src2})",
513
+ "ag.rc.fellBackBad":
514
+ "Toca rotar ({why}). Su traspaso sigue sin pasar la revisión tras pedírselo 2 veces.",
515
+ "ag.rc.from.one":
516
+ "Rota tras {n} compactación ({src}); al tocar: {mode} ({src2})",
517
+ "ag.rc.fromNever": "Nunca rota ({src}); al tocar: {mode} ({src2})",
518
+ "ag.rc.midTurn":
519
+ "{id} está a mitad de turno: pídelo de nuevo cuando termine",
520
+ "ag.rc.neverN": "nunca",
521
+ "ag.rc.mode.auto": "relanzar solo",
522
+ "ag.rc.mode.ask": "preguntar",
523
+ "ag.rc.mode.off": "nada",
524
+ "ag.rc.menu": "Ajustes de rotación",
525
+ "ag.rc.noState": "Aún no rota: cuenta desde su próximo inicio.",
526
+ "ag.rc.asked": "Se pidió a {id} su traspaso",
527
+ "ag.rc.askedRelaunch":
528
+ "Se pidió a {id} su traspaso. Den lo relanza en cuanto pase la revisión.",
529
+ "ag.rc.askedResumed":
530
+ "{id} reanudado para escribir su traspaso. Den lo relanza en cuanto pase la revisión.",
531
+ "ag.rc.kept": "Conversación de {id} mantenida: su cuenta vuelve a empezar",
532
+ "ag.rc.keptNever": "Conversación de {id} mantenida: ya no rotará",
533
+ "ag.rc.autoRelaunched":
534
+ "{id} relanzado desde su traspaso tras {why}. La conversación anterior {old} se conserva.",
493
535
  "ag.remove": "Quitar",
494
536
  "ag.removeName": "Quitar {id}",
495
537
  "ag.removed": "{id} quitado",
@@ -705,6 +747,7 @@
705
747
  "ver.pinned": "fijada {v}",
706
748
  "ver.usedLatest": "{n} ahora sigue la versión más reciente",
707
749
  "set.agents.personas": "Personas",
750
+ "set.agents.rotation": "Rotación",
708
751
  // The Agents tab (AG-4a-term, agents-page.js).
709
752
  "n.agents": "Agentes",
710
753
  "kind.agent": "Agente",
@@ -299,9 +299,10 @@
299
299
  "err.signin.claimOrg":
300
300
  "GitHub の {org} 組織のメンバーは、{host} 上のブラウザーでこれを登録できます。",
301
301
  "err.signin.claimHere": "これは自分のコンピューター: GitHub を接続",
302
+ "err.signin.claimHereOn": "{host} 上で: GitHub を接続",
303
+ "err.signin.claimOrgOnly":
304
+ "GitHub の {org} 組織のメンバーは、{host} 上のブラウザーからこれを登録できます。このページを {host} で開くか、どこからでもサインインできるよう web.owner_github を設定してください。",
302
305
  "err.signin.claimAs": "@{login} としてサインイン",
303
- "err.signin.claimHereNote":
304
- "「これは自分のコンピューター」は {host} 上のブラウザーでだけ使えます。ほかの場所では何も開きません。",
305
306
  "err.signin.claimChecking": "ここが {host} か確認しています…",
306
307
  "err.signin.githubRefused":
307
308
  "@{login} はこの {name} のオーナーではありません。GitHub でサインインを使えるのはオーナーのアカウントだけです。代わりに「このコンピューターを承認」を使ってください。",
@@ -473,9 +474,46 @@
473
474
  "セッションは交代しました({why})が、handoff.md がありません。再開して書かせるか、新しく始めてください。",
474
475
  "ag.rot.badHandoff":
475
476
  "セッションは交代しました({why})が、handoff.md が不完全です。再開して直すか、新しく始めてください。",
476
- "ag.relaunch": "引き継ぎから再起動",
477
- "ag.relaunchName": "{id} を引き継ぎから再起動",
477
+ "ag.relaunch": "今すぐ再起動",
478
+ "ag.relaunchName": "{id} を今すぐ再起動",
478
479
  "ag.relaunched": "{id} を引き継ぎから再起動しました",
480
+ "ag.rc.autoAsk":
481
+ "交代の時期です({why})。待機中になったら Den が引き継ぎを書かせ、再起動します。",
482
+ "ag.rc.autoReady":
483
+ "交代の時期です({why})。引き継ぎは準備済み: 待機中になったら Den が再起動します。",
484
+ "ag.rc.ready": "交代の時期です({why})。引き継ぎは準備済みです。",
485
+ "ag.rc.missing": "交代の時期です({why})。引き継ぎがまだありません。",
486
+ "ag.rc.bad": "交代の時期です({why})。引き継ぎが不完全です。",
487
+ "ag.rc.fellBack":
488
+ "交代の時期です({why})。2 回頼んでも引き継ぎを書きませんでした。",
489
+ "ag.rc.wanted":
490
+ "交代の時期です({why})。引き継ぎが書かれたら Den が再起動します。",
491
+ "ag.rc.write": "引き継ぎを書かせる",
492
+ "ag.rc.writeName": "{id} に引き継ぎを書かせる",
493
+ "ag.rc.keep": "この会話を続ける",
494
+ "ag.rc.keepName": "{id} の会話を続ける",
495
+ "ag.rc.never": "このエージェントには今後たずねない",
496
+ "ag.rc.settings": "設定",
497
+ "ag.rc.from": "圧縮 {n} 回で交代({src})。時期が来たら: {mode}({src2})",
498
+ "ag.rc.fellBackBad":
499
+ "交代の時期です({why})。2 回頼んでも引き継ぎが確認を通りません。",
500
+ "ag.rc.fromNever": "交代しません({src})。時期が来たら: {mode}({src2})",
501
+ "ag.rc.midTurn": "{id} は応答中です: ターンが終わってから頼んでください",
502
+ "ag.rc.neverN": "しない",
503
+ "ag.rc.mode.auto": "自動で再起動",
504
+ "ag.rc.mode.ask": "たずねる",
505
+ "ag.rc.mode.off": "何もしない",
506
+ "ag.rc.menu": "交代の設定",
507
+ "ag.rc.noState": "まだ交代の記録がありません: 次の起動から数えます。",
508
+ "ag.rc.asked": "{id} に引き継ぎを頼みました",
509
+ "ag.rc.askedRelaunch":
510
+ "{id} に引き継ぎを頼みました。確認が通ったら Den が再起動します。",
511
+ "ag.rc.askedResumed":
512
+ "引き継ぎを書かせるため {id} を再開しました。確認が通ったら Den が再起動します。",
513
+ "ag.rc.kept": "{id} の会話を続けます: 回数はやり直しです",
514
+ "ag.rc.keptNever": "{id} の会話を続けます: 今後は交代しません",
515
+ "ag.rc.autoRelaunched":
516
+ "{why}のあと {id} を引き継ぎから再起動しました。前の会話 {old} は残っています。",
479
517
  "ag.remove": "削除",
480
518
  "ag.removeName": "{id} を削除",
481
519
  "ag.removed": "{id} を削除しました",
@@ -691,6 +729,7 @@
691
729
  "ver.pinned": "{v} に固定",
692
730
  "ver.usedLatest": "{n} は最新バージョンに追従します",
693
731
  "set.agents.personas": "ペルソナ",
732
+ "set.agents.rotation": "交代",
694
733
  // The Agents tab (AG-4a-term, agents-page.js).
695
734
  "n.agents": "エージェント",
696
735
  "kind.agent": "エージェント",