diff --git a/internal/adapters/store/channel_errors.go b/internal/adapters/store/channel_errors.go
new file mode 100644
index 0000000..fef00a9
--- /dev/null
+++ b/internal/adapters/store/channel_errors.go
@@ -0,0 +1,59 @@
+package store
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/jackc/pgx/v5"
+)
+
+// ChannelError is a channel that returned HTTP 404 on a discovery pass.
+type ChannelError struct {
+ ChannelID string
+ ChannelName string
+ FirstSeen time.Time
+ LastSeen time.Time
+}
+
+// UpsertChannelError records (or refreshes) a 404 channel for the current user.
+// Called by the runner inside a withUser scope; RLS guards user isolation.
+func (s *Store) UpsertChannelError(ctx context.Context, userID, channelID, channelName string) error {
+ return s.withUser(ctx, userID, func(tx pgx.Tx) error {
+ _, err := tx.Exec(ctx, `
+ INSERT INTO channel_errors (user_id, channel_id, channel_name)
+ VALUES ($1, $2, $3)
+ ON CONFLICT (user_id, channel_id)
+ DO UPDATE SET channel_name = EXCLUDED.channel_name, last_seen = now()`,
+ userID, channelID, channelName)
+ if err != nil {
+ return fmt.Errorf("store: upsert channel error: %w", err)
+ }
+ return nil
+ })
+}
+
+// ListChannelErrors returns all 404-flagged channels for the user, newest first.
+func (s *Store) ListChannelErrors(ctx context.Context, userID string) ([]ChannelError, error) {
+ var out []ChannelError
+ err := s.withUser(ctx, userID, func(tx pgx.Tx) error {
+ rows, err := tx.Query(ctx, `
+ SELECT channel_id, channel_name, first_seen, last_seen
+ FROM channel_errors
+ WHERE user_id = $1
+ ORDER BY last_seen DESC`, userID)
+ if err != nil {
+ return fmt.Errorf("store: list channel errors: %w", err)
+ }
+ defer rows.Close()
+ for rows.Next() {
+ var ce ChannelError
+ if err := rows.Scan(&ce.ChannelID, &ce.ChannelName, &ce.FirstSeen, &ce.LastSeen); err != nil {
+ return fmt.Errorf("store: scan channel error: %w", err)
+ }
+ out = append(out, ce)
+ }
+ return rows.Err()
+ })
+ return out, err
+}
diff --git a/internal/adapters/store/migrate_test.go b/internal/adapters/store/migrate_test.go
index 1777830..37f1935 100644
--- a/internal/adapters/store/migrate_test.go
+++ b/internal/adapters/store/migrate_test.go
@@ -53,7 +53,9 @@ func TestMigration010LoginEventsUpDown(t *testing.T) {
require.True(t, loginEventsExists(t), "login_events must exist at latest migration")
m := fileMigrator(t)
- // 011 and 012 sit above 010; step them down first so 010 is exercised in isolation.
+ // 011, 012, 013 sit above 010; step them down first so 010 is exercised in isolation.
+ require.NoError(t, m.Steps(-1), "down 013 drops channel_errors, login_events intact")
+ require.True(t, loginEventsExists(t), "013 down leaves login_events intact")
require.NoError(t, m.Steps(-1), "down 012 is a no-op, login_events intact")
require.True(t, loginEventsExists(t), "012 down leaves login_events intact")
require.NoError(t, m.Steps(-1), "down 011 must not touch login_events")
@@ -62,7 +64,7 @@ func TestMigration010LoginEventsUpDown(t *testing.T) {
require.NoError(t, m.Steps(-1), "down 010 must drop login_events")
require.False(t, loginEventsExists(t), "login_events must be gone after the down migration")
- require.NoError(t, m.Steps(3), "up must recreate 010 then re-apply 011 and 012")
+ require.NoError(t, m.Steps(4), "up must recreate 010 then re-apply 011, 012, 013")
require.True(t, loginEventsExists(t), "login_events must be restored after the up migration")
}
@@ -81,10 +83,11 @@ func autoSummarizeDefault(t *testing.T) string {
// up sets the auto_summarize column default to TRUE (ADR-018), down restores
// FALSE. The down intentionally does not revert existing rows — only the default.
func TestMigration011AutoSummarizeDefaultUpDown(t *testing.T) {
- newStore(t) // latest (012 applied)
+ newStore(t) // latest (013 applied)
require.Equal(t, "true", autoSummarizeDefault(t), "011 sets the default to TRUE")
m := fileMigrator(t)
+ require.NoError(t, m.Steps(-1), "down 013 drops channel_errors")
require.NoError(t, m.Steps(-1), "down 012 is a no-op")
require.NoError(t, m.Steps(-1), "down 011 reverts the column default")
require.Equal(t, "false", autoSummarizeDefault(t), "default is FALSE after the down migration")
@@ -92,6 +95,7 @@ func TestMigration011AutoSummarizeDefaultUpDown(t *testing.T) {
require.NoError(t, m.Steps(1), "up 011 re-applies the TRUE default")
require.Equal(t, "true", autoSummarizeDefault(t))
require.NoError(t, m.Steps(1), "up 012 runs clean (no FORCE RLS on fresh schema)")
+ require.NoError(t, m.Steps(1), "up 013 creates channel_errors")
}
// TestMigration012FixAutoSummarizeRLS proves 012 runs cleanly and flips any
diff --git a/internal/adapters/store/migrations/013_channel_errors.down.sql b/internal/adapters/store/migrations/013_channel_errors.down.sql
new file mode 100644
index 0000000..6d9b98d
--- /dev/null
+++ b/internal/adapters/store/migrations/013_channel_errors.down.sql
@@ -0,0 +1 @@
+DROP TABLE IF EXISTS channel_errors;
diff --git a/internal/adapters/store/migrations/013_channel_errors.up.sql b/internal/adapters/store/migrations/013_channel_errors.up.sql
new file mode 100644
index 0000000..2389bc2
--- /dev/null
+++ b/internal/adapters/store/migrations/013_channel_errors.up.sql
@@ -0,0 +1,22 @@
+-- channel_errors: channels that returned HTTP 404 (deleted/private) on the most
+-- recent scheduler pass. Surfaced on the account page so users know why some
+-- subscribed channels produce no videos. Upserted per-pass; cleared when the
+-- channel starts returning results again (runner calls UpsertChannelError only
+-- on 404, so a recovered channel simply stops appearing after its row ages out
+-- or the user takes action). ON DELETE CASCADE keeps rows tidy on account deletion.
+CREATE TABLE channel_errors (
+ user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ channel_id TEXT NOT NULL,
+ channel_name TEXT NOT NULL,
+ first_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
+ last_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
+ PRIMARY KEY (user_id, channel_id)
+);
+
+CREATE INDEX idx_channel_errors_user_id ON channel_errors(user_id);
+
+ALTER TABLE channel_errors ENABLE ROW LEVEL SECURITY;
+ALTER TABLE channel_errors FORCE ROW LEVEL SECURITY;
+CREATE POLICY channel_errors_isolation ON channel_errors
+ FOR ALL
+ USING (user_id = current_setting('tapir.current_user_id', true)::uuid);
diff --git a/internal/adapters/youtube/youtube.go b/internal/adapters/youtube/youtube.go
index 21ea9f8..361d815 100644
--- a/internal/adapters/youtube/youtube.go
+++ b/internal/adapters/youtube/youtube.go
@@ -216,6 +216,9 @@ func (a *Adapter) NewVideos(ctx context.Context, sub domain.Subscription) ([]dom
var resp playlistItemListResponse
if err := a.getJSON(ctx, client, "/playlistItems", q, &resp); err != nil {
+ if isHTTP404(err) {
+ return nil, &domain.ErrChannelUnavailable{ChannelID: sub.ChannelID, ChannelTitle: sub.ChannelTitle}
+ }
return nil, fmt.Errorf("new videos for channel %q: %w", sub.ChannelID, err)
}
@@ -269,6 +272,12 @@ func (a *Adapter) resolveUploadsPlaylist(ctx context.Context, client *http.Clien
return resp.Items[0].ContentDetails.RelatedPlaylists.Uploads, nil
}
+// isHTTP404 reports whether err came from a YouTube API call that returned HTTP 404.
+// getRaw encodes the status as "youtube api : status 404: ...".
+func isHTTP404(err error) bool {
+ return err != nil && strings.Contains(err.Error(), "status 404")
+}
+
// getJSON issues a GET and decodes a JSON body into out. A non-200 status is an
// error carrying a bounded slice of the response body for diagnosis.
func (a *Adapter) getJSON(ctx context.Context, client *http.Client, path string, q url.Values, out any) error {
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index 438c963..64e0f2c 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -2,7 +2,22 @@
// the standard library — no providers, no storage, no AI. See docs/data-model.md.
package domain
-import "time"
+import (
+ "fmt"
+ "time"
+)
+
+// ErrChannelUnavailable is returned by a VideoSource when a channel's upload
+// playlist returns HTTP 404 — the channel was deleted or made private. The runner
+// stores these so the account page can surface them to the user.
+type ErrChannelUnavailable struct {
+ ChannelID string
+ ChannelTitle string
+}
+
+func (e *ErrChannelUnavailable) Error() string {
+ return fmt.Sprintf("channel %q (%s) unavailable: playlist not found", e.ChannelTitle, e.ChannelID)
+}
// Provider identifies a video platform.
type Provider string
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index b85b504..4e4fcae 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -38,6 +38,10 @@ type VideoStore interface {
// SetTranscriptStatus records the outcome of a transcript attempt: "none",
// "rate_limited" (stamps the backoff clock), or "fetched".
SetTranscriptStatus(ctx context.Context, userID, videoID, status string) error
+ // UpsertChannelError records a channel that returned HTTP 404 (deleted/private).
+ // Called when NewVideos returns domain.ErrChannelUnavailable; best-effort, errors
+ // are logged and never abort the pass.
+ UpsertChannelError(ctx context.Context, userID, channelID, channelTitle string) error
}
// Processor runs the core use case for a single video. *usecase.Engine
@@ -94,6 +98,7 @@ type Stats struct {
SkippedManual int // discovered but not queued, in manual mode
SkippedRateLimited int // 429'd previously and still inside the backoff window
Errors int
+ ChannelUnavailable int // channels that returned HTTP 404 (deleted/private)
}
// RunOnce performs a single pass over the user's subscriptions. Per-item errors
@@ -152,8 +157,17 @@ func (r *Runner) RunOnce(ctx context.Context) (Stats, error) {
for _, sub := range subs {
vids, err := r.src.NewVideos(ctx, sub)
if err != nil {
- errs = append(errs, fmt.Errorf("new videos for %q: %w", sub.ChannelTitle, err))
- stats.Errors++
+ var unavail *domain.ErrChannelUnavailable
+ if errors.As(err, &unavail) {
+ stats.ChannelUnavailable++
+ r.log.Warn("channel unavailable (playlist 404)", "channel", sub.ChannelTitle, "channel_id", sub.ChannelID)
+ if storeErr := r.store.UpsertChannelError(ctx, r.userID, unavail.ChannelID, unavail.ChannelTitle); storeErr != nil {
+ r.log.Warn("failed to store channel error", "err", storeErr)
+ }
+ } else {
+ errs = append(errs, fmt.Errorf("new videos for %q: %w", sub.ChannelTitle, err))
+ stats.Errors++
+ }
continue
}
for _, v := range vids {
@@ -251,7 +265,7 @@ func (r *Runner) Loop(ctx context.Context, interval time.Duration) error {
"candidates", stats.Candidates, "summarized", stats.Summarized,
"skipped_seen", stats.SkippedSeen, "skipped_no_text", stats.SkippedNoText,
"skipped_manual", stats.SkippedManual, "skipped_rate_limited", stats.SkippedRateLimited,
- "errors", stats.Errors)
+ "channel_unavailable", stats.ChannelUnavailable, "errors", stats.Errors)
if err != nil {
r.log.Warn("run pass had errors", "err", err)
}
diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go
index 97527d2..62928a0 100644
--- a/internal/runner/runner_test.go
+++ b/internal/runner/runner_test.go
@@ -91,6 +91,8 @@ func (f *fakeStore) RateLimitedVideoIDs(_ context.Context, _ string) (map[string
return cp, nil
}
+func (f *fakeStore) UpsertChannelError(_ context.Context, _, _, _ string) error { return nil }
+
func (f *fakeStore) SetTranscriptStatus(_ context.Context, _, videoID, status string) error {
if f.statuses == nil {
f.statuses = map[string]string{}
diff --git a/internal/web/account.go b/internal/web/account.go
index e099523..10d222d 100644
--- a/internal/web/account.go
+++ b/internal/web/account.go
@@ -33,7 +33,12 @@ func (a *App) handleAccount(w http.ResponseWriter, r *http.Request) {
a.serverError(w, r, "summarize mode", err)
return
}
- a.render(w, r, AccountPage(name, email, conns, auto, takeFlash(w, r)))
+ channelErrs, err := a.Store.ListChannelErrors(r.Context(), userID)
+ if err != nil {
+ a.serverError(w, r, "channel errors", err)
+ return
+ }
+ a.render(w, r, AccountPage(name, email, conns, auto, channelErrs, takeFlash(w, r)))
}
// handleDisconnect removes a provider connection: it deletes the OAuth token from
diff --git a/internal/web/handlers.go b/internal/web/handlers.go
index d3ea6ae..d8e8f7c 100644
--- a/internal/web/handlers.go
+++ b/internal/web/handlers.go
@@ -34,6 +34,9 @@ type Store interface {
DeleteConnection(ctx context.Context, userID, provider string) error
DeleteUser(ctx context.Context, userID string) error
DisplayName(ctx context.Context, userID string) (string, error)
+ // ListChannelErrors returns channels that returned HTTP 404 (deleted/private) on
+ // the most recent discovery pass, shown on the account page as a warning.
+ ListChannelErrors(ctx context.Context, userID string) ([]store.ChannelError, error)
// StampLogin records (throttled, one row per user per day) that the resolved
// user was active on this request — the read-side Stage-0 usage signal the
diff --git a/internal/web/view.go b/internal/web/view.go
index 038b8dd..3fbad6a 100644
--- a/internal/web/view.go
+++ b/internal/web/view.go
@@ -503,6 +503,7 @@ a.btn, a.btn:visited { color: var(--accent-fg); }
/* passive "retrying later" chip: dim/grey (CharmDim), not the accent — it is a
status, not an action the user can take. */
.chip-retry { background: rgba(108, 108, 108, .16); color: #6c6c6c; }
+.chip-warn { background: rgba(255, 110, 156, .15); color: #FF6E9C; }
.card-state { color: var(--muted); font-size: .8rem; }
.badge { display: inline-block; padding: .15rem .55rem; border-radius: 999px; background: var(--badge-bg); color: var(--badge-fg); font-size: .72rem; font-weight: 600; }
@@ -591,6 +592,11 @@ a.btn, a.btn:visited { color: var(--accent-fg); }
.account-meta { display: grid; grid-template-columns: max-content 1fr; gap: var(--s1) var(--s3); margin: 0; }
.account-meta dt { color: var(--muted); font-size: .85rem; }
.account-meta dd { margin: 0; }
+.channel-errors { }
+.channel-error-list { list-style: none; margin: 0 0 var(--s3); padding: 0; display: grid; gap: var(--s1); }
+.channel-error-list li { display: flex; align-items: center; gap: var(--s2); }
+.channel-error-name { font-weight: 500; }
+.channel-error-since { font-size: .8rem; }
.conn-list { list-style: none; margin: 0 0 var(--s3); padding: 0; display: grid; gap: var(--s2); }
.conn { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s3); display: flex; flex-direction: column; gap: var(--s1); }
.conn-main { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
diff --git a/internal/web/views.templ b/internal/web/views.templ
index e3ff521..c9e3d71 100644
--- a/internal/web/views.templ
+++ b/internal/web/views.templ
@@ -1,6 +1,7 @@
package web
import (
+ "fmt"
"strings"
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
@@ -397,7 +398,7 @@ templ InviteNoticePage(message string, showLogin bool) {
// signed-in email, the user's connected video accounts (each with a Disconnect
// control), a Connect-YouTube link when none is connected, and the delete-account
// danger zone. flash surfaces a one-shot notification (disconnect/connect).
-templ AccountPage(displayName, email string, conns []store.Connection, autoSummarize bool, flash string) {
+templ AccountPage(displayName, email string, conns []store.Connection, autoSummarize bool, channelErrors []store.ChannelError, flash string) {
@Layout("Tapir — Account") {
@flashBanner(flash)
@@ -419,6 +420,24 @@ templ AccountPage(displayName, email string, conns []store.Connection, autoSumma
@summarizeModeControl(autoSummarize)
+ if len(channelErrors) > 0 {
+
+ Unavailable channels
+
+ { fmt.Sprintf("%d channel(s) returned errors on the last discovery pass.", len(channelErrors)) }
+ These may have been deleted or made private on YouTube.
+
+
+ for _, ce := range channelErrors {
+ -
+ { ce.ChannelName }
+ unavailable
+ since { ce.FirstSeen.Format("2006-01-02") }
+
+ }
+
+
+ }
Connected accounts
if len(conns) == 0 {
diff --git a/internal/web/views_templ.go b/internal/web/views_templ.go
index a286bef..3025654 100644
--- a/internal/web/views_templ.go
+++ b/internal/web/views_templ.go
@@ -9,6 +9,7 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
+ "fmt"
"strings"
"gitea.d-ma.be/mathias/tapir/internal/adapters/store"
@@ -45,7 +46,7 @@ func Layout(title string) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 18, Col: 17}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 19, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -106,7 +107,7 @@ func PublicLayout(title string) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 43, Col: 17}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 44, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -198,7 +199,7 @@ func WelcomePage(user User, loggedIn bool) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 71, Col: 57}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 72, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@@ -284,7 +285,7 @@ func flashBanner(code string) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(f.Message)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 98, Col: 88}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 99, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -398,7 +399,7 @@ func filterForm(f Filter) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.Channel)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 126, Col: 68}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 127, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
@@ -411,7 +412,7 @@ func filterForm(f Filter) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.From)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 127, Col: 59}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 128, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16)
if templ_7745c5c3_Err != nil {
@@ -424,7 +425,7 @@ func filterForm(f Filter) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(f.To)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 128, Col: 53}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 129, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
if templ_7745c5c3_Err != nil {
@@ -545,7 +546,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, 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: 166, Col: 88}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 167, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var22)
if templ_7745c5c3_Err != nil {
@@ -563,7 +564,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var23 templ.SafeURL
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(videoURL(r.VideoID))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 168, Col: 56}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 169, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@@ -576,7 +577,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, 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: 168, Col: 76}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 169, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
@@ -594,7 +595,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, 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: 170, Col: 44}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 171, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@@ -613,7 +614,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, 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: 173, Col: 39}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 174, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
@@ -633,7 +634,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(p)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 177, Col: 33}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 178, Col: 33}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
@@ -658,7 +659,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(r.AIProvider)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 183, Col: 38}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 184, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
@@ -691,7 +692,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(r.Actions, ", "))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 189, Col: 61}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 190, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
@@ -720,7 +721,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var30 templ.SafeURL
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(summarizeURL(r.VideoID))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 199, Col: 37}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 200, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
@@ -733,7 +734,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(string(summarizeURL(r.VideoID)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 200, Col: 46}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 201, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31)
if templ_7745c5c3_Err != nil {
@@ -746,7 +747,7 @@ func VideoCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, 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: 201, Col: 38}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 202, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
if templ_7745c5c3_Err != nil {
@@ -822,7 +823,7 @@ func TapirSpinner() templ.Component {
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("color:" + CharmMint)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 221, Col: 82}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 222, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
@@ -835,7 +836,7 @@ func TapirSpinner() templ.Component {
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(tapirBarFill)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 221, Col: 99}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 222, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
@@ -882,7 +883,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var37 string
templ_7745c5c3_Var37, 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: 234, Col: 27}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 235, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var37)
if templ_7745c5c3_Err != nil {
@@ -895,7 +896,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var38 string
templ_7745c5c3_Var38, 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: 235, Col: 39}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 236, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38)
if templ_7745c5c3_Err != nil {
@@ -908,7 +909,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, 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: 239, Col: 43}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 240, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
@@ -926,7 +927,7 @@ func processingCard(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, 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: 241, Col: 39}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 242, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
if templ_7745c5c3_Err != nil {
@@ -991,7 +992,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var43 string
templ_7745c5c3_Var43, 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: 252, Col: 24}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 253, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
if templ_7745c5c3_Err != nil {
@@ -1009,7 +1010,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var44 string
templ_7745c5c3_Var44, 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: 255, Col: 26}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 256, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
if templ_7745c5c3_Err != nil {
@@ -1038,7 +1039,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var45 string
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.ResolveAttributeValue(url)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 264, Col: 15}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 265, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var45)
if templ_7745c5c3_Err != nil {
@@ -1051,7 +1052,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var46 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.ResolveAttributeValue(displayTitle(r))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 265, Col: 29}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 266, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var46)
if templ_7745c5c3_Err != nil {
@@ -1070,7 +1071,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var47 templ.SafeURL
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinURLErrs(externalURL(r.URL))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 274, Col: 50}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 275, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
if templ_7745c5c3_Err != nil {
@@ -1092,7 +1093,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var48 string
templ_7745c5c3_Var48, 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: 279, Col: 31}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 280, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
if templ_7745c5c3_Err != nil {
@@ -1115,7 +1116,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var49 string
templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(h)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 286, Col: 14}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 287, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
if templ_7745c5c3_Err != nil {
@@ -1144,7 +1145,7 @@ func DetailPage(r store.SummaryRow) templ.Component {
var templ_7745c5c3_Var50 string
templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(t)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 296, Col: 14}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 297, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil {
@@ -1222,7 +1223,7 @@ func RegisterPage(email, errMsg string) templ.Component {
var templ_7745c5c3_Var53 string
templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 313, Col: 40}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 314, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53))
if templ_7745c5c3_Err != nil {
@@ -1245,7 +1246,7 @@ func RegisterPage(email, errMsg string) templ.Component {
var templ_7745c5c3_Var54 string
templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 317, Col: 42}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 318, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54))
if templ_7745c5c3_Err != nil {
@@ -1316,7 +1317,7 @@ func InvitePage(email, token, errMsg string) templ.Component {
var templ_7745c5c3_Var57 string
templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 344, Col: 41}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 345, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
if templ_7745c5c3_Err != nil {
@@ -1334,7 +1335,7 @@ func InvitePage(email, token, errMsg string) templ.Component {
var templ_7745c5c3_Var58 string
templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 347, Col: 42}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 348, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
if templ_7745c5c3_Err != nil {
@@ -1352,7 +1353,7 @@ func InvitePage(email, token, errMsg string) templ.Component {
var templ_7745c5c3_Var59 templ.SafeURL
templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinURLErrs(inviteURL(token))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 349, Col: 48}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 350, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
if templ_7745c5c3_Err != nil {
@@ -1365,7 +1366,7 @@ func InvitePage(email, token, errMsg string) templ.Component {
var templ_7745c5c3_Var60 string
templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.ResolveAttributeValue(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 352, Col: 51}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 353, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var60)
if templ_7745c5c3_Err != nil {
@@ -1478,7 +1479,7 @@ func InviteNoticePage(message string, showLogin bool) templ.Component {
var templ_7745c5c3_Var65 string
templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinStringErrs(message)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 388, Col: 15}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 389, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
if templ_7745c5c3_Err != nil {
@@ -1512,7 +1513,7 @@ func InviteNoticePage(message string, showLogin bool) templ.Component {
// signed-in email, the user's connected video accounts (each with a Disconnect
// control), a Connect-YouTube link when none is connected, and the delete-account
// danger zone. flash surfaces a one-shot notification (disconnect/connect).
-func AccountPage(displayName, email string, conns []store.Connection, autoSummarize bool, flash string) templ.Component {
+func AccountPage(displayName, email string, conns []store.Connection, autoSummarize bool, channelErrors []store.ChannelError, flash 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 {
@@ -1556,7 +1557,7 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
var templ_7745c5c3_Var68 string
templ_7745c5c3_Var68, 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: 407, Col: 36}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 408, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var68))
if templ_7745c5c3_Err != nil {
@@ -1574,7 +1575,7 @@ func AccountPage(displayName, email string, conns []store.Connection, autoSummar
var templ_7745c5c3_Var69 string
templ_7745c5c3_Var69, templ_7745c5c3_Err = templ.JoinStringErrs(email)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 410, Col: 16}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 411, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var69))
if templ_7745c5c3_Err != nil {
@@ -1593,113 +1594,172 @@ 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, 117, "Connected accounts
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if len(conns) == 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "No connected video accounts yet.
")
+ if len(channelErrors) > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "Unavailable channels
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, "
")
+ var templ_7745c5c3_Var70 string
+ templ_7745c5c3_Var70, 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: 427, Col: 100}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- for _, c := range conns {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, " 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, 120, "- ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var70 string
- templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinStringErrs(providerLabel(c.Provider))
+ var templ_7745c5c3_Var71 string
+ templ_7745c5c3_Var71, 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: 431, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 433, Col: 57}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
+ _, 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, 121, " ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if c.ProviderAccount != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var71 string
- templ_7745c5c3_Var71, 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: 433, Col: 49}
- }
- _, 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, 123, " ")
- 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, 121, " unavailable since ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var72 string
- templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(c.Status)
+ templ_7745c5c3_Var72, 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: 435, Col: 38}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 435, Col: 89}
}
_, 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, 125, "
connected ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var76 string
+ templ_7745c5c3_Var76, 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: 456, Col: 83}
+ }
+ _, 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, 133, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if !hasYouTube(conns) {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "Connect YouTube
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "Connect YouTube
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, "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, 137, "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
}
@@ -1733,51 +1793,51 @@ func summarizeModeControl(auto bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var75 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var75 == nil {
- templ_7745c5c3_Var75 = templ.NopComponent
+ templ_7745c5c3_Var78 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var78 == nil {
+ templ_7745c5c3_Var78 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, "Current mode: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "Current mode: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var76 string
- templ_7745c5c3_Var76, templ_7745c5c3_Err = templ.JoinStringErrs(summarizeModeLabel(auto))
+ var templ_7745c5c3_Var79 string
+ templ_7745c5c3_Var79, 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: 475, Col: 53}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/views.templ`, Line: 494, Col: 53}
}
- _, 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, 132, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -1805,117 +1865,117 @@ func ActionButtons(videoID string, active map[string]bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var79 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var79 == nil {
- templ_7745c5c3_Var79 = templ.NopComponent
+ templ_7745c5c3_Var82 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var82 == nil {
+ templ_7745c5c3_Var82 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}