kaizen-agent 1.42.2 → 1.43.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.
@@ -1,7 +1,7 @@
1
1
  import { store } from "./store.ts";
2
2
  import { load, post } from "./api.ts";
3
- import { render } from "./cards.ts";
4
- import { $, esc, ico, modal } from "./dom.ts";
3
+ import { newIdea, render } from "./cards.ts";
4
+ import { $, html, ico, modal } from "./dom.ts";
5
5
  import { openSources, srcOpen } from "./icebox.ts";
6
6
  import { toast } from "./toast.ts";
7
7
  import { view } from "./views.ts";
@@ -20,13 +20,13 @@ export const short = (l) => l.split(/[\\/]/).pop() || l;
20
20
  // A project's mark in every picker: its own logo when /state found one, else its initial.
21
21
  // A logo the browser cannot draw falls back to the initial too.
22
22
  export const plogo = (dir, label = "") => {
23
- const ini = `<span class="plogo" aria-hidden="true">${esc((short(label) || "?").charAt(0).toUpperCase())}</span>`;
23
+ const ini = html`<span class="plogo" aria-hidden="true">${(short(label) || "?").charAt(0).toUpperCase()}</span>`;
24
24
  return store.S?.projects?.some((p) => p.dir === dir && p.logo)
25
- ? `<img class="plogo" src="/project-logo?dir=${encodeURIComponent(dir)}" alt="" loading="lazy" onerror="this.outerHTML=this.dataset.f" data-f="${esc(ini)}">`
25
+ ? html`<img class="plogo" src="/project-logo?dir=${encodeURIComponent(dir)}" alt="" loading="lazy" onerror="this.outerHTML=this.dataset.f" data-f="${String(ini)}">`
26
26
  : ini;
27
27
  };
28
28
  // A picker row: the mark, then the folder name; the full path in the tooltip.
29
- const prow = (dir, label) => `${plogo(dir, label)}<span>${esc(short(label))}</span>`;
29
+ const prow = (dir, label) => html`${plogo(dir, label)}<span>${short(label)}</span>`;
30
30
  // The project picker: a button shaped like a select that opens a search field over the list.
