kaizen-agent 1.34.11 → 1.35.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
3
  import { render } from "./cards.ts";
4
- import { $, esc } from "./dom.ts";
4
+ import { $, esc, ico } 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";
@@ -17,6 +17,16 @@ export function keep(d) {
17
17
  }
18
18
  // A project shows by its folder name alone; the full path stays in the tooltip.
19
19
  export const short = (l) => l.split(/[\\/]/).pop() || l;
20
+ // A project's mark in every picker: its own logo when /state found one, else its initial.
21
+ // A logo the browser cannot draw falls back to the initial too.
22
+ export const plogo = (dir, label = "") => {
23
+ const ini = `<span class="plogo" aria-hidden="true">${esc((short(label) || "?").charAt(0).toUpperCase())}</span>`;
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)}">`
26
+ : ini;
27
+ };
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>`;
20
30
  // The project picker: a button shaped like a select that opens a search field over the list.
21
31
  export function list() {
22
32
  const q = $("projq").value.trim().toLowerCase();
@@ -25,7 +35,7 @@ export function list() {
25
35
  store.PL.map((p, i) =>
26
36
  p[0].toLowerCase().includes(q)
27
37
  ? (p[2] && !g++ ? `<div class="grp">In your projects folder</div>` : "") +
28
- `<button role="option" data-i="${i}" title="${esc(p[0])}">${esc(short(p[0]))}</button>`
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] === "__find" ? `<span class="plogo" aria-hidden="true">${ico("plus")}</span><span>${esc(p[0])}</span>` : prow(p[1], p[0])}</button>`
29
39
  : "",
30
40
  ).join("") || `<div class="grp">No match</div>`;
31
41
  const f = $("projs").querySelector("button");
