feat(web): unify card states — one 'Summarize now' verb, honest no-captions state
Five explicit footer states, status-primary:
1. Summarized — chip + actions, no button (unchanged)
2. No captions (TranscriptStatus=="none") — NEW: 'No transcript available' muted
text, no button, no POST URL. Removes the dead-end 'Summarize' button that
tried and failed when there were no captions to fetch.
3. Queued (SummarizeRequested) — chip + muted text, no button (unchanged)
4. Rate-limited — 'Fetching soon…' + quiet 'Summarize now' → /retry-now
5. Pending — 'Not summarized' + quiet 'Summarize now' → /summarize
One verb ('Summarize now'), one quiet style (.btn-quiet, renamed from .btn-retry
which was state-specific). User doesn't see the internal pipeline distinction;
both buttons post to their existing handlers unchanged. Form class renamed
card-nudge-form. Dropped engineer-facing tooltip; user-facing hint added.
'Try now' wording removed entirely.
This commit is contained in:
@@ -17,9 +17,75 @@ func renderVideoCard(t *testing.T, r store.SummaryRow) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// A rate-limited, unsummarized video shows an active "Try now" button so the user
|
||||
// can manually trigger an immediate fetch through the shared rate gate.
|
||||
func TestVideoCard_RateLimitedShowsRetryingBadge(t *testing.T) {
|
||||
// noActionButton asserts the rendered HTML contains no form POST (no action URL
|
||||
// of the form /v/.../summarize or /v/.../retry-now) and no btn-quiet button.
|
||||
func noActionButton(t *testing.T, html, state string) {
|
||||
t.Helper()
|
||||
if strings.Contains(html, "/summarize") {
|
||||
t.Errorf("state %q: expected no summarize URL, got:\n%s", state, html)
|
||||
}
|
||||
if strings.Contains(html, "/retry-now") {
|
||||
t.Errorf("state %q: expected no retry-now URL, got:\n%s", state, html)
|
||||
}
|
||||
if strings.Contains(html, "btn-quiet") {
|
||||
t.Errorf("state %q: expected no action button, got:\n%s", state, html)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoCard_State1_Summarized — chip + no nudge button.
|
||||
func TestVideoCard_State1_Summarized(t *testing.T) {
|
||||
html := renderVideoCard(t, store.SummaryRow{
|
||||
VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
Title: "Done Video",
|
||||
Summarized: true,
|
||||
Summary: "A great talk about Go.",
|
||||
AIProvider: "local",
|
||||
})
|
||||
|
||||
if !strings.Contains(html, "local") {
|
||||
t.Errorf("state 1: expected AI provider chip, got:\n%s", html)
|
||||
}
|
||||
noActionButton(t, html, "summarized")
|
||||
if strings.Contains(html, "Summarize now") {
|
||||
t.Errorf("state 1: no nudge button on a summarized card, got:\n%s", html)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoCard_State2_NoTranscript — terminal; muted status, NO button, NO POST URL.
|
||||
func TestVideoCard_State2_NoTranscript(t *testing.T) {
|
||||
html := renderVideoCard(t, store.SummaryRow{
|
||||
VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
Title: "Silent Video",
|
||||
Summarized: false,
|
||||
TranscriptStatus: "none",
|
||||
})
|
||||
|
||||
if !strings.Contains(html, "No transcript available") {
|
||||
t.Errorf("state 2: expected 'No transcript available' text, got:\n%s", html)
|
||||
}
|
||||
noActionButton(t, html, "none-transcript")
|
||||
if strings.Contains(html, "Summarize now") {
|
||||
t.Errorf("state 2: no nudge button when there are no captions, got:\n%s", html)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoCard_State3_Queued — "Queued" chip, no button.
|
||||
func TestVideoCard_State3_Queued(t *testing.T) {
|
||||
html := renderVideoCard(t, store.SummaryRow{
|
||||
VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
Title: "Queued Video",
|
||||
Summarized: false,
|
||||
SummarizeRequested: true,
|
||||
})
|
||||
|
||||
if !strings.Contains(html, "Queued") {
|
||||
t.Errorf("state 3: expected 'Queued' chip, got:\n%s", html)
|
||||
}
|
||||
noActionButton(t, html, "queued")
|
||||
}
|
||||
|
||||
// TestVideoCard_State4_RateLimited — quiet status + "Summarize now" → retry-now URL.
|
||||
func TestVideoCard_State4_RateLimited(t *testing.T) {
|
||||
html := renderVideoCard(t, store.SummaryRow{
|
||||
VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
Title: "Throttled Video",
|
||||
@@ -27,29 +93,61 @@ func TestVideoCard_RateLimitedShowsRetryingBadge(t *testing.T) {
|
||||
TranscriptStatus: "rate_limited",
|
||||
})
|
||||
|
||||
if !strings.Contains(html, "Try now") {
|
||||
t.Errorf("expected a 'Try now' button, got:\n%s", html)
|
||||
if !strings.Contains(html, "Fetching soon") {
|
||||
t.Errorf("state 4: expected 'Fetching soon' status text, got:\n%s", html)
|
||||
}
|
||||
if !strings.Contains(html, "Summarize now") {
|
||||
t.Errorf("state 4: expected 'Summarize now' button, got:\n%s", html)
|
||||
}
|
||||
if !strings.Contains(html, "retry-now") {
|
||||
t.Errorf("expected the retry-now route in the form action, got:\n%s", html)
|
||||
t.Errorf("state 4: expected retry-now URL in form action, got:\n%s", html)
|
||||
}
|
||||
if strings.Contains(html, ">Summarize<") {
|
||||
t.Errorf("the Summarize button must be hidden for a rate-limited video, got:\n%s", html)
|
||||
if strings.Contains(html, "/summarize\"") {
|
||||
t.Errorf("state 4: rate-limited card must not POST to /summarize, got:\n%s", html)
|
||||
}
|
||||
if strings.Contains(html, "Try now") {
|
||||
t.Errorf("state 4: 'Try now' verb must not appear, got:\n%s", html)
|
||||
}
|
||||
}
|
||||
|
||||
// An ordinary unsummarized video still offers the Summarize button.
|
||||
func TestVideoCard_UnsummarizedShowsSummarize(t *testing.T) {
|
||||
// TestVideoCard_State5_Pending — quiet status + "Summarize now" → summarize URL.
|
||||
func TestVideoCard_State5_Pending(t *testing.T) {
|
||||
html := renderVideoCard(t, store.SummaryRow{
|
||||
VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
Title: "Fresh Video",
|
||||
Summarized: false,
|
||||
})
|
||||
|
||||
if !strings.Contains(html, ">Summarize<") {
|
||||
t.Errorf("expected a Summarize button, got:\n%s", html)
|
||||
if !strings.Contains(html, "Not summarized") {
|
||||
t.Errorf("state 5: expected 'Not summarized' status text, got:\n%s", html)
|
||||
}
|
||||
if strings.Contains(html, "Retrying later") {
|
||||
t.Errorf("no retry badge for a non-rate-limited video, got:\n%s", html)
|
||||
if !strings.Contains(html, "Summarize now") {
|
||||
t.Errorf("state 5: expected 'Summarize now' button, got:\n%s", html)
|
||||
}
|
||||
if !strings.Contains(html, "/summarize") {
|
||||
t.Errorf("state 5: expected summarize URL in form action, got:\n%s", html)
|
||||
}
|
||||
if strings.Contains(html, "retry-now") {
|
||||
t.Errorf("state 5: pending card must not POST to /retry-now, got:\n%s", html)
|
||||
}
|
||||
if strings.Contains(html, "Try now") {
|
||||
t.Errorf("state 5: 'Try now' verb must not appear, got:\n%s", html)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVideoCard_TryNowAbsent — "Try now" must not appear in any rendered card.
|
||||
func TestVideoCard_TryNowAbsent(t *testing.T) {
|
||||
cases := []store.SummaryRow{
|
||||
{VideoID: "a", Summarized: true, Summary: "s", AIProvider: "local"},
|
||||
{VideoID: "b", TranscriptStatus: "none"},
|
||||
{VideoID: "c", SummarizeRequested: true},
|
||||
{VideoID: "d", TranscriptStatus: "rate_limited"},
|
||||
{VideoID: "e"},
|
||||
}
|
||||
for _, r := range cases {
|
||||
html := renderVideoCard(t, r)
|
||||
if strings.Contains(html, "Try now") {
|
||||
t.Errorf("'Try now' must not appear in any card state; video %q got:\n%s", r.VideoID, html)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user