From 451f02c1d553a6ff0c0a84e154dcfb43f5849b12 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 17 Jul 2026 19:26:12 +0200 Subject: [PATCH] feat(dashboard): expandable full text on decision cards 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
) to labeled full Description and Rationale sections; short single-field cards render unchanged. Co-Authored-By: Claude Fable 5 --- dashboard/src/decisions.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/dashboard/src/decisions.md b/dashboard/src/decisions.md index e23c8f5..029addf 100644 --- a/dashboard/src/decisions.md +++ b/dashboard/src/decisions.md @@ -330,7 +330,13 @@ if (filtered.length === 0) { } else { display(html`
${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`` : ""}
${d.title}
- ${snippet ? html`
${snippet}${snippet.length < (d.description || d.rationale || "").length ? "…" : ""}
` : ""} + ${_primary ? (_needsExpand + ? html`
+ + ${snippet}${_primary.length > 200 ? "…" : ""} + + +
+ ${_desc ? html`
Description
${_desc}
` : ""} + ${_rat ? html`
Rationale
${_rat}
` : ""} +
+
` + : html`
${_primary}
`) : ""} ${d.decided_by ? html`
✓ ${d.decided_by}${decided ? " · " + decided : ""}
` : ""} ${d.escalation_note && !["resolved", "superseded"].includes(d.status) ? html`
${d.escalation_note}
` : ""} `; @@ -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 ───────────────────────────────────────────────────────── */