refactor(web): dock chat inline below the summary, integrated view (ADR-027)
The chat was a separate page — opening it left the summary behind, the very
context you're asking about. Now the chat docks open IN PLACE below the summary:
"Dig deeper" reveals the chat section (HTMX, outerHTML over the closed dock) so
the summary stays on screen above it; no navigation. The no-JS fallback renders
the full summary AND the open chat on one page (the same integrated view), so
progressive enhancement holds.
Extracts a shared summaryBody templ so the detail page and the chat page render
one identical summary, not two divergent ones. GET /v/{id}/chat returns just the
open chat section as a fragment for the inline reveal, or the full summary+chat
page for a no-JS navigation; POST swaps the panel inline or re-renders the whole
page. Tests assert summary+chat coexist on the page and the reveal is a fragment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+16
-7
@@ -64,13 +64,21 @@ func (a *App) handleChat(w http.ResponseWriter, r *http.Request) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.render(w, r, ChatPage(chatView{
|
||||
view := chatView{
|
||||
VideoID: row.VideoID,
|
||||
Title: displayTitle(*row),
|
||||
Available: hasText,
|
||||
Models: a.Chat.Models(),
|
||||
Selected: a.Chat.DefaultModel(row.AIModel),
|
||||
}))
|
||||
}
|
||||
// HTMX (the in-place reveal from the summary) gets just the open chat section,
|
||||
// swapped over the closed dock so the summary above it stays put. A no-JS
|
||||
// navigation gets the full page: the whole summary plus the open chat.
|
||||
if isHTMX(r) {
|
||||
a.render(w, r, chatSection(view))
|
||||
return
|
||||
}
|
||||
a.render(w, r, ChatPage(*row, view))
|
||||
}
|
||||
|
||||
// handleChatMessage answers one question against the stored transcript (POST).
|
||||
@@ -129,7 +137,7 @@ func (a *App) handleChatMessage(w http.ResponseWriter, r *http.Request) {
|
||||
view.Truncated = reply.Truncated
|
||||
}
|
||||
}
|
||||
a.renderChat(w, r, view)
|
||||
a.renderChatTurn(w, r, *row, view)
|
||||
}
|
||||
|
||||
// loadOwnedSummary fetches the summary for the path's video scoped to userID, or
|
||||
@@ -166,14 +174,15 @@ func (a *App) readTranscript(w http.ResponseWriter, r *http.Request, row store.S
|
||||
return t.Content, true, true
|
||||
}
|
||||
|
||||
// renderChat returns the chat panel fragment for an HTMX request, or the full
|
||||
// chat page otherwise (no-JS POST re-renders the whole page).
|
||||
func (a *App) renderChat(w http.ResponseWriter, r *http.Request, v chatView) {
|
||||
// renderChatTurn returns the chat panel fragment for an HTMX answer (swapped in
|
||||
// place within the open dock), or the full chat page otherwise — the no-JS POST
|
||||
// re-renders the whole summary + open chat with the new turn.
|
||||
func (a *App) renderChatTurn(w http.ResponseWriter, r *http.Request, row store.SummaryRow, v chatView) {
|
||||
if isHTMX(r) {
|
||||
a.render(w, r, chatPanel(v))
|
||||
return
|
||||
}
|
||||
a.render(w, r, ChatPage(v))
|
||||
a.render(w, r, ChatPage(row, v))
|
||||
}
|
||||
|
||||
// resolveModel keeps the posted model only when it is an offered option; anything
|
||||
|
||||
Reference in New Issue
Block a user