feat(dashboard): expandable full text on decision cards
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Cards previously showed a hard 200-char snippet of description OR
rationale with no way to read the rest, and rationale was hidden
whenever a description existed. Long or dual-field cards now expand
in place (native <details>) to labeled full Description and Rationale
sections; short single-field cards render unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-17 19:26:12 +02:00
parent 1906289997
commit 451f02c1d5

View file

@ -330,7 +330,13 @@ if (filtered.length === 0) {
} else {
display(html`<div class="dec-list">${filtered.map(d => {
const border = STATUS_BORDER[d.status] ?? "#ccc";
const snippet = (d.description || d.rationale || "").slice(0, 200);
const _desc = d.description || "";
const _rat = d.rationale || "";
const _primary = _desc || _rat;
const snippet = _primary.slice(0, 200);
// Expandable when text is truncated, or when both fields exist (the
// collapsed view only shows one of them).
const _needsExpand = _primary.length > 200 || (_desc && _rat && _desc !== _rat);
const due = fmtDate(d.deadline);
const decided = fmtDate(d.decided_at);
const overdue = isOverdue(d.deadline);
@ -375,7 +381,18 @@ if (filtered.length === 0) {
${_isOpen ? html`<button class="dec-resolve-btn" onclick=${onResolve}>Resolve</button>` : ""}
</div>
<div class="dec-title">${d.title}</div>
${snippet ? html`<div class="dec-snippet">${snippet}${snippet.length < (d.description || d.rationale || "").length ? "…" : ""}</div>` : ""}
${_primary ? (_needsExpand
? html`<details class="dec-expand">
<summary>
<span class="dec-snippet dec-snippet-collapsed">${snippet}${_primary.length > 200 ? "…" : ""}</span>
<span class="dec-more"></span>
</summary>
<div class="dec-fulltext">
${_desc ? html`<div class="dec-fulltext-section"><div class="dec-fulltext-label">Description</div><div class="dec-snippet">${_desc}</div></div>` : ""}
${_rat ? html`<div class="dec-fulltext-section"><div class="dec-fulltext-label">Rationale</div><div class="dec-snippet">${_rat}</div></div>` : ""}
</div>
</details>`
: html`<div class="dec-snippet">${_primary}</div>`) : ""}
${d.decided_by ? html`<div class="dec-resolved-by">✓ ${d.decided_by}${decided ? " · " + decided : ""}</div>` : ""}
${d.escalation_note && !["resolved", "superseded"].includes(d.status) ? html`<div class="dec-escalation-note">${d.escalation_note}</div>` : ""}
</div>`;
@ -458,6 +475,18 @@ if (escalated.length > 0) {
.dec-title { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.2rem; }
.dec-snippet { font-size: 0.82rem; color: var(--theme-foreground-muted); line-height: 1.45; white-space: pre-wrap; }
.dec-resolved-by { font-size: 0.78rem; color: #22c55e; margin-top: 0.3rem; }
/* ── Expandable full text ─────────────────────────────────────────────────── */
.dec-expand summary { list-style: none; cursor: pointer; }
.dec-expand summary::-webkit-details-marker { display: none; }
.dec-expand[open] .dec-snippet-collapsed { display: none; }
.dec-more::after { content: "Show full text ▾"; }
.dec-expand[open] .dec-more::after { content: "Show less ▴"; }
.dec-more { display: inline-block; margin-left: 0.35rem; font-size: 0.72rem; font-weight: 600; color: steelblue; white-space: nowrap; }
.dec-more:hover { text-decoration: underline; }
.dec-expand[open] .dec-more { display: block; margin: 0.15rem 0 0.35rem; }
.dec-fulltext-section + .dec-fulltext-section { margin-top: 0.5rem; }
.dec-fulltext-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--theme-foreground-faint, #aaa); margin-bottom: 0.1rem; }
.dec-escalation-note { font-size: 0.78rem; color: #b45309; margin-top: 0.3rem; background: #fef3c7; border-radius: 4px; padding: 0.25rem 0.5rem; }
/* ── Resolve button ───────────────────────────────────────────────────────── */