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
+
+ for _, t := range r.Takeaways {
+ { t }
+ }
+
+
+ }
+ if len(r.Highlights) > 0 {
+
+ Highlights
+
+ for _, h := range r.Highlights {
+ { h }
+ }
+
+
+ }
+
+ 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) {
+
+}
+
+// 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, "")
+ 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, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "
Model ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, m := range v.Models {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var86 string
+ templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(m)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 569, Col: 60}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, " Switch models to compare answers on the same transcript. Ask 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, "Display name Register ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "Display name Register ")
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 ")
+ 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, "
Disconnect ")
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?
Yes, permanently delete my account ")
+ 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?
Yes, permanently delete my account ")
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, 213, "\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var103 string
- templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto))
+ var templ_7745c5c3_Var106 string
+ templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeToggleLabel(auto))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 682, Col: 79}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 714, Col: 79}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var106))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, " ")
+ 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, 223, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isActive {
- var templ_7745c5c3_Var112 string
- templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(verb))
+ var templ_7745c5c3_Var115 string
+ templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(verb))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 723, Col: 31}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 755, Col: 31}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- var templ_7745c5c3_Var113 string
- templ_7745c5c3_Var113, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(verb))
+ var templ_7745c5c3_Var116 string
+ templ_7745c5c3_Var116, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(verb))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 725, Col: 22}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 757, Col: 22}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var113))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var116))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 220, " ")
+ 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",