@@ -71,7 +81,8 @@ export function ideaCombo(f) {
71
81
  box = f.querySelector("#nds");
72
82
  if (!ps.some((p) => p.dir === newDir)) newDir = ps[0]?.dir ?? null;
73
83
  const name = () => {
74
- btn.textContent = short((ps.find((p) => p.dir === newDir) || { label: "Choose project" }).label);
84
+ const p = ps.find((p) => p.dir === newDir);
85
+ btn.innerHTML = p ? prow(p.dir, p.label) : "Choose project";
75
86
  };
76
87
  const draw = () => {
77
88
  const t = q.value.trim().toLowerCase();
@@ -80,7 +91,7 @@ export function ideaCombo(f) {
80
91
  .filter((p) => p.label.toLowerCase().includes(t))
81
92
  .map(
82
93
  (p) =>
83
- `<button type="button" role="option" data-d="${esc(p.dir)}" title="${esc(p.label)}" class="${p.dir === newDir ? "on" : ""}">${esc(short(p.label))}</button>`,
94
+ `<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>`,
84
95
  )
85
96
  .join("") || `<div class="grp">No match</div>`;
86
97
  box.querySelectorAll("button").forEach(
@@ -136,9 +147,11 @@ export function showList() {
136
147
  $("shows").innerHTML =
137
148
  SP.map((p, i) =>
138
149
  p.label.toLowerCase().includes(q)
139
- ? `<label title="${esc(p.label)}"><input type="checkbox" data-i="${i}" ${store.shown.has(p.dir) ? "checked" : ""}>${esc(short(p.label))}</label>`
150
+ ? `<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>`
140
151
  : "",
141
152
  ).join("") || `<div class="grp">No match</div>`;
153
+ const n = SP.filter((p) => store.shown.has(p.dir)).length;
154
+ $("showcount").textContent = n ? `${n} of ${SP.length} shown` : `All ${SP.length} shown`;
142
155
  }
143
156
  export function setShown() {
144
157
  try {
@@ -196,12 +209,13 @@ export function initProjects() {
196
209
  $("show").focus();
197
210
  }
198
211
  };
199
- // render() redraws the list; the ticked box keeps keyboard focus.
200
- $("shows").onchange = (e) => {
201
- const i = e.target.dataset.i,
212
+ // render() redraws the list; the toggled row keeps keyboard focus.
213
+ $("shows").onclick = (e) => {
214
+ const b = e.target.closest("button[data-i]"),
215
+ i = b?.dataset.i,
202
216
  d = SP[i]?.dir;
203
217
  if (!d) return;
204
- e.target.checked ? store.shown.add(d) : store.shown.delete(d);
218
+ store.shown.has(d) ? store.shown.delete(d) : store.shown.add(d);
205
219
  setShown();
206
220
  $("shows").querySelector(`[data-i="${i}"]`)?.focus();
207
221
  };
package/web/board.html CHANGED
@@ -87,7 +87,18 @@ select.sel,button.sel{cursor:pointer;appearance:none;padding-right:30px;max-widt
87
87
  #projs,#nds{display:flex;flex-direction:column;max-height:300px;overflow-y:auto}
88
88
  #projs button,#nds button{display:block;flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
89
89
  #projs button.on,#nds button.on{background:var(--hover)}
90
- #shows{display:flex;flex-direction:column;max-height:300px;overflow-y:auto}#shows label{display:flex;gap:8px;align-items:center;padding:6px 10px;font-size:13px;border-radius:var(--r-s);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#shows label:hover{background:var(--hover)}#shows .grp{padding:8px 10px 4px;font-size:11px;color:var(--dim)}
90
+ #shows{display:flex;flex-direction:column;max-height:320px;overflow-y:auto;gap:1px}
91
+ #shows button,#projs button,#nds button,.pick-list button{display:flex;align-items:center;gap:9px;flex:none;min-width:0;white-space:nowrap}
92
+ #shows button>span:not([class]),#projs button>span:not([class]),#nds button>span:not([class]),.pick-list button>span:not([class]){flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
93
+ #shows .ck{width:14px;height:14px;color:var(--accent);visibility:hidden}#shows button[aria-checked=true] .ck{visibility:visible}#shows button[aria-checked=true]{color:var(--ink);font-weight:500}
94
+ #shows .grp{padding:8px 10px 4px;font-size:11px;color:var(--dim)}
95
+ #showpop{padding:6px}#showpop .pop-ft{display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--line);margin-top:4px;padding:6px 4px 0;font-size:12px;color:var(--dim)}#showpop .pop-ft button{padding:4px 8px;font-size:12px;color:var(--accent)}
96
+ .plogo{width:18px;height:18px;border-radius:5px;flex:none;object-fit:contain;display:inline-grid;place-items:center;font-size:10px;font-weight:600;color:var(--dim);background:var(--hover);overflow:hidden}
97
+ img.plogo{background:none}.ice-meta .plogo{width:14px;height:14px;font-size:9px;border-radius:4px}
98
+ #proj,#show,#ndc>.sel{display:inline-flex;align-items:center;gap:8px}#proj>span:last-child,#ndc>.sel>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis}
99
+ .pick-list{display:flex;flex-direction:column;max-height:300px;overflow-y:auto}.pick-list .grp{padding:8px 10px 4px;font-size:11px;color:var(--dim);display:flex;align-items:center;gap:6px}.pick-list .grp svg{width:12px;height:12px}.pick-list button.on{background:var(--hover);color:var(--ink);font-weight:500}.pick-list button.sub{padding-left:28px}
100
+ .ice-combo>.sel{display:inline-flex;align-items:center;gap:7px;max-width:220px;height:30px;font-size:12px}.ice-combo>.sel>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ice-combo>.sel>svg{width:14px;height:14px;flex:none}.ice-combo>.sel>svg:last-child{color:var(--dim)}.ice-combo .menu-pop{width:280px}
101
+ .ice-combo .fav,.pick-list .fav{width:15px;height:15px;justify-content:center}.ice-combo .fav svg,.pick-list .fav svg,.ice-combo .fav img,.pick-list .fav img{width:14px;height:14px}.pick-list button>span:not([class]),.ice-combo>.sel>span:not([class]){text-align:left}
91
102
  #projs .grp,#nds .grp{padding:8px 10px 4px;font-size:11px;color:var(--dim)}
92
103
  .sp{flex:1}
93
104
  .ib{width:34px;height:34px;display:grid;place-items:center;color:var(--dim);border-radius:var(--r-s);transition:background .12s,color .12s}
@@ -190,7 +201,8 @@ section h2 .clear{margin-left:auto;min-height:22px;padding:0 8px}
190
201
  .inl textarea,.notes textarea{min-height:72px;resize:vertical}
191
202
  .notes textarea{min-height:160px}
192
203
  .inl input::placeholder,.inl textarea::placeholder,.notes textarea::placeholder{color:var(--dim)}
193
- .inl .row,.notes .row{display:flex;gap:6px;justify-content:flex-end}
204
+ .inl .row,.notes .row{display:flex;gap:6px;justify-content:flex-end;align-items:center}
205
+ .inl .row .mic{margin-right:0}.inl .row #newpick{margin-right:auto}.inl .row .btn{white-space:nowrap}.inl .att{font-size:12px;color:var(--dim)}.inl .att{font-size:12px;color:var(--dim)}.inl .att:empty{display:none}
194
206
  .inl .run-actions{display:grid;gap:8px}.inl .run-actions .row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.inl .run-actions .pri{grid-column:1/-1}
195
207
  .notes{display:flex;flex-direction:column;gap:8px}
196
208
  #pbody img{max-width:100%;border-radius:var(--r-s)}
@@ -213,7 +225,7 @@ aside.wide{width:100%;border-left:0}#pbody:has(.term){display:flex;flex-directio
213
225
  .ftr span+span,.ftr a+a{margin-left:14px}.ftr .strip{font-size:12px}.ftr .strip .st{display:inline-grid}
214
226
  /* The footer is out of the eye's path, so the waiting alert is filled, not just outlined. */
215
227
  .ftr .strip .attn{color:var(--ink);background:color-mix(in srgb,var(--warn) 18%,transparent);box-shadow:inset 0 0 0 1px var(--warn)}.ftr .strip .attn:hover,.ftr .strip .attn.on{background:color-mix(in srgb,var(--warn) 30%,transparent)}.ftr .strip .attn svg{color:var(--warn)}.ftr .st svg{width:12px;height:12px}
216
- .ftr a{color:var(--dim);cursor:pointer;white-space:nowrap;text-decoration:none}.ftr a:hover{color:var(--ink)}
228
+ .ftr a{color:var(--dim);cursor:pointer;white-space:nowrap;text-decoration:none;display:inline-flex;align-items:center;gap:5px}.ftr a>svg{width:13px;height:13px}.ftr a:hover{color:var(--ink)}
217
229
 
218
230
  /* side panel */
219
231
  .scrim{position:fixed;inset:0;z-index:10;background:color-mix(in srgb,var(--bg) 60%,transparent);backdrop-filter:blur(3px);display:none}
@@ -232,7 +244,7 @@ aside.docked #pclose{display:none}
232
244
  aside header{display:flex;align-items:flex-start;gap:12px;padding:20px 24px 16px;border-bottom:1px solid var(--line)}
233
245
  aside header .t{flex:1;min-width:0}
234
246
  aside header b{display:block;font-size:17px;font-weight:600;line-height:1.35}
235
- aside header .sub{color:var(--dim);font-size:12px;margin-top:4px}
247
+ aside header .sub{color:var(--dim);font-size:12px;margin-top:6px}#psub{display:flex;flex-wrap:wrap;gap:6px}.pst{display:inline-flex;align-items:center;gap:6px;padding:2px 9px 2px 6px;border-radius:var(--r-pill);background:var(--hover);color:var(--ink);font-size:12px;font-weight:500}.pst.dim{color:var(--dim);font-weight:400;padding-left:9px}.pst .st{display:inline-grid;width:14px;height:14px}.pst .st svg{width:14px;height:14px}
236
248
  aside nav{display:flex;border-bottom:1px solid var(--line);padding-inline:12px;overflow:auto hidden;scrollbar-width:none}
237
249
  aside nav:empty{display:none}
238
250
  aside nav button{padding:12px;font-size:13px;font-weight:500;color:var(--dim);white-space:nowrap}
@@ -316,9 +328,10 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
316
328
  .ice-bar .end{flex:1}
317
329
  .ice-bar .btn,.ice-bulk .btn,.ice-ra .btn{display:inline-flex;align-items:center;gap:6px}
318
330
  .ice-bar .btn svg,.ice-bulk .btn svg,.ice-ra .btn svg{width:14px;height:14px}
319
- .ice-bar input,.ice-row input,.ice-bulk input{accent-color:var(--accent);width:15px;height:15px;margin:0;flex-shrink:0}
331
+ .ice-bar>input,.ice-row input,.ice-bulk input{accent-color:var(--accent);width:15px;height:15px;margin:0;flex-shrink:0}
320
332
  .ice-bar .ice-sel{width:auto;max-width:170px;height:28px;font-size:12px;padding:2px 26px 2px 8px}
321
- @media(max-width:640px){.ice-bar{flex-wrap:wrap}.ice-bar .ice-sel{max-width:none;min-width:0;width:0;flex:1 1 40%}}
333
+ @media(max-width:640px){.ice-bar{flex-wrap:wrap}.ice-bar .ice-combo{min-width:0;flex:1 1 40%}.ice-bar .ice-combo>.sel{max-width:none;width:100%}}
334
+ .pick-list i{font-style:normal;font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums}.pick-list [hidden]{display:none}
322
335
  .ice-bulk{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:8px;padding:6px 8px 6px 14px;margin:0 0 8px;border-radius:var(--r-pill);background:color-mix(in srgb,var(--dim) 12%,var(--bg-2));font-size:13px;color:var(--ink)}
323
336
  .ice-bulk .end{flex:1}
324
337
  .ice-list{display:flex;flex-direction:column;gap:1px}
@@ -331,7 +344,7 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
331
344
  .ice-t{font:inherit;font-size:13px;color:var(--ink);background:none;border:0;padding:6px 0;text-align:left;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
332
345
  .ice-t[aria-expanded=true]{font-weight:500}
333
346
  .ice-src{display:inline-flex;align-items:center}
334
- .ice-meta{display:inline-flex;align-items:center;gap:10px;min-width:0;font-size:11px;color:var(--dim)}.ice-meta .nm{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
347
+ .ice-meta{display:inline-flex;align-items:center;gap:10px;min-width:0;font-size:11px;color:var(--dim)}.ice-meta .nm{display:inline-flex;align-items:center;gap:5px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
335
348
  .ice-age{font-size:11px;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap;min-width:64px;text-align:right}
336
349
  .ice-ra{display:none;gap:2px;min-width:64px;justify-content:flex-end}
337
350
  .ice-ra .btn{padding:0 6px}
@@ -346,39 +359,49 @@ aside .foot .more .menu-pop{left:0;right:auto;top:auto;bottom:calc(100% + 4px)}.
346
359
  body:not(.paged) .src-sec,body:not(.paged) .src-scrim{display:none}
347
360
  .src-scrim{position:fixed;inset:0;z-index:10;background:color-mix(in srgb,var(--bg) 60%,transparent);backdrop-filter:blur(3px);opacity:0;visibility:hidden;transition:opacity .18s,visibility .18s}
348
361
  .src-scrim.on{opacity:1;visibility:visible}
349
- .src-sec .sub a{color:var(--accent);text-decoration:none;cursor:pointer}
350
- .src-sec .sub a:hover{text-decoration:underline}
351
362
  @media(hover:none){.ice-ra{display:flex}.ice-age{display:none}}
352
363
  @media(max-width:640px){.ice-meta .nm{display:none}.ice-open{padding-left:12px}}
353
364
  .src-list{display:flex;flex-direction:column;gap:10px}
354
- .src-card{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:14px;box-shadow:var(--shadow-1);display:flex;flex-direction:column;gap:9px;transition:border-color .15s}
365
+ .src-card{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:12px 12px 10px;display:flex;flex-direction:column;gap:8px;transition:border-color .15s}
355
366
  .src-card:hover{border-color:color-mix(in srgb,var(--line) 70%,var(--ink))}
356
- .src-hd{display:flex;justify-content:space-between;align-items:center;gap:10px}
357
- .src-info{display:flex;align-items:center;gap:8px;min-width:0}
358
- .src-ico{width:20px;height:20px;flex-shrink:0;color:var(--accent);display:inline-flex;align-items:center;justify-content:center}
359
- .src-ico svg{width:18px;height:18px}
367
+ .src-hd{display:flex;align-items:center;gap:10px}
368
+ .src-info{display:flex;flex-direction:column;min-width:0;flex:1;gap:1px}
369
+ .src-ico{width:28px;height:28px;flex-shrink:0;color:var(--ink);display:inline-grid;place-items:center;border-radius:8px;background:var(--hover)}
370
+ .src-ico svg{width:16px;height:16px}
360
371
  /* a site's own favicon; the generic icon after it shows only once the image fails and removes itself */
361
372
  .fav{display:inline-flex;align-items:center;flex-shrink:0}.fav img{width:16px;height:16px;border-radius:4px;padding:1px;background:#fff;box-sizing:border-box}.fav img+svg{display:none}
362
373
  .ice-src .fav svg{width:14px;height:14px}
363
- .src-title{font-size:14px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
364
- .src-acts{display:flex;align-items:center;gap:4px;flex-shrink:0}
365
- .src-acts .btn{display:inline-flex;align-items:center;gap:5px}
366
- .src-acts .btn svg{width:14px;height:14px}
367
- .src-url{font-size:12px;color:var(--dim);overflow-wrap:anywhere;line-height:1.4}
368
- .src-url a{color:inherit;text-decoration:none;display:inline-flex;align-items:center;gap:4px}
369
- .src-url a:hover{color:var(--ink);text-decoration:underline}
370
- .src-url svg{width:12px;height:12px;stroke-width:1.8;flex-shrink:0}
371
- .src-status{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:12px}
374
+ .src-title{font-size:13px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
375
+ .src-url{font-size:11px;color:var(--dim);text-decoration:none;display:inline-flex;align-items:center;gap:4px;min-width:0}
376
+ .src-url span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
377
+ .src-url:hover{color:var(--ink)}
378
+ .src-url svg{width:11px;height:11px;flex-shrink:0;opacity:0;transition:opacity .15s}.src-url:hover svg{opacity:1}
379
+ .src-acts{display:flex;align-items:center;gap:2px;flex-shrink:0}
380
+ .src-acts .ib{color:var(--dim)}.src-acts .ib:hover{color:var(--ink)}.src-acts .ib.danger:hover{color:var(--danger)}
381
+ .src-status{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:11px;color:var(--dim)}
372
382
  .src-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:var(--r-pill);font-size:11px;font-weight:500}
373
383
  .src-badge.ok{background:color-mix(in srgb,var(--ok) 14%,transparent);color:var(--ok)}
374
384
  .src-badge.warn{background:color-mix(in srgb,var(--warn) 20%,transparent);color:var(--ink);font-weight:500}
375
385
  .src-badge.warn svg{color:var(--warn)}
376
386
  .src-badge.dim{background:color-mix(in srgb,var(--dim) 14%,transparent);color:var(--dim)}
377
387
  .src-badge svg{width:12px;height:12px;stroke-width:2;flex-shrink:0}
378
- .src-dest{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dim);padding-top:8px;border-top:1px solid var(--line)}
379
- .src-dest .sel{flex:1;min-width:0;max-width:240px;height:28px;font-size:12px;padding:2px 8px}
380
- .src-dest .btn{height:28px;padding:0 10px;font-size:12px;display:inline-flex;align-items:center;gap:5px}
381
- .src-dest .btn svg{width:14px;height:14px;flex-shrink:0}
388
+ .src-fields{display:grid;grid-template-columns:1fr 1fr;gap:6px;padding-top:8px;border-top:1px solid var(--line)}
389
+ .src-f{display:flex;align-items:center;gap:6px;min-width:0;font-size:11px;color:var(--dim)}
390
+ .src-f>span:first-child{flex:none}
391
+ .src-f .sel{flex:1;min-width:0;height:28px;font-size:12px;padding:2px 8px;background-color:transparent;border-color:transparent}
392
+ .src-f select.sel{padding-right:26px}
393
+ .src-f .sel:hover,.src-f .sel:focus{border-color:var(--line);background-color:var(--bg)}
394
+ .src-f .btn{height:26px;padding:0 9px;font-size:12px}.src-f .btn:disabled{display:none}
395
+ .src-f .plogo{width:16px;height:16px}
396
+ .src-wb{grid-column:1/-1;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--dim);cursor:pointer}.src-wb input{accent-color:var(--accent);margin:0}
397
+ @media(max-width:640px){.src-fields{grid-template-columns:1fr}}
398
+ .src-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
399
+ .schip{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 9px;border-radius:var(--r-pill);border:1px solid var(--line);font-size:12px;color:var(--dim);text-decoration:none;cursor:pointer;white-space:nowrap}
400
+ .schip svg{width:13px;height:13px}
401
+ a.schip:hover{color:var(--ink);border-color:color-mix(in srgb,var(--line) 60%,var(--ink))}
402
+ .schip.on{color:var(--ink);background:color-mix(in srgb,var(--ok) 10%,transparent);border-color:color-mix(in srgb,var(--ok) 35%,var(--line))}
403
+ .schip.on>svg:first-child{color:var(--ok)}
404
+ .schip a{display:inline-grid;place-items:center;margin-right:-4px;width:16px;height:16px;border-radius:50%;color:var(--dim);cursor:pointer}.schip a:hover{background:var(--hover);color:var(--danger)}.schip a svg{width:11px;height:11px}
382
405
  .ice-hd{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin:22px 0 4px}
383
406
  .ice-hd h3{margin:0;font-size:14px;font-weight:600;color:var(--ink)}
384
407
  .ice-cat{font-size:12px;font-weight:600;color:var(--dim);margin:14px 0 6px}
@@ -426,6 +449,14 @@ body:not(.paged) .src-sec,body:not(.paged) .src-scrim{display:none}
426
449
  <body>
427
450
 
428
451
  <svg style="display:none"><defs>
452
+ <symbol id="i-clip" viewBox="0 0 24 24"><path d="M21.4 11.1l-9.2 9.2a6 6 0 0 1-8.5-8.5l9.2-9.2a4 4 0 0 1 5.7 5.7l-9.2 9.2a2 2 0 0 1-2.8-2.8l8.5-8.5"/></symbol>
453
+ <symbol id="i-bug" viewBox="0 0 24 24"><rect x="8" y="6" width="8" height="14" rx="4"/><path d="M12 20v-9M8 12H4M20 12h-4M9 7 7 4M15 7l2-3M8 17l-3 2M16 17l3 2M8.5 8.5 5 7M15.5 8.5 19 7"/></symbol>
454
+ <symbol id="i-kbd" viewBox="0 0 24 24"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10"/></symbol>
455
+ <symbol id="i-check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></symbol>
456
+ <symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol>
457
+ <symbol id="i-chev" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></symbol>
458
+ <symbol id="i-ai" viewBox="0 0 24 24"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/></symbol>
459
+ <symbol id="i-google" viewBox="0 0 24 24"><path fill="currentColor" stroke="none" d="M12.48 10.92v3.28h7.84c-.24 1.84-.85 3.18-1.73 4.1-1.08 1.08-2.76 2.26-5.7 2.26-4.82 0-8.6-3.89-8.6-8.72s3.78-8.72 8.6-8.72c2.6 0 4.5 1.02 5.9 2.34l2.31-2.31C19.13 1.19 16.56 0 12.48 0 5.87 0 .31 5.39.31 12s5.56 12 12.17 12c3.57 0 6.26-1.17 8.37-3.36 2.16-2.16 2.84-5.21 2.84-7.66 0-.76-.05-1.47-.17-2.06z"/></symbol>
429
460
  <symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
430
461
  <symbol id="i-gear" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
431
462
  <symbol id="i-inbox" viewBox="0 0 24 24"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5.1L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.5-6.9A2 2 0 0 0 16.8 4H7.2a2 2 0 0 0-1.7 1.1z"/></symbol>
@@ -503,13 +534,13 @@ body:not(.paged) .src-sec,body:not(.paged) .src-scrim{display:none}
503
534
  <div class="hdr">
504
535
  <h1>Board</h1>
505
536
  <div class="combo" id="combo"><button class="sel" id="proj" aria-label="project" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" id="projpop" hidden><input class="sel" id="projq" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="projs" role="listbox"></div></div></div>
506
- <div class="combo" id="showc" hidden><button class="sel" id="show" aria-label="Projects shown" aria-haspopup="true" aria-expanded="false"></button><div class="menu-pop" id="showpop" hidden><input class="sel" id="showq" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="shows"></div><button id="showall">Show all</button></div></div>
537
+ <div class="combo" id="showc" hidden><button class="sel" id="show" aria-label="Projects shown" aria-haspopup="true" aria-expanded="false"></button><div class="menu-pop" id="showpop" hidden><input class="sel" id="showq" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="shows"></div><div class="pop-ft"><span id="showcount"></span><button id="showall">Show all</button></div></div></div>
507
538
  <span class="sp"></span>
508
539
  <button class="btn pri" id="newtop">New idea</button>
509
540
  </div>
510
541
  <main id="board" aria-live="polite"></main>
511
542
 
512
- <div class="ftr"><span class="strip" id="strip"></span><span><a href="/issue" target="_blank" rel="noopener">Report an issue</a><a id="kbd">Keyboard shortcuts</a></span></div>
543
+ <div class="ftr"><span class="strip" id="strip"></span><span><a href="/issue" target="_blank" rel="noopener"><svg><use href="#i-bug"/></svg>Report an issue</a><a id="kbd"><svg><use href="#i-kbd"/></svg>Keyboard shortcuts</a></span></div>
513
544
 
514
545
  <div class="scrim" id="scrim"></div>
515
546
  <aside id="panel" aria-label="detail">