diff --git a/docs/ux-review/fixes/01-landing-fixed.png b/docs/ux-review/fixes/01-landing-fixed.png new file mode 100644 index 0000000..4e2e86b Binary files /dev/null and b/docs/ux-review/fixes/01-landing-fixed.png differ diff --git a/docs/ux-review/fixes/02-empty-connected.png b/docs/ux-review/fixes/02-empty-connected.png new file mode 100644 index 0000000..16308c9 Binary files /dev/null and b/docs/ux-review/fixes/02-empty-connected.png differ diff --git a/docs/ux-review/fixes/03-empty-fresh.png b/docs/ux-review/fixes/03-empty-fresh.png new file mode 100644 index 0000000..c88dcc4 Binary files /dev/null and b/docs/ux-review/fixes/03-empty-fresh.png differ diff --git a/internal/domain/domain.go b/internal/domain/domain.go index 8b0f1eb..438c963 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -18,6 +18,12 @@ type TranscriptSource string const ( SourceCaptions TranscriptSource = "captions" SourceNone TranscriptSource = "none" + // SourceRateLimited records that the caption endpoint returned HTTP 429. + // Unlike SourceNone (a permanent absence), this is a transient "retry later": + // the IP is rate-limited, not the video caption-less. It carries no text + // (HasText is false), so the engine degrades the same as SourceNone, but the + // runner persists it distinctly to retry after a backoff window. + SourceRateLimited TranscriptSource = "rate_limited" ) // User is the Tapir-side profile. At Stage 0 there is exactly one. diff --git a/internal/web/view.go b/internal/web/view.go index 893b966..f00718c 100644 --- a/internal/web/view.go +++ b/internal/web/view.go @@ -535,6 +535,11 @@ a.btn, a.btn:visited { color: var(--accent-fg); } .empty { text-align: center; color: var(--muted); padding: var(--s5) var(--s4); border: 1px dashed var(--line); border-radius: var(--radius); background: var(--card); } .empty strong { display: block; color: var(--fg); font-size: 1.05rem; margin-bottom: var(--s2); } .empty code { background: var(--accent-weak); color: var(--accent); padding: .1rem .35rem; border-radius: .3rem; } +.empty p { margin: var(--s3) 0 0; } +/* connected-but-empty: a distinct accent callout, not a muted blank state, so a + fresh account knows the next step is to run tapir, not "something is broken". */ +.empty-connected { border-style: solid; border-color: var(--accent); background: var(--accent-weak); color: var(--fg); } +.empty-connected strong { color: var(--accent); } /* flash / notification banner */ .flash { padding: var(--s2) var(--s3); border-radius: var(--radius); margin-bottom: var(--s4); font-size: .92rem; border: 1px solid var(--line); } diff --git a/internal/web/views.templ b/internal/web/views.templ index e6af114..a25f1ee 100644 --- a/internal/web/views.templ +++ b/internal/web/views.templ @@ -79,12 +79,12 @@ templ flashBanner(code string) { // #summary-list region; a non-HTMX request renders the whole page. flash carries // a one-shot notification (e.g. "connected", "registered") surfaced on arrival // after a POST→redirect. -templ ListPage(rows []store.SummaryRow, f Filter, flash string) { +templ ListPage(rows []store.SummaryRow, f Filter, flash string, hasConnected bool) { @Layout("Tapir — Summaries") { @flashBanner(flash) @filterForm(f)
tapir run to fetch them. In manual mode, use the Summarize button to queue one.
- tapir run to discover your subscriptions. Videos will appear here once discovered. In manual mode, each new video gets a Summarize button.
+