feat(web): inline-expand summary + Q&A in the list (ADR-031, #16)
CI / Lint / Test / Vet (push) Successful in 11s
CI / Build & Import (push) Successful in 11s

Click a summarized card → its full summary (summaryBody) + chat dock (chatReveal)
expand in place via HTMX (GET /v/{id}/expand → expandedCard), collapse back via
GET /v/{id}/card → compact VideoCard. Same <li id>, outerHTML swap — the existing
list-fragment pattern. The card title carries href=/v/{id} as the no-JS fallback
(detail page stays for no-JS + deep links); only summarized cards expand. Reuses
summaryBody + chatReveal so the expanded card never drifts from the detail page.

BDD: inline_expand.feature un-pended + mapped. TDD: 6 handler/fragment tests
(expand/collapse fragments, chat dock, summarized-only, no-JS href, shared body).
Minimal CSS only — the TUI/charm restyle is #17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 10:47:47 +02:00
co-authored by Claude Opus 4.8
parent 607a8cbe8d
commit f98b640531
7 changed files with 866 additions and 522 deletions
+36 -1
View File
@@ -264,7 +264,16 @@ templ summaryList(b listBuckets, hasConnected bool, autoSummarize bool) {
templ VideoCard(r store.SummaryRow) {
<li class={ "card", templ.KV("card-pending", !r.Summarized) } id={ "video-" + r.VideoID }>
if r.Summarized {
<div class="card-title"><a href={ videoURL(r.VideoID) }>{ displayTitle(r) }</a></div>
// Expand the full summary + Q&A in place (ADR-031); href is the no-JS
// fallback to the detail page, so nothing becomes JS-only.
<div class="card-title">
<a
href={ videoURL(r.VideoID) }
hx-get={ string(expandURL(r.VideoID)) }
hx-target={ "#video-" + r.VideoID }
hx-swap="outerHTML"
>{ displayTitle(r) }</a>
</div>
} else {
<div class="card-title">{ displayTitle(r) }</div>
}
@@ -327,6 +336,32 @@ templ VideoCard(r store.SummaryRow) {
</li>
}
// expandedCard is a summarized list card opened IN PLACE (ADR-031): the full
// summary body + the deeper-dive chat dock, with a collapse control back to the
// compact card. It shares the <li id> with VideoCard so HTMX swaps it outerHTML,
// and reuses summaryBody + chatReveal so it never drifts from the detail page.
// Note: chatReveal uses a single #chat-section id, so this assumes one card open
// at a time; a per-video chat id is a follow-up if simultaneous expansion is wanted.
templ expandedCard(r store.SummaryRow, chatEnabled bool) {
<li class="card card-expanded" id={ "video-" + r.VideoID }>
<div class="card-expanded-head">
<span class="card-title">{ displayTitle(r) }</span>
<a
href={ videoURL(r.VideoID) }
hx-get={ string(cardURL(r.VideoID)) }
hx-target={ "#video-" + r.VideoID }
hx-swap="outerHTML"
class="card-collapse"
title="Collapse"
>collapse </a>
</div>
@summaryBody(r)
if chatEnabled {
@chatReveal(r.VideoID)
}
</li>
}
// TapirSpinner is the summarizing animation: a Charmbracelet-style TUI panel —
// three richly coloured ASCII tapir frames (inline span colours, snout wiggling
// ∩→∪→~) cross-faded by CSS, plus a lipgloss-style progress bar whose mint fill