diff --git a/docs/use-cases/chat_transcript.feature b/docs/use-cases/chat_transcript.feature index 47f4d0f..410b5b0 100644 --- a/docs/use-cases/chat_transcript.feature +++ b/docs/use-cases/chat_transcript.feature @@ -14,6 +14,12 @@ Feature: Chat with a video's stored transcript Scenario: A summary view offers a deeper-dive into the video When I view the summary Then I see a "dig deeper" affordance that opens a chat about this video + And it opens the chat in place, below the summary, without leaving the page + + Scenario: The summary and the chat are on one page + When I open the chat + Then the summary stays visible alongside the chat + And I can read the summary while I ask questions Scenario: Ask a question answered from the stored transcript When I ask a question in the chat diff --git a/internal/web/chat.go b/internal/web/chat.go index 164004c..2408fe0 100644 --- a/internal/web/chat.go +++ b/internal/web/chat.go @@ -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 diff --git a/internal/web/chat_handler_test.go b/internal/web/chat_handler_test.go index 7fea6aa..ec53f70 100644 --- a/internal/web/chat_handler_test.go +++ b/internal/web/chat_handler_test.go @@ -137,6 +137,8 @@ func TestChatEntryAffordanceOnSummaryView(t *testing.T) { html := body(t, do(t, withChat, httptest.NewRequest(http.MethodGet, "/v/"+videoX, nil))) require.Contains(t, html, "Dig deeper", "the deeper-dive affordance is shown when chat is enabled") require.Contains(t, html, "/v/"+videoX+"/chat", "it links to this video's chat") + require.Contains(t, html, `id="chat-section"`, "the dock lives on the detail page") + require.Contains(t, html, `hx-get="/v/`+videoX+`/chat"`, "it opens the chat in place (HTMX), not a navigation") // With no chat backend wired the affordance is absent (routes unmounted). noChat := newApp(t) @@ -145,6 +147,39 @@ func TestChatEntryAffordanceOnSummaryView(t *testing.T) { require.NotContains(t, html, "Dig deeper", "no affordance when chat is disabled") } +// The summary and the chat live together (the integrated UX): the no-JS chat page +// renders the full summary alongside the chat, and the HTMX reveal returns just +// the open chat section as a fragment so it docks in below the summary already on +// screen — the summary is never navigated away from. +func TestChatIntegratedWithSummaryOnSamePage(t *testing.T) { + ctx := context.Background() + chatter := &fakeChatter{models: []string{"phi4-mini", "gemma4-26b"}} + app := newChatApp(t, chatter, nil) + p := rawPool(t) + resetDB(t, p) + require.NoError(t, deliver(ctx, app, videoX, "SUMMARY-BODY-MARKER")) + seedVideo(t, p, videoX, "X Title", "https://x", time.Time{}) + seedTranscript(t, app.Store.(*store.Store), videoX, "the transcript") + + // No-JS full page: the summary payload and the chat are on one page. + html := body(t, getChat(t, app, videoX)) + require.Contains(t, html, "SUMMARY-BODY-MARKER", "the summary text is shown on the chat page") + require.Contains(t, html, "takeaway one", "takeaways shown alongside the chat") + require.Contains(t, html, "highlight one", "highlights shown alongside the chat") + require.Contains(t, html, "Ask about this video", "the chat sits on the same page as the summary") + require.Contains(t, html, `name="question"`, "the ask form is present") + + // HTMX reveal: the open chat section ONLY (a fragment) — no full-page chrome and + // no duplicated summary, so it swaps in below the summary already rendered. + req := httptest.NewRequest(http.MethodGet, "/v/"+videoX+"/chat", nil) + req.Header.Set("HX-Request", "true") + frag := body(t, do(t, app, req)) + require.NotContains(t, frag, " } -// DetailPage is the full summary view: text, highlights, takeaways, metadata, -// and the action button group. chatEnabled adds the "dig deeper" affordance -// (ADR-027) — a quiet link into the per-video chat over the stored transcript. +// summaryBody is the summary payload shared by the detail page and the no-JS +// chat page (so the chat page shows the same summary, not a separate view): +// metadata, embed, source, the action toggles, then the attention-saving order +// Takeaways → Highlights → Summary (UX review A8). +templ summaryBody(r store.SummaryRow) { +

+ if detailMeta(r) != "" { + { detailMeta(r) } + } + if r.FallbackUsed { + fallback + } +

