feat(dashboard): add entity detail modal and fixed-layout tables

Replace Inputs.table() with buildEntityTable() across workstreams and
tasks pages. Add click-to-detail modal (openEntityModal) on all entity
list views: workstreams, tasks, extension points, and technical debt.

- New component: src/components/entity-modal.js
  - openEntityModal(entity, type) — full-detail overlay (Esc/click-outside to close)
  - buildEntityTable(rows, cols, onRowClick) — table-layout:fixed, overflow-safe wrapper
  - CSS injected lazily; no separate stylesheet required

- Tables: table-layout:fixed keeps content within the content column;
  title col 32%, workstream col 14%, all cells ellipsis + title tooltip
- Cards (EP, TD): onclick → modal; workstream name span gets title tooltip
- Blocked task cards also wired to modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:28:44 +01:00
parent bd6e16394a
commit 0546a1bb2a
5 changed files with 479 additions and 36 deletions

View File

@@ -86,8 +86,9 @@ const filtered = data.filter(e =>
# Extension Points
```js
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import {openEntityModal} from "./components/entity-modal.js";
// ── KPI sidebar ───────────────────────────────────────────────────────────────
const _open = data.filter(e => e.status === "open" || e.status === "in_progress");
@@ -180,17 +181,19 @@ display(_filtersForm);
display(html`<p><strong>${filtered.length}</strong> extension points shown.</p>`);
display(html`<div class="ep-list">${filtered.map(ep => html`
<div class="ep-item ep-status-${ep.status}">
<div class="ep-item ep-status-${ep.status} entity-row"
onclick=${() => openEntityModal(ep, "ep")}
title="Click to view full details">
<div class="ep-item-header">
${ep.ep_id ? html`<span class="ep-ref">${ep.ep_id}</span>` : ""}
<span class="ep-type-badge ep-type-${ep.ep_type}">${ep.ep_type}</span>
<span class="ep-badge ep-badge-${ep.status}">${ep.status.replace("_", " ")}</span>
<span class="ep-badge ep-priority-${ep.priority}">${ep.priority}</span>
<span class="ep-domain">${ep.domain}</span>
${ep.workstream_title ? html`<span class="ep-ws">${ep.workstream_title}</span>` : ""}
${ep.workstream_title ? html`<span class="ep-ws" title=${ep.workstream_title}>${ep.workstream_title}</span>` : ""}
</div>
<div class="ep-title">${ep.title}</div>
${ep.description ? html`<div class="ep-desc">${ep.description.slice(0, 240)}${ep.description.length > 240 ? "…" : ""}</div>` : ""}
${ep.description ? html`<div class="ep-desc">${ep.description.slice(0, 220)}${ep.description.length > 220 ? " …" : ""}</div>` : ""}
${ep.location ? html`<div class="ep-location"><code>${ep.location}</code></div>` : ""}
</div>
`)}
@@ -222,6 +225,8 @@ display(html`<div class="ep-list">${filtered.map(ep => html`
/* ── EP list ──────────────────────────────────────────────────────────────── */
.ep-list { display: flex; flex-direction: column; gap: 0.5rem; }
.ep-item { border-left: 3px solid #94a3b8; border-radius: 0 6px 6px 0; background: var(--theme-background-alt); padding: 0.65rem 0.9rem; }
.ep-item.entity-row { cursor: pointer; transition: filter 0.1s; }
.ep-item.entity-row:hover { filter: brightness(0.97); }
.ep-status-open { border-left-color: #3b82f6; }
.ep-status-in_progress { border-left-color: #f59e0b; }
.ep-status-addressed { border-left-color: #22c55e; }