31
31
  export function list() {
32
32
  const q = $("projq").value.trim().toLowerCase();
@@ -34,8 +34,7 @@ export function list() {
34
34
  $("projs").innerHTML =
35
35
  store.PL.map((p, i) =>
36
36
  p[0].toLowerCase().includes(q)
37
- ? (p[2] && !g++ ? `<div class="grp">In your projects folder</div>` : "") +
38
- `<button role="option" data-i="${i}" title="${esc(p[0])}">${p[1] === "all" ? `<span class="plogo" aria-hidden="true">${ico("board")}</span><span>${esc(p[0])}</span>` : p[1] === "__new" || p[1] === "__find" || p[1] === "__clone" ? `<span class="plogo" aria-hidden="true">${ico("plus")}</span><span>${esc(p[0])}</span>` : prow(p[1], p[0])}</button>`
37
+ ? html`${p[2] && !g++ ? html`<div class="grp">In your projects folder</div>` : ""}<button role="option" data-i="${i}" title="${p[0]}">${p[1] === "all" ? html`<span class="plogo" aria-hidden="true">${ico("board")}</span><span>${p[0]}</span>` : p[1] === "__new" || p[1] === "__find" || p[1] === "__clone" ? html`<span class="plogo" aria-hidden="true">${ico("plus")}</span><span>${p[0]}</span>` : prow(p[1], p[0])}</button>`
39
38
  : "",
40
39
  ).join("") || `<div class="grp">No match</div>`;
41
40
  const f = $("projs").querySelector("button");
@@ -175,7 +174,7 @@ export function ideaCombo(f) {
175
174
  .filter((p) => p.label.toLowerCase().includes(t))
176
175
  .map(
177
176
  (p) =>
178
- `<button type="button" role="option" data-d="${esc(p.dir)}" title="${esc(p.label)}" class="${p.dir === newDir ? "on" : ""}">${prow(p.dir, p.label)}</button>`,
177
+ html`<button type="button" role="option" data-d="${p.dir}" title="${p.label}" class="${p.dir === newDir ? "on" : ""}">${prow(p.dir, p.label)}</button>`,
179
178
  )
180
179
  .join("") || `<div class="grp">No match</div>`;
181
180
  box.querySelectorAll("button").forEach(
@@ -231,7 +230,7 @@ export function showList() {
231
230
  $("shows").innerHTML =
232
231
  SP.map((p, i) =>
233
232
  p.label.toLowerCase().includes(q)
234
- ? `<button type="button" role="menuitemcheckbox" data-i="${i}" aria-checked="${store.shown.has(p.dir)}" title="${esc(p.label)}">${prow(p.dir, p.label)}<svg class="ck"><use href="#i-check"/></svg></button>`
233
+ ? html`<button type="button" role="menuitemcheckbox" data-i="${i}" aria-checked="${String(store.shown.has(p.dir))}" title="${p.label}">${prow(p.dir, p.label)}<svg class="ck"><use href="#i-check"/></svg></button>`
235
234
  : "",
236
235
  ).join("") || `<div class="grp">No match</div>`;
237
236
  const n = SP.filter((p) => store.shown.has(p.dir)).length;
@@ -313,9 +312,5 @@ export function initProjects() {
313
312
  $("show").setAttribute("aria-expanded", false);
314
313
  }
315
314
  });
316
- $("newtop").onclick = () => {
317
- store.mode = "new";
318
- render();
319
- $("board").scrollTo(0, 0);
320
- };
315
+ $("newtop").onclick = newIdea;
321
316
  }
@@ -2,7 +2,7 @@ import { store } from "./store.ts";
2
2
  import { termStop } from "./agent-term.ts";
3
3
  import { load, post } from "./api.ts";
4
4
  import { stopDictation } from "./dictation.ts";
5
- import { $, esc, SKEL } from "./dom.ts";
5
+ import { $, html, SKEL } from "./dom.ts";
6
6
  import { close } from "./panel.ts";
7
7
  import { askNotify, toast } from "./toast.ts";
8
8
  import { updBar } from "./update.ts";
@@ -51,55 +51,51 @@ export async function openSettings() {
51
51
  const ctl = (id, vals, on) => {
52
52
  // The preset is a segmented control over the tiers; custom, which only an edit to another row makes, lights none.
53
53
  if (id === "s-preset")
54
- return `<span class="bar" id="${id}" role="group" aria-label="Preset">${vals
54
+ return html`<span class="bar" id="${id}" role="group" aria-label="Preset">${vals
55
55
  .filter((v) => v !== "custom")
56
- .map((v) => `<button id="${id}-${v}" data-v="${v}" class="${v === on ? "on" : ""}" aria-pressed="${v === on}">${v}</button>`)
57
- .join("")}</span>`;
56
+ .map((v) => html`<button id="${id}-${v}" data-v="${v}" class="${v === on ? "on" : ""}" aria-pressed="${String(v === on)}">${v}</button>`)}</span>`;
58
57
  if (vals.length === 2 && vals.includes("true") && vals.includes("false")) {
59
58
  const yes = on === "true";
60
- return `<button class="chk${yes ? " on" : ""}" id="${id}" data-v="${yes ? "false" : "true"}" role="switch" aria-checked="${yes}" title="${yes ? "On" : "Off"}"></button>`;
59
+ return html`<button class="chk${yes ? " on" : ""}" id="${id}" data-v="${yes ? "false" : "true"}" role="switch" aria-checked="${String(yes)}" title="${yes ? "On" : "Off"}"></button>`;
61
60
  }
62
61
  if (vals.every((v) => /^\d+$/.test(v)))
63
- return `<span class="bar" id="${id}" role="group">${vals.map((v) => `<button data-v="${v}" class="${v === on ? "on" : ""}" aria-pressed="${v === on}">${v}</button>`).join("")}</span>`;
64
- return `<select class="sel" id="${id}">${vals.map((v) => `<option value="${esc(v)}"${v === on ? " selected" : ""}>${v === "" ? "—" : esc(v)}</option>`).join("")}</select>`;
62
+ return html`<span class="bar" id="${id}" role="group">${vals.map((v) => html`<button data-v="${v}" class="${v === on ? "on" : ""}" aria-pressed="${String(v === on)}">${v}</button>`)}</span>`;
63
+ return html`<select class="sel" id="${id}">${vals.map((v) => html`<option value="${v}"${v === on ? " selected" : ""}>${v === "" ? "—" : v}</option>`)}</select>`;
65
64
  };
66
65
  const row = (label, control, hint, id, q) =>
67
- `<div class="srow" data-q="${esc(q.toLowerCase())}"><label for="${id}">${esc(label)}</label>${control}${hint ? `<p class="hint">${hint}</p>` : ""}</div>`;
66
+ html`<div class="srow" data-q="${q.toLowerCase()}"><label for="${id}">${label}</label>${control}${hint ? html`<p class="hint">${hint}</p>` : ""}</div>`;
68
67
  const hadFocus = document.activeElement?.id === "sq";
69
68
  const np = typeof Notification === "undefined" ? null : Notification.permission;
70
69
  const ntHelp = np ? (np === "denied" ? "Blocked by this browser" : "") : "Not supported in this browser";
71
- $("pbody").innerHTML =
72
- `<div class="sq"><input class="sel" id="sq" type="search" placeholder="Search settings" aria-label="Search settings" spellcheck="false" autocomplete="off" value="${esc(store.setQ)}"></div>
70
+ $("pbody").innerHTML = html`<div class="sq"><input class="sel" id="sq" type="search" placeholder="Search settings" aria-label="Search settings" spellcheck="false" autocomplete="off" value="${store.setQ}"></div>
73
71
  ${
74
72
  cfg
75
73
  ? groups(cfg.rows)
76
74
  .map(
77
75
  ([g, rows]) =>
78
- `<h3 class="sg">${esc(g)}</h3><div class="sgrp">${rows
76
+ html`<h3 class="sg">${g}</h3><div class="sgrp">${rows
79
77
  .map((r) => {
80
78
  const id = "s-" + r.key.replace(/\./g, "-");
81
79
  return row(
82
80
  r.label,
83
81
  r.text
84
82
  ? r.folder
85
- ? `<span class="fpick"><input class="sel" id="${id}" value="${esc(r.value)}" placeholder="not set" spellcheck="false"><button class="btn" id="${id}-pick" type="button">Browse…</button></span>`
86
- : `<input class="sel" id="${id}" value="${esc(r.value)}" placeholder="not set" spellcheck="false">`
83
+ ? html`<span class="fpick"><input class="sel" id="${id}" value="${r.value}" placeholder="not set" spellcheck="false"><button class="btn" id="${id}-pick" type="button">Browse…</button></span>`
84
+ : html`<input class="sel" id="${id}" value="${r.value}" placeholder="not set" spellcheck="false">`
87
85
  : ctl(id, r.values, r.value),
88
- esc(r.help),
86
+ r.help,
89
87
  id,
90
88
  [r.label, r.help, r.key, g, ...r.values].join(" "),
91
89
  );
92
- })
93
- .join("")}</div>`,
90
+ })}</div>`,
94
91
  )
95
- .join("")
96
- : `<p class="snone">Settings could not be read. Run <code>kaizen settings</code>.</p>`
92
+ : html`<p class="snone">Settings could not be read. Run <code>kaizen settings</code>.</p>`
97
93
  }
98
- <h3 class="sg">Google</h3><div class="sgrp">${row("OAuth client id", `<input class="sel" id="gg-id" placeholder="${gg.hasClient ? "saved; type to replace" : "from Google Cloud, Desktop app"}" spellcheck="false" autocomplete="off">`, "", "gg-id", "google oauth client id sheets write back")}
99
- ${row("OAuth client secret", `<span class="fpick"><input class="sel" id="gg-secret" type="password" placeholder="${gg.hasClient ? "saved; type to replace" : ""}" spellcheck="false" autocomplete="off"><button class="btn" id="gg-save" type="button">Save</button></span>`, "", "gg-secret", "google oauth client secret")}
100
- ${row("Google account", `<button class="btn${gg.connected ? "" : " pri"}" id="gg-conn" type="button"${gg.hasClient || gg.connected ? "" : " disabled"}>${gg.connected ? "Disconnect" : "Connect"}</button>`, "", "gg-conn", "google account connect sheets write back")}</div>
94
+ <h3 class="sg">Google</h3><div class="sgrp">${row("OAuth client id", html`<input class="sel" id="gg-id" placeholder="${gg.hasClient ? "saved; type to replace" : "from Google Cloud, Desktop app"}" spellcheck="false" autocomplete="off">`, "", "gg-id", "google oauth client id sheets write back")}
95
+ ${row("OAuth client secret", html`<span class="fpick"><input class="sel" id="gg-secret" type="password" placeholder="${gg.hasClient ? "saved; type to replace" : ""}" spellcheck="false" autocomplete="off"><button class="btn" id="gg-save" type="button">Save</button></span>`, "", "gg-secret", "google oauth client secret")}
96
+ ${row("Google account", html`<button class="btn${gg.connected ? "" : " pri"}" id="gg-conn" type="button"${gg.hasClient || gg.connected ? "" : " disabled"}>${gg.connected ? "Disconnect" : "Connect"}</button>`, "", "gg-conn", "google account connect sheets write back")}</div>
101
97
  <h3 class="sg">Browser</h3><div class="sgrp">${row("Theme", ctl("th", ["system", "light", "dark"], cur), "", "th", "theme light dark system browser")}
102
- ${row("Notifications", `<button class="chk${np === "granted" ? " on" : ""}" id="nt" role="switch" aria-checked="${np === "granted"}"${np && np !== "denied" ? "" : " disabled"}></button>`, ntHelp, "nt", "notifications " + ntHelp + " browser")}</div>
98
+ ${row("Notifications", html`<button class="chk${np === "granted" ? " on" : ""}" id="nt" role="switch" aria-checked="${String(np === "granted")}"${np && np !== "denied" ? "" : " disabled"}></button>`, ntHelp, "nt", "notifications " + ntHelp + " browser")}</div>
103
99
  <p class="snone" id="snone" hidden></p>`;
104
100
  // Hides rows that do not match, then any group (and its chip) left with none.
105
101
  updBar();
@@ -14,6 +14,8 @@ export const store = {
14
14
  lastTab: "request",
15
15
  runDoc: null,
16
16
  clearMenu: false,
17
+ // The Ideas column's severity menu, on its Backlog tab.
18
+ sevMenu: false,
17
19
  wantTerm: null,
18
20
  // The settings search survives the re-render each save does; the gear button starts it empty.
19
21
  setQ: "",
@@ -1,5 +1,5 @@
1
1
  import { post } from "./api.ts";
2
- import { $, esc } from "./dom.ts";
2
+ import { $, html } from "./dom.ts";
3
3
  import { openSettings } from "./settings.ts";
4
4
  import { view } from "./views.ts";
5
5
 
@@ -21,16 +21,16 @@ export function updBar() {
21
21
  const d = document.createElement("div");
22
22
  d.className = `updbar${upd ? "" : " quiet"}`;
23
23
  if (!upd) {
24
- d.innerHTML = `<span id="upd-msg" aria-live="polite">${ver ? `kaizen ${esc(ver.version)}` : "kaizen"}</span><button class="btn" id="upd-chk">Check for updates</button>`;
24
+ d.innerHTML = html`<span id="upd-msg" aria-live="polite">${ver ? `kaizen ${ver.version}` : "kaizen"}</span><button class="btn" id="upd-chk">Check for updates</button>`;
25
25
  } else {
26
26
  // What's new: each newer release's one-line title, shown on hover, focus or click.
27
- const notes = (upd.notes || []).map((n) => `<li><b>${esc(n.v)}</b> ${esc(n.text)}</li>`).join("");
28
- const news = notes
29
- ? `<span class="updnew"><button class="btn" aria-describedby="upd-notes">What's new</button><span class="updpop" id="upd-notes" role="tooltip"><ul>${notes}</ul><a href="https://github.com/hfadhlullah/kaizen/releases" target="_blank" rel="noopener">All release notes</a></span></span>`
27
+ const notes = (upd.notes || []).map((n) => html`<li><b>${n.v}</b> ${n.text}</li>`);
28
+ const news = notes.length
29
+ ? html`<span class="updnew"><button class="btn" aria-describedby="upd-notes">What's new</button><span class="updpop" id="upd-notes" role="tooltip"><ul>${notes}</ul><a href="https://github.com/hfadhlullah/kaizen/releases" target="_blank" rel="noopener">All release notes</a></span></span>`
30
30
  : "";
31
- d.innerHTML = `<span>kaizen <b>${esc(upd.latest)}</b> is available. You are running ${esc(upd.version)}.</span><span class="updact">${news}<button class="btn" id="upd-chk">Check for updates</button><button class="btn pri" id="upd-go">Update kaizen</button></span>`;
31
+ d.innerHTML = html`<span>kaizen <b>${upd.latest}</b> is available. You are running ${upd.version}.</span><span class="updact">${news}<button class="btn" id="upd-chk">Check for updates</button><button class="btn pri" id="upd-go">Update kaizen</button></span>`;
32
32
  }
33
- if (ver && ver.failed) d.insertAdjacentHTML("afterbegin", `<span class="updfail" role="alert">Last update failed: ${esc(ver.failed)}</span>`);
33
+ if (ver && ver.failed) d.insertAdjacentHTML("afterbegin", html`<span class="updfail" role="alert">Last update failed: ${ver.failed}</span>`);
34
34
  $("pbody").prepend(d);
35
35
  const check = $("upd-chk");
36
36
  check.onclick = async () => {
package/web/board.html CHANGED
@@ -144,6 +144,18 @@ section h2{margin:0;font-size:13px;color:var(--ink);font-weight:500;display:flex
144
144
  section h2 i{font-style:normal;font-weight:400;font-size:12px;color:var(--dim)}
145
145
  section h2 i:empty{display:none}
146
146
  section h2 .ib{margin-left:auto}
147
+ /* Ideas column: its title is two icon tabs, Ideas | Backlog; on Backlog a severity filter icon */
148
+ .itabs{display:flex;align-items:center;gap:2px}
149
+ .itabs [role=tab]{min-height:26px;padding:0 8px;border:0;border-radius:13px;background:none;color:var(--dim);font:inherit;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px}
150
+ .itabs [role=tab] svg{width:14px;height:14px;stroke-width:1.7}
151
+ .itabs [role=tab][aria-selected=true]{color:var(--ink);font-weight:500;background:color-mix(in srgb,var(--dim) 14%,transparent)}
152
+ .itabs i{font-style:normal}
153
+ .itabs button:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
154
+ section h2 .sevf{margin-left:auto}section h2 .sevf~.ib{margin-left:0}
155
+ section h2 .sevf.on{color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent)}
156
+ .sev-pop{top:30px;right:0;min-width:180px}
157
+ .sev-pop button i{font-style:normal;color:var(--dim);margin-left:auto;background:none;padding:0}
158
+ .sev-pop button[aria-checked=true]{color:var(--accent)}
147
159
  ul{list-style:none;margin:0;padding:4px 0 0;display:flex;flex-direction:column;gap:6px}
148
160
 
149
161
  /* card, Linear grammar in our palette