feat(dashboard): shift+click trigger + Improvements section in Todo

improvement-modal.js:
- Replace contextmenu handler with click+shiftKey check — browser
  context menu is no longer intercepted
- Add keydown/keyup/blur listeners: holding Shift applies
  .impr-shift-mode to <body>, switching cursor to crosshair
  across the entire page as a visual affordance
- Update hint text to "Ctrl + Enter to submit · Escape to cancel"

todo.md:
- New "Improvements" section shows open dashboard-improvement TD items
  with a "review →" link to the UI Feedback page
- KPI sidebar row added for open improvement count (indigo when > 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 23:56:34 +01:00
parent b558610de6
commit 46f4b0c25d
2 changed files with 67 additions and 13 deletions

View File

@@ -11,14 +11,15 @@ const THIS_REPO = "the-custodian";
// Live poll: tasks + workstreams + topics + contributions
const todoState = (async function*() {
while (true) {
let tasks = [], contribs = [], wsMap = {}, ok = false;
let tasks = [], contribs = [], improvements = [], wsMap = {}, ok = false;
try {
const [rt, rw, rto, rr, rc] = await Promise.all([
const [rt, rw, rto, rr, rc, ri] = await Promise.all([
fetch(`${API}/tasks/?limit=500`),
fetch(`${API}/workstreams/`),
fetch(`${API}/topics/`),
fetch(`${API}/repos/`),
fetch(`${API}/contributions/`),
fetch(`${API}/technical-debt/?debt_type=dashboard-improvement`),
]);
ok = rt.ok && rw.ok && rto.ok && rr.ok && rc.ok;
if (ok) {
@@ -37,19 +38,21 @@ const todoState = (async function*() {
domain: wsMap[t.workstream_id]?.domain ?? "unknown",
}));
contribs = contribList;
improvements = ri.ok ? (await ri.json()).filter(t => t.debt_type === "dashboard-improvement" && t.status === "open") : [];
}
} catch {}
yield {tasks, contribs, ok, ts: new Date()};
yield {tasks, contribs, improvements, ok, ts: new Date()};
await new Promise(res => setTimeout(res, POLL));
}
})();
```
```js
const tasks = todoState.tasks ?? [];
const contribs = todoState.contribs ?? [];
const _ok = todoState.ok ?? false;
const _ts = todoState.ts;
const tasks = todoState.tasks ?? [];
const contribs = todoState.contribs ?? [];
const improvements = todoState.improvements ?? [];
const _ok = todoState.ok ?? false;
const _ts = todoState.ts;
```
```js
@@ -102,6 +105,12 @@ const _kpiBox = html`<div class="kpi-infobox">
<div class="kpi-row-value">${thirdParty.length}</div>
</div>
</div>
<div class="kpi-row">
<span class="kpi-row-label">improvements (open)</span>
<div class="kpi-row-right">
<div class="kpi-row-value" style="color:${improvements.length > 0 ? '#6366f1' : 'inherit'}">${improvements.length}</div>
</div>
</div>
</div>`;
// ── Live indicator ────────────────────────────────────────────────────────────
@@ -198,6 +207,31 @@ if (thirdParty.length === 0) {
}
```
---
## Improvements
Dashboard suggestions submitted via Shift+click. Review and action on the
[UI Feedback](/ui-feedback) page; open items shown here for visibility.
```js
if (improvements.length === 0) {
display(html`<p class="dim">No open improvement suggestions. Shift+click any widget to submit one.</p>`);
} else {
display(html`<div class="task-list">${improvements.map(t => html`
<div class="task-item impr-item">
<div class="task-item-header">
<span class="task-badge" style="background:#ede9fe;color:#4c1d95">improvement</span>
<span class="task-context">${t.location ?? ""}</span>
<a class="impr-review-link" href="/ui-feedback">review →</a>
</div>
<div class="task-title">${t.title.replace(/^UI:\s*/, "")}</div>
${t.description ? html`<div class="impr-desc">${t.description.slice(0, 200)}${t.description.length > 200 ? " …" : ""}</div>` : ""}
</div>
`)}</div>`);
}
```
<style>
/* ── Live indicator ───────────────────────────────────────────────────────── */
.live-indicator { font-size: 0.8rem; color: gray; position: relative; padding: 0.55rem 1.8rem 0.55rem 0.7rem; margin-bottom: 0.75rem; }
@@ -231,4 +265,8 @@ if (thirdParty.length === 0) {
.task-title { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.15rem; }
.task-blocking-reason { font-size: 0.8rem; color: #b45309; background: #fef3c7; border-radius: 4px; padding: 0.2rem 0.5rem; margin-top: 0.25rem; }
.dim { color: gray; font-style: italic; }
.task-item.impr-item { border-left-color: #6366f1; }
.impr-desc { font-size: 0.8rem; color: var(--theme-foreground-muted); margin-top: 0.2rem; line-height: 1.45; }
.impr-review-link { margin-left: auto; font-size: 0.75rem; color: #6366f1; text-decoration: none; }
.impr-review-link:hover { text-decoration: underline; }
</style>