+ if url, ok := embedURL(r.ProviderVideoID); ok { +
+ +
+ } + if r.URL != "" { +

watch on source ↗

+ } + @ActionButtons(r.VideoID, actionSet(r.Actions)) + if len(r.Takeaways) > 0 { +
+

Takeaways

+ +
+ } + if len(r.Highlights) > 0 { +
+

Highlights

+ +
+ } +
+

Summary

+

{ r.Summary }

+
+} + +// DetailPage is the full summary view: the summary payload, then (when chat is +// enabled) the deeper-dive dock (ADR-027) — a reveal that opens the chat IN PLACE +// below the summary, so the summary stays on screen as the context being asked +// about rather than being navigated away from. templ DetailPage(r store.SummaryRow, chatEnabled bool) { @Layout("Tapir — " + displayTitle(r)) {

← Summaries

{ displayTitle(r) }

-

- if detailMeta(r) != "" { - { detailMeta(r) } - } - if r.FallbackUsed { - fallback - } -

- if url, ok := embedURL(r.ProviderVideoID); ok { -
- -
- } - if r.URL != "" { -

watch on source ↗

- } + @summaryBody(r) if chatEnabled { - // Where the "I want more" reaction goes (ADR-027). A quiet link, not a - // loud CTA — it deepens value for a reader already here, never nudges. -

Dig deeper — ask about this video →

+ @chatReveal(r.VideoID) } - @ActionButtons(r.VideoID, actionSet(r.Actions)) - // Lead with the attention-saving payload: Takeaways ("is this worth my - // time?") first, then Highlights, then the full Summary last (UX review - // A8). Takeaways/Highlights are conditional, so a video without them falls - // through to the Summary leading naturally. - if len(r.Takeaways) > 0 { -
-

Takeaways

-
    - for _, t := range r.Takeaways { -
  • { t }
  • - } -
-
- } - if len(r.Highlights) > 0 { -
-

Highlights

-
    - for _, h := range r.Highlights { -
  • { h }
  • - } -
-
- } -
-

Summary

-

{ r.Summary }

-
} } -// ChatPage is the per-video deeper-dive chat (ADR-027): a question/answer surface -// over the video's ALREADY-STORED transcript. The back link returns to the -// summary it was entered from. All the interaction lives in chatPanel so the HTMX -// answer-swap and the no-JS full-page render share one component. -templ ChatPage(v chatView) { - @Layout("Tapir — Ask — " + v.Title) { -
-

← { v.Title }

-

Ask about this video

-

Answers come only from this video's stored transcript — Tapir never fetches anything new here.

- @chatPanel(v) +// chatReveal is the CLOSED dock at the foot of the summary: a quiet affordance, +// not a loud CTA (it deepens value for a reader already here, never nudges). With +// JS it swaps itself for the open chat section in place (HTMX, summary stays +// above); without JS the same href navigates to the full chat page, which renders +// the summary alongside the chat. Either way the summary is never lost. +templ chatReveal(videoID string) { +
+ + Dig deeper — ask about this video → + +
+} + +// chatSection is the OPEN dock: heading + scope note + the chat panel, swapped in +// over the closed reveal (same #chat-section id, outerHTML). It is the HTMX reveal +// response AND the inline chat block on the no-JS chat page. +templ chatSection(v chatView) { +
+

Ask about this video

+

Answers come only from this video's stored transcript — Tapir never fetches anything new here.

+ @chatPanel(v) +
+} + +// ChatPage is the no-JS full-page render of the chat: the whole summary followed +// by the open chat dock, so a visitor without JS sees the same integrated view +// (summary beside the conversation) that JS users get inline via the reveal. +templ ChatPage(r store.SummaryRow, v chatView) { + @Layout("Tapir — " + displayTitle(r)) { +
+

← Summaries

+

{ displayTitle(r) }

+ @summaryBody(r) + @chatSection(v)
} } diff --git a/internal/web/views_templ.go b/internal/web/views_templ.go index bb47b81..62efd27 100644 --- a/internal/web/views_templ.go +++ b/internal/web/views_templ.go @@ -1452,10 +1452,11 @@ func noCaptionsCard(r store.SummaryRow) templ.Component { }) } -// DetailPage is the full summary view: text, highlights, takeaways, metadata, -// and the action button group. chatEnabled adds the "dig deeper" affordance -// (ADR-027) — a quiet link into the per-video chat over the stored transcript. -func DetailPage(r store.SummaryRow, chatEnabled bool) templ.Component { +// summaryBody is the summary payload shared by the detail page and the no-JS +// chat page (so the chat page shows the same summary, not a separate view): +// metadata, embed, source, the action toggles, then the attention-saving order +// Takeaways → Highlights → Summary (UX review A8). +func summaryBody(r store.SummaryRow) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -1476,7 +1477,199 @@ func DetailPage(r store.SummaryRow, chatEnabled bool) templ.Component { templ_7745c5c3_Var59 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var60 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if detailMeta(r) != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var60 string + templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 420, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if r.FallbackUsed { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "fallback") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if url, ok := embedURL(r.ProviderVideoID); ok { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if r.URL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "

watch on source ↗

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = ActionButtons(r.VideoID, actionSet(r.Actions)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(r.Takeaways) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "

Takeaways

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, t := range r.Takeaways { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var64 string + templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(t) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 447, Col: 12} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(r.Highlights) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "

Highlights

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, h := range r.Highlights { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var65 string + templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(h) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 457, Col: 12} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "

Summary

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var66 string + templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 464, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// DetailPage is the full summary view: the summary payload, then (when chat is +// enabled) the deeper-dive dock (ADR-027) — a reveal that opens the chat IN PLACE +// below the summary, so the summary stays on screen as the context being asked +// about rather than being navigated away from. +func DetailPage(r store.SummaryRow, chatEnabled bool) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var67 := templ.GetChildren(ctx) + if templ_7745c5c3_Var67 == nil { + templ_7745c5c3_Var67 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var68 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -1488,204 +1681,40 @@ func DetailPage(r store.SummaryRow, chatEnabled bool) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "

← Summaries

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var61 string - templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 420, Col: 24} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if detailMeta(r) != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var62 string - templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 423, Col: 26} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if r.FallbackUsed { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, "fallback") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if url, ok := embedURL(r.ProviderVideoID); ok { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if r.URL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "

watch on source ↗

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if chatEnabled { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "

Dig deeper — ask about this video →

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = ActionButtons(r.VideoID, actionSet(r.Actions)).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(r.Takeaways) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "

Takeaways

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, t := range r.Takeaways { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "
  • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var67 string - templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(t) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 459, Col: 14} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "
  • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if len(r.Highlights) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "

Highlights

    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, h := range r.Highlights { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "
  • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var68 string - templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(h) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 469, Col: 14} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "
  • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "

Summary

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "

← Summaries

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var69 string - templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary) + templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 476, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 476, Col: 24} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = summaryBody(r).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if chatEnabled { + templ_7745c5c3_Err = chatReveal(r.VideoID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var60), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var68), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1693,11 +1722,12 @@ func DetailPage(r store.SummaryRow, chatEnabled bool) templ.Component { }) } -// ChatPage is the per-video deeper-dive chat (ADR-027): a question/answer surface -// over the video's ALREADY-STORED transcript. The back link returns to the -// summary it was entered from. All the interaction lives in chatPanel so the HTMX -// answer-swap and the no-JS full-page render share one component. -func ChatPage(v chatView) templ.Component { +// chatReveal is the CLOSED dock at the foot of the summary: a quiet affordance, +// not a loud CTA (it deepens value for a reader already here, never nudges). With +// JS it swaps itself for the open chat section in place (HTMX, summary stays +// above); without JS the same href navigates to the full chat page, which renders +// the summary alongside the chat. Either way the summary is never lost. +func chatReveal(videoID string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -1718,7 +1748,105 @@ func ChatPage(v chatView) templ.Component { templ_7745c5c3_Var70 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var71 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "
Dig deeper — ask about this video →
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// chatSection is the OPEN dock: heading + scope note + the chat panel, swapped in +// over the closed reveal (same #chat-section id, outerHTML). It is the HTMX reveal +// response AND the inline chat block on the no-JS chat page. +func chatSection(v chatView) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var73 := templ.GetChildren(ctx) + if templ_7745c5c3_Var73 == nil { + templ_7745c5c3_Var73 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "

Ask about this video

Answers come only from this video's stored transcript — Tapir never fetches anything new here.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = chatPanel(v).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// ChatPage is the no-JS full-page render of the chat: the whole summary followed +// by the open chat dock, so a visitor without JS sees the same integrated view +// (summary beside the conversation) that JS users get inline via the reveal. +func ChatPage(r store.SummaryRow, v chatView) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var74 := templ.GetChildren(ctx) + if templ_7745c5c3_Var74 == nil { + templ_7745c5c3_Var74 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var75 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -1730,47 +1858,38 @@ func ChatPage(v chatView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "

← Summaries

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var72 templ.SafeURL - templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinURLErrs(videoURL(v.VideoID)) + var templ_7745c5c3_Var76 string + templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 489, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 522, Col: 24} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "\">← ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var73 string - templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(v.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 489, Col: 64} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73)) + templ_7745c5c3_Err = summaryBody(r).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "

