From 1665a1e7c41379fdc9ba4637e5ab8ea99d06f331 Mon Sep 17 00:00:00 2001
From: Mathias
Date: Wed, 10 Jun 2026 21:52:33 +0200
Subject: [PATCH] feat(web): honest, state-aware summarize status with charm
spinner (ADR-025)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Clicking Summarize polled /status, which knew only "spinning" or "done". The web
ProcessVideo recorded an outcome only on success, so a 429'd or caption-less
click left transcript_status unset and the poll silently reverted to the
Summarize button — the rate limit was invisible and the click felt broken.
- ProcessVideo now records rate_limited / none / fetched (mirrors the runner); a
rate-limited video keeps its requested flag so the background sweep retries it.
- /status is state-aware: summary card (done), working spinner (in-flight),
a calm "waiting on rate limit, will retry" card that keeps polling so the
summary lands on its own (no re-click), and a terminal "no captions" card.
- Charm status text (Claude-Code / Crush inspired): the spinner cycles playful
tapir-themed gerunds via CSS only (no JS), decorative + an sr-only stable
status line for a11y.
Pillar A (foreground fetch priority lane) is a separate follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
DECISIONS.md | 35 ++
cmd/tapir/processor.go | 23 +-
internal/web/handlers.go | 17 +-
internal/web/processing_handler_test.go | 40 ++
internal/web/view.go | 19 +
internal/web/views.templ | 49 +-
internal/web/views_templ.go | 619 +++++++++++++++---------
7 files changed, 579 insertions(+), 223 deletions(-)
diff --git a/DECISIONS.md b/DECISIONS.md
index 0b1a50d..9b19462 100644
--- a/DECISIONS.md
+++ b/DECISIONS.md
@@ -923,6 +923,41 @@ summaries.
---
+## ADR-025 — Honest, state-aware foreground summarization status
+
+**Status:** Accepted (2026-06-10). **Pillar B of the manual-mode UX work** (Pillar A, foreground
+fetch priority, is a separate follow-up). Builds on ADR-014 (the rate limit the UX must make
+legible).
+
+**Context.** Clicking "Summarize" spawned a background goroutine and polled `/status`, which
+returned only two states: the spinner (in-flight) or the normal card (done). But the web
+`ProcessVideo` only recorded an outcome on *success* — a 429'd or caption-less click left
+`transcript_status` unset, so the next poll silently reverted to the "Summarize" button. The
+user saw either an endless spinner or a button that did nothing useful when clicked again. The
+binding constraint (YouTube's caption rate limit) was completely invisible.
+
+**Decision.**
+1. **Record every outcome on the web path**, mirroring the runner: `ProcessVideo` stamps
+ `rate_limited` / `none` / `fetched`. A rate-limited video keeps its requested flag so the
+ background sweep retries it; `none` and `fetched` are terminal.
+2. **`/status` is state-aware**: summarized → summary card; in-flight → working spinner;
+ `rate_limited` → a calm "waiting, will retry" card that keeps polling (every 30s) so the
+ summary appears on its own when the retry lands — the user never clicks again;
+ `none` → a terminal "no captions" card with no poll and no dead-end button.
+3. **Charm status text** (Claude-Code / Crush inspired): the working spinner cycles playful,
+ tapir-themed gerunds ("Chewing the cud…", "Munching leaves…", "Distilling the gist…") via
+ CSS only — no JS, keeping the HTMX/no-JS ethos. Decorative (aria-hidden) with a stable
+ `role=status` line for assistive tech.
+
+**Principle.** When the system cannot be fast (throttled IP), it is at least honest, and it
+self-resolves without making the user retry. Honesty is the load-bearing half — Pillar A's
+priority lane only improves the odds of a fast slot; it cannot beat an already-hot IP.
+
+**Reversibility.** Pure transport-layer + view change over the unchanged engine/ports. No
+schema change (reuses `transcript_status` from migration 007).
+
+---
+
## Rejected alternatives
Approaches considered during the 2026-06-02 planning + grill session and **deliberately not
diff --git a/cmd/tapir/processor.go b/cmd/tapir/processor.go
index 4e222fe..a61359f 100644
--- a/cmd/tapir/processor.go
+++ b/cmd/tapir/processor.go
@@ -132,7 +132,28 @@ func (p *engineProcessor) ProcessVideo(ctx context.Context, userID, videoID stri
if err != nil {
return fmt.Errorf("process video %q: %w", videoID, err)
}
- if res.Summary != nil {
+
+ // Record the outcome so the status endpoint can show honest state (ADR-025):
+ // a 429'd or caption-less click used to leave transcript_status unset, so the
+ // poll silently reverted to the "Summarize" button. Mirror the runner: stamp
+ // rate_limited / none / fetched. A rate-limited video keeps its requested flag
+ // so the background sweep retries it; none and fetched are terminal here.
+ switch {
+ case res.Skipped && res.TranscriptSource == string(domain.SourceRateLimited):
+ if err := p.store.SetTranscriptStatus(ctx, userID, videoID, "rate_limited"); err != nil {
+ return fmt.Errorf("set rate_limited status %q: %w", videoID, err)
+ }
+ case res.Skipped:
+ if err := p.store.SetTranscriptStatus(ctx, userID, videoID, "none"); err != nil {
+ return fmt.Errorf("set none status %q: %w", videoID, err)
+ }
+ if err := p.store.ClearSummarizeRequested(ctx, userID, videoID); err != nil {
+ return fmt.Errorf("clear summarize flag %q: %w", videoID, err)
+ }
+ case res.Summary != nil:
+ if err := p.store.SetTranscriptStatus(ctx, userID, videoID, "fetched"); err != nil {
+ return fmt.Errorf("set fetched status %q: %w", videoID, err)
+ }
if err := p.store.ClearSummarizeRequested(ctx, userID, videoID); err != nil {
return fmt.Errorf("clear summarize flag %q: %w", videoID, err)
}
diff --git a/internal/web/handlers.go b/internal/web/handlers.go
index 29ec5b1..48041e6 100644
--- a/internal/web/handlers.go
+++ b/internal/web/handlers.go
@@ -495,11 +495,22 @@ func (a *App) handleStatus(w http.ResponseWriter, r *http.Request) {
return
}
- if row.Summarized || !a.Processing.Has(processingKey(userID, videoID)) {
+ // Honest, state-aware status (ADR-025). Order matters: a finished summary wins;
+ // an in-flight goroutine shows the working spinner; a recorded rate-limit shows
+ // the calm "waiting, will retry" card that keeps polling; a recorded "none" is
+ // terminal; anything else falls back to the normal card.
+ switch {
+ case row.Summarized:
+ a.render(w, r, VideoCard(*row))
+ case a.Processing.Has(processingKey(userID, videoID)):
+ a.render(w, r, processingCard(*row))
+ case row.TranscriptStatus == "rate_limited":
+ a.render(w, r, waitingCard(*row))
+ case row.TranscriptStatus == "none":
+ a.render(w, r, noCaptionsCard(*row))
+ default:
a.render(w, r, VideoCard(*row))
- return
}
- a.render(w, r, processingCard(*row))
}
// handleSummarizeMode toggles the user's auto/manual summarization mode. The form
diff --git a/internal/web/processing_handler_test.go b/internal/web/processing_handler_test.go
index 456b3ff..601bdf1 100644
--- a/internal/web/processing_handler_test.go
+++ b/internal/web/processing_handler_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"time"
+ "github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/require"
"gitea.d-ma.be/mathias/tapir/internal/web"
@@ -129,3 +130,42 @@ func getStatus(t *testing.T, app *web.App, videoID string) *httptest.ResponseRec
app.Router().ServeHTTP(rec, req)
return rec
}
+
+// setTranscriptStatus stamps videos.transcript_status directly (bypassing RLS via
+// the super pool) so a test can drive the status endpoint into a given state.
+func setTranscriptStatus(t *testing.T, p *pgxpool.Pool, videoID, status string) {
+ t.Helper()
+ _, err := p.Exec(context.Background(),
+ `UPDATE videos SET transcript_status = $2 WHERE id = $1`, videoID, status)
+ require.NoError(t, err)
+}
+
+// TestStatusRateLimitedShowsWaitingCard: a click that hit YouTube's rate limit
+// must surface the honest "waiting, will retry" card that keeps polling — not a
+// silent revert to the Summarize button.
+func TestStatusRateLimitedShowsWaitingCard(t *testing.T) {
+ app := newApp(t)
+ p := rawPool(t)
+ resetDB(t, p)
+ seedVideo(t, p, videoX, "Throttled Title", "https://x", time.Time{})
+ setTranscriptStatus(t, p, videoX, "rate_limited")
+
+ html := body(t, getStatus(t, app, videoX))
+ require.Contains(t, html, "Waiting on YouTube rate limits", "honest rate-limit copy")
+ require.Contains(t, html, `hx-trigger="every 30s"`, "waiting card keeps polling so it self-resolves")
+ require.NotContains(t, html, "Summarize this video", "must not revert to the Summarize button")
+}
+
+// TestStatusNoCaptionsTerminal: a no-captions outcome is terminal — an honest
+// message, no poll, no button to click back into the same dead end.
+func TestStatusNoCaptionsTerminal(t *testing.T) {
+ app := newApp(t)
+ p := rawPool(t)
+ resetDB(t, p)
+ seedVideo(t, p, videoX, "Silent Title", "https://x", time.Time{})
+ setTranscriptStatus(t, p, videoX, "none")
+
+ html := body(t, getStatus(t, app, videoX))
+ require.Contains(t, html, "No captions available", "honest terminal copy")
+ require.NotContains(t, html, "hx-trigger", "terminal card must stop polling")
+}
diff --git a/internal/web/view.go b/internal/web/view.go
index d415ebd..e053c46 100644
--- a/internal/web/view.go
+++ b/internal/web/view.go
@@ -658,11 +658,30 @@ a.btn, a.btn:visited { color: var(--accent-fg); }
.tapir-bar-fill { animation: tapir-fill 8s linear infinite; text-shadow: 0 0 6px rgba(14, 249, 182, .7); }
@keyframes tapir-fill { 0% { clip-path: inset(0 100% 0 0); } 100% { clip-path: inset(0 0 0 0); } }
.tapir-label { color: var(--muted); font-size: .9rem; margin: 0; }
+/* Cycling status verbs (Claude-Code / Crush style): five gerunds stacked, each
+ visible 1/5 of a 6s loop, cross-faded. The container reserves one line height
+ so the layout does not jump as verbs swap. */
+.tapir-verbs { position: relative; height: 1.3em; margin: .2em 0 0; color: var(--muted); font-size: .9rem; }
+.tapir-verbs span { position: absolute; left: 0; top: 0; white-space: nowrap; opacity: 0; animation: tapir-verb 6s steps(1, end) infinite; }
+.tapir-verbs .tv1 { animation-delay: 0s; }
+.tapir-verbs .tv2 { animation-delay: 1.2s; }
+.tapir-verbs .tv3 { animation-delay: 2.4s; }
+.tapir-verbs .tv4 { animation-delay: 3.6s; }
+.tapir-verbs .tv5 { animation-delay: 4.8s; }
+@keyframes tapir-verb { 0%, 19.99% { opacity: 1; } 20%, 100% { opacity: 0; } }
+/* Resting tapir for the rate-limit waiting state: the panel, one still frame, no
+ animation — calm, not busy, signalling "parked, not stuck". */
+.tapir-resting pre { position: relative; opacity: 1; animation: none; }
+.card-waiting { border-style: dashed; opacity: .92; }
+.card-no-captions .card-state { font-style: italic; }
+.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
@media (prefers-reduced-motion: reduce) {
.tapir-charm pre { animation: none; }
.tapir-charm .tapir-f2, .tapir-charm .tapir-f3 { display: none; }
.tapir-charm .tapir-f1 { opacity: 1; }
.tapir-bar-fill { animation: none; clip-path: inset(0 35% 0 0); }
+ .tapir-verbs span { animation: none; }
+ .tapir-verbs .tv1 { opacity: 1; }
}
/* summarization mode toggle on the account page */
diff --git a/internal/web/views.templ b/internal/web/views.templ
index e8d9631..4d82cd6 100644
--- a/internal/web/views.templ
+++ b/internal/web/views.templ
@@ -339,7 +339,17 @@ templ TapirSpinner() {
@templ.Raw(tapirFrameHTML3)
{ tapirBarFill }
- Summarizing…
+ // Claude-Code / Crush-style status: playful gerunds cycle in place (CSS only,
+ // no JS). Decorative — aria-hidden — with one stable status line below for
+ // assistive tech.
+
+ Fetching captions…
+ Chewing the cud…
+ Munching leaves…
+ Distilling the gist…
+ Summarizing…
+
+ Summarizing…
}
// processingCard is the in-flight summarization card. It replaces the Summarize
@@ -363,6 +373,43 @@ templ processingCard(r store.SummaryRow) {
}
+// waitingCard is the honest rate-limited state: the click landed but YouTube is
+// throttling the caption fetch, so the tapir rests and the card keeps polling
+// (gently, every 30s) until the background retry lands the summary — the user
+// never has to click again. Replaces the old silent revert to a Summarize button.
+templ waitingCard(r store.SummaryRow) {
+
+ { displayTitle(r) }
+ if cardMeta(r) != "" {
+ { cardMeta(r) }
+ }
+
+
@templ.Raw(tapirFrameHTML2)
+
+
+ Waiting on YouTube rate limits. Tapir keeps trying, slowly and politely, and the summary will appear here on its own.
+
+
+}
+
+// noCaptionsCard is the terminal no-captions state: nothing to summarize, so the
+// card stops (no poll, no button to click again into the same dead end).
+templ noCaptionsCard(r store.SummaryRow) {
+
+ { displayTitle(r) }
+ if cardMeta(r) != "" {
+ { cardMeta(r) }
+ }
+ No captions available, so Tapir cannot summarize this one.
+
+}
+
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
// and the action button group.
templ DetailPage(r store.SummaryRow) {
diff --git a/internal/web/views_templ.go b/internal/web/views_templ.go
index b6ddd3e..54d1027 100644
--- a/internal/web/views_templ.go
+++ b/internal/web/views_templ.go
@@ -1161,7 +1161,7 @@ func TapirSpinner() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "Summarizing…
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "Fetching captions… Chewing the cud… Munching leaves… Distilling the gist… Summarizing…
Summarizing…
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1202,7 +1202,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var46 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue("video-" + r.VideoID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 353, Col: 27}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 363, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
if templ_7745c5c3_Err != nil {
@@ -1215,7 +1215,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var47 string
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(statusURL(r.VideoID)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 354, Col: 39}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 364, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var47)
if templ_7745c5c3_Err != nil {
@@ -1228,7 +1228,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var48 string
templ_7745c5c3_Var48, 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: 358, Col: 43}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 368, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
if templ_7745c5c3_Err != nil {
@@ -1246,7 +1246,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var49 string
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(cardMeta(r))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 360, Col: 39}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 370, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil {
@@ -1269,6 +1269,189 @@ func processingCard(r store.SummaryRow) templ.Component {
})
}
+// waitingCard is the honest rate-limited state: the click landed but YouTube is
+// throttling the caption fetch, so the tapir rests and the card keeps polling
+// (gently, every 30s) until the background retry lands the summary — the user
+// never has to click again. Replaces the old silent revert to a Summarize button.
+func waitingCard(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 {
+ 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_Var50 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var50 == nil {
+ templ_7745c5c3_Var50 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var53 string
+ templ_7745c5c3_Var53, 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: 388, Col: 43}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if cardMeta(r) != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var54 string
+ templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(cardMeta(r))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 390, Col: 39}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(tapirFrameHTML2).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, " Waiting on YouTube rate limits. Tapir keeps trying, slowly and politely, and the summary will appear here on its own.
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+// noCaptionsCard is the terminal no-captions state: nothing to summarize, so the
+// card stops (no poll, no button to click again into the same dead end).
+func noCaptionsCard(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 {
+ 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_Var55 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var55 == nil {
+ templ_7745c5c3_Var55 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var57 string
+ templ_7745c5c3_Var57, 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: 405, Col: 43}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if cardMeta(r) != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var58 string
+ templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(cardMeta(r))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 407, Col: 39}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "No captions available, so Tapir cannot summarize this one.
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
// DetailPage is the full summary view: text, highlights, takeaways, metadata,
// and the action button group.
func DetailPage(r store.SummaryRow) templ.Component {
@@ -1287,12 +1470,12 @@ func DetailPage(r store.SummaryRow) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var50 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var50 == nil {
- templ_7745c5c3_Var50 = templ.NopComponent
+ templ_7745c5c3_Var59 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var59 == nil {
+ templ_7745c5c3_Var59 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var51 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var60 := 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 {
@@ -1304,99 +1487,99 @@ func DetailPage(r store.SummaryRow) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "← Summaries
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "← Summaries
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var52 string
- templ_7745c5c3_Var52, templ_7745c5c3_Err = templ.JoinStringErrs(displayTitle(r))
+ 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: 372, Col: 24}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 419, Col: 24}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var52))
+ _, 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, 108, " ")
+ 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, 109, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var53 string
- templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(detailMeta(r))
+ 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: 375, Col: 26}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 422, Col: 26}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
+ _, 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, 110, " ")
+ 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, 111, "fallback ")
+ 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, 112, "
")
+ 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, 113, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "\" loading=\"lazy\" referrerpolicy=\"strict-origin-when-cross-origin\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if r.URL != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "watch on source ↗
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "\" rel=\"noopener noreferrer\">watch on source ↗")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1406,83 +1589,83 @@ func DetailPage(r store.SummaryRow) templ.Component {
return templ_7745c5c3_Err
}
if len(r.Takeaways) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "Takeaways ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "Takeaways ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, t := range r.Takeaways {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var57 string
- templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(t)
+ var templ_7745c5c3_Var66 string
+ templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(t)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 406, Col: 14}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 453, Col: 14}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
+ _, 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, 120, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(r.Highlights) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "Highlights ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "Highlights ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, h := range r.Highlights {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var58 string
- templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(h)
+ var templ_7745c5c3_Var67 string
+ templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(h)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 416, Col: 14}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 463, Col: 14}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
+ _, 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, 124, " ")
+ 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, 125, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "Summary ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "Summary ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var59 string
- templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinStringErrs(r.Summary)
+ var templ_7745c5c3_Var68 string
+ templ_7745c5c3_Var68, 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: 423, Col: 31}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 470, Col: 31}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
+ _, 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, 127, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var51), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout("Tapir — "+displayTitle(r)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var60), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1509,12 +1692,12 @@ func RegisterPage(email, errMsg string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var60 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var60 == nil {
- templ_7745c5c3_Var60 = templ.NopComponent
+ templ_7745c5c3_Var69 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var69 == nil {
+ templ_7745c5c3_Var69 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var61 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var70 := 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 {
@@ -1526,59 +1709,59 @@ func RegisterPage(email, errMsg string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "Complete your registration ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "Complete your registration ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if email != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "Signed in as ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "
Signed in as ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var62 string
- templ_7745c5c3_Var62, templ_7745c5c3_Err = templ.JoinStringErrs(email)
+ var templ_7745c5c3_Var71 string
+ templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 437, Col: 40}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 484, Col: 40}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var62))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, ".
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, ".")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "Choose a display name to finish setting up your Tapir account.
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "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, 132, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var63 string
- templ_7745c5c3_Var63, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
+ var templ_7745c5c3_Var72 string
+ templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 441, Col: 42}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 488, Col: 42}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var63))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
- templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var61), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout("Tapir — Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var70), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1606,12 +1789,12 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var64 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var64 == nil {
- templ_7745c5c3_Var64 = templ.NopComponent
+ templ_7745c5c3_Var73 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var73 == nil {
+ templ_7745c5c3_Var73 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var65 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var74 := 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 {
@@ -1627,43 +1810,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, 135, " Account Display name ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, " Account Display name ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var66 string
- templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(displayNameOr(displayName))
+ var templ_7745c5c3_Var75 string
+ templ_7745c5c3_Var75, 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: 465, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 512, Col: 36}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var75))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if email != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "Signed in as ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, " Signed in as ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var67 string
- templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(email)
+ var templ_7745c5c3_Var76 string
+ templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 468, Col: 16}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 515, Col: 16}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
+ _, 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, 138, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "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, 153, "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
}
@@ -1671,178 +1854,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, 140, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(channelErrors) > 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "Unavailable channels ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "Unavailable channels ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var68 string
- templ_7745c5c3_Var68, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d channel(s) returned errors on the last discovery pass.", len(channelErrors)))
+ var templ_7745c5c3_Var77 string
+ templ_7745c5c3_Var77, 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: 485, Col: 100}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 532, Col: 100}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
+ _, 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, 142, " These may have been deleted or made private on YouTube.
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, " 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, 143, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var69 string
- templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(ce.ChannelName)
+ var templ_7745c5c3_Var78 string
+ templ_7745c5c3_Var78, 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: 491, Col: 57}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 538, Col: 57}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
+ _, 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, 144, " unavailable since ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, " unavailable since ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var70 string
- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(ce.FirstSeen.Format("2006-01-02"))
+ var templ_7745c5c3_Var79 string
+ templ_7745c5c3_Var79, 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: 493, Col: 89}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 540, Col: 89}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
+ _, 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, 145, " ")
+ 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, 146, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "Connected accounts ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "Connected accounts ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(conns) == 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, "No connected video accounts yet.
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "No connected video accounts yet.
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, c := range conns {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var71 string
- templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider))
+ var templ_7745c5c3_Var80 string
+ templ_7745c5c3_Var80, 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: 508, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 555, Col: 64}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
+ _, 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, 151, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if c.ProviderAccount != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var72 string
- templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(c.ProviderAccount)
+ var templ_7745c5c3_Var81 string
+ templ_7745c5c3_Var81, 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: 510, Col: 49}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 557, Col: 49}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
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, 167, " ")
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, 168, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var73 string
- templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status)
+ var templ_7745c5c3_Var82 string
+ templ_7745c5c3_Var82, 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: 512, Col: 38}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 559, Col: 38}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, "
connected ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "
connected ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var74 string
- templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(c.ConnectedAt.Format("2006-01-02"))
+ var templ_7745c5c3_Var83 string
+ templ_7745c5c3_Var83, 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: 514, Col: 83}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 561, Col: 83}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "\">Disconnect ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !hasYouTube(conns) {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "Connect YouTube
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "Connect YouTube
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, "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, 174, "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_Var65), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout("Tapir — Account").Render(templ.WithChildren(ctx, templ_7745c5c3_Var74), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1870,51 +2053,51 @@ func summarizeModeControl(auto bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var76 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var76 == nil {
- templ_7745c5c3_Var76 = templ.NopComponent
+ templ_7745c5c3_Var85 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var85 == nil {
+ templ_7745c5c3_Var85 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "Current mode: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "Current mode: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var77 string
- templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto))
+ var templ_7745c5c3_Var86 string
+ templ_7745c5c3_Var86, 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: 552, Col: 53}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 599, Col: 53}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
+ _, 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, 162, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1942,38 +2125,38 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var80 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var80 == nil {
- templ_7745c5c3_Var80 = templ.NopComponent
+ templ_7745c5c3_Var89 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var89 == nil {
+ templ_7745c5c3_Var89 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -2019,81 +2202,81 @@ func actionButton(verb string, isActive bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var83 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var83 == nil {
- templ_7745c5c3_Var83 = templ.NopComponent
+ templ_7745c5c3_Var92 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var92 == nil {
+ templ_7745c5c3_Var92 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var84 = []any{"action", templ.KV("active", isActive)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var84...)
+ var templ_7745c5c3_Var93 = []any{"action", templ.KV("active", isActive)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var93...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if isActive {
- var templ_7745c5c3_Var88 string
- templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs("✓ " + actionLabel(verb))
+ var templ_7745c5c3_Var97 string
+ templ_7745c5c3_Var97, 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: 602, Col: 31}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 649, Col: 31}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var97))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- var templ_7745c5c3_Var89 string
- templ_7745c5c3_Var89, templ_7745c5c3_Err = templ.JoinStringErrs(actionLabel(verb))
+ var templ_7745c5c3_Var98 string
+ templ_7745c5c3_Var98, 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: 604, Col: 22}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 651, Col: 22}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var89))
+ _, 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, 174, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}