Ask about this video

Answers come only from this video's stored transcript — Tapir never fetches anything new here.

") + templ_7745c5c3_Err = chatSection(v).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = chatPanel(v).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = Layout("Tapir — Ask — "+v.Title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var71), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var75), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -1799,210 +1918,210 @@ func chatPanel(v chatView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var74 := templ.GetChildren(ctx) - if templ_7745c5c3_Var74 == nil { - templ_7745c5c3_Var74 = templ.NopComponent + templ_7745c5c3_Var77 := templ.GetChildren(ctx) + if templ_7745c5c3_Var77 == nil { + templ_7745c5c3_Var77 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if !v.Available { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "

Chat isn't available for this video — its transcript isn't stored, and chat never fetches new captions. Summarize the video first to store its transcript.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "

Chat isn't available for this video — its transcript isn't stored, and chat never fetches new captions. Summarize the video first to store its transcript.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { if len(v.History) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, t := range v.History { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var75 string - templ_7745c5c3_Var75, templ_7745c5c3_Err = templ.JoinStringErrs(t.Question) + var templ_7745c5c3_Var78 string + templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(t.Question) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 510, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 542, Col: 51} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var76 string - templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(t.Answer) + var templ_7745c5c3_Var79 string + templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(t.Answer) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 511, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 543, Col: 62} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var76)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.Truncated { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "

Working from a bounded portion of a long transcript — answers about the end of the video may be incomplete.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "

Working from a bounded portion of a long transcript — answers about the end of the video may be incomplete.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.Error != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var77 string - templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(v.Error) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 519, Col: 48} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, t := range v.History { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var80 string - templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.ResolveAttributeValue(t.Question) + templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinStringErrs(v.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 530, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 551, Col: 48} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var80) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "\"> ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, " thinking…
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, " thinking…") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2029,12 +2148,12 @@ func RegisterPage(email, errMsg string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var84 := templ.GetChildren(ctx) - if templ_7745c5c3_Var84 == nil { - templ_7745c5c3_Var84 = templ.NopComponent + templ_7745c5c3_Var87 := templ.GetChildren(ctx) + if templ_7745c5c3_Var87 == nil { + templ_7745c5c3_Var87 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var85 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var88 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -2046,59 +2165,59 @@ func RegisterPage(email, errMsg string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "

Complete your registration

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "

Complete your registration

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if email != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "

Signed in as ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "

Signed in as ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var86 string - templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(email) + var templ_7745c5c3_Var89 string + templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 558, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 590, Col: 40} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, ".

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, ".

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "

Choose a display name to finish setting up your Tapir account.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "

Choose a display name to finish setting up your Tapir account.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if errMsg != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var87 string - templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg) + var templ_7745c5c3_Var90 string + templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 562, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 594, Col: 42} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var85), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var88), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2126,12 +2245,12 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var88 := templ.GetChildren(ctx) - if templ_7745c5c3_Var88 == nil { - templ_7745c5c3_Var88 = templ.NopComponent + templ_7745c5c3_Var91 := templ.GetChildren(ctx) + if templ_7745c5c3_Var91 == nil { + templ_7745c5c3_Var91 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var89 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var92 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -2147,43 +2266,43 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "

Account

Display name
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "

Account

Display name
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var90 string - templ_7745c5c3_Var90, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName)) + var templ_7745c5c3_Var93 string + templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 586, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 618, Col: 36} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var90)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if email != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "
Signed in as
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "
Signed in as
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var91 string - templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinStringErrs(email) + var templ_7745c5c3_Var94 string + templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(email) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 589, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 621, Col: 16} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "

Summarization

Automatic summarizes new videos from about the last week as they are discovered. Older videos stay browsable — summarize them on demand. Manual lets you pick which videos to summarize — every new video appears in your list with a Summarize button.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "

Summarization

Automatic summarizes new videos from about the last week as they are discovered. Older videos stay browsable — summarize them on demand. Manual lets you pick which videos to summarize — every new video appears in your list with a Summarize button.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2191,178 +2310,178 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(channelErrors) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "

Unavailable channels

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "

Unavailable channels

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var92 string - templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d channel(s) returned errors on the last discovery pass.", len(channelErrors))) + var templ_7745c5c3_Var95 string + templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d channel(s) returned errors on the last discovery pass.", len(channelErrors))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 606, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 638, Col: 100} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var92)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, " These may have been deleted or made private on YouTube.

    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, " These may have been deleted or made private on YouTube.

      ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, ce := range channelErrors { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "
    • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var93 string - templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(ce.ChannelName) + var templ_7745c5c3_Var96 string + templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(ce.ChannelName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 612, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 644, Col: 57} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, " unavailable since ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var94 string - templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinStringErrs(ce.FirstSeen.Format("2006-01-02")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 614, Col: 89} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "
    • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "

Connected accounts

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(conns) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "

No connected video accounts yet.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "
    ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, c := range conns { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "
  • ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var95 string - templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 629, Col: 64} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if c.ProviderAccount != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var96 string - templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 631, Col: 49} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, " unavailable since ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var97 string - templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status) + templ_7745c5c3_Var97, templ_7745c5c3_Err = templ.JoinStringErrs(ce.FirstSeen.Format("2006-01-02")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 633, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 646, Col: 89} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "
    connected ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "

Connected accounts

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(conns) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "

No connected video accounts yet.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, c := range conns { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "
  • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var98 string - templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02")) + templ_7745c5c3_Var98, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 635, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 661, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var98)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var99 templ.SafeURL - templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinURLErrs(disconnectURL(c.Provider)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 636, Col: 62} + if c.ProviderAccount != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var99 string + templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 663, Col: 49} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, "\">
  • ") + var templ_7745c5c3_Var100 string + templ_7745c5c3_Var100, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 665, Col: 38} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var100)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "
    connected ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var101 string + templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 667, Col: 83} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "
    ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if !hasYouTube(conns) { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "

Connect YouTube

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "

Connect YouTube

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, "

Delete account

Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.

Delete account…

This permanently deletes your account and all data. Are you sure?

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "

Delete account

Permanently remove your Tapir account and all of its data — summaries, watch/skip/save actions, and connected accounts. This cannot be undone.

Delete account…

This permanently deletes your account and all data. Are you sure?

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var89), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var92), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2390,51 +2509,51 @@ func summarizeModeControl(auto bool) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var100 := templ.GetChildren(ctx) - if templ_7745c5c3_Var100 == nil { - templ_7745c5c3_Var100 = templ.NopComponent + templ_7745c5c3_Var103 := templ.GetChildren(ctx) + if templ_7745c5c3_Var103 == nil { + templ_7745c5c3_Var103 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "

Current mode: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "

Current mode: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var101 string - templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto)) + var templ_7745c5c3_Var104 string + templ_7745c5c3_Var104, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 673, Col: 53} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 705, Col: 53} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var101)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var104)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2462,38 +2581,38 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var104 := templ.GetChildren(ctx) - if templ_7745c5c3_Var104 == nil { - templ_7745c5c3_Var104 = templ.NopComponent + templ_7745c5c3_Var107 := templ.GetChildren(ctx) + if templ_7745c5c3_Var107 == nil { + templ_7745c5c3_Var107 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "\" hx-target=\"#action-buttons\" hx-swap=\"outerHTML\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2505,7 +2624,7 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2513,7 +2632,7 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 219, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -2539,81 +2658,81 @@ func actionButton(verb string, isActive bool) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var107 := templ.GetChildren(ctx) - if templ_7745c5c3_Var107 == nil { - templ_7745c5c3_Var107 = templ.NopComponent + templ_7745c5c3_Var110 := templ.GetChildren(ctx) + if templ_7745c5c3_Var110 == nil { + templ_7745c5c3_Var110 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var108 = []any{"action", templ.KV("active", isActive)} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var108...) + var templ_7745c5c3_Var111 = []any{"action", templ.KV("active", isActive)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var111...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 224, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/test/acceptance/scenario_coverage_test.go b/test/acceptance/scenario_coverage_test.go index 099f78b..0a2fec3 100644 --- a/test/acceptance/scenario_coverage_test.go +++ b/test/acceptance/scenario_coverage_test.go @@ -65,6 +65,7 @@ var scenarioCoverage = map[string]string{ // chat_transcript.feature (ADR-027) "A summary view offers a deeper-dive into the video": "TestChatEntryAffordanceOnSummaryView", + "The summary and the chat are on one page": "TestChatIntegratedWithSummaryOnSamePage", "Ask a question answered from the stored transcript": "TestChatAnswersFromStoredTranscriptWithoutAnyFetch", "Chat never fetches captions or reaches YouTube": "TestChatAnswersFromStoredTranscriptWithoutAnyFetch", "A video with no stored transcript offers no chat": "TestChatUnavailableWhenNoStoredTranscript",