feat(web): mode-aware backlog banner — stop telling manual users summaries auto-arrive
The first pilot user sat in Manual mode reading "new summaries land gradually, check back tomorrow" — copy that only makes sense in Automatic mode. Manual mode never auto-summarizes, so the banner promised delivery that would never come. The list page now reads the user's summarize mode and shows mode-correct copy: - Auto: unchanged "land gradually" backlog note. - Manual: "new videos appear here but are not summarized automatically — use the Summarize button" plus a "Switch to Automatic" link to /account. The connected-but-empty first-run state is likewise mode-aware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -233,11 +233,20 @@ func (a *App) handleList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
hasConnected := len(conns) > 0
|
hasConnected := len(conns) > 0
|
||||||
|
|
||||||
if isHTMX(r) {
|
// Summarization mode drives the backlog copy: an auto user is told summaries
|
||||||
a.render(w, r, summaryList(buckets, hasConnected))
|
// land gradually; a manual user is told to click Summarize (the first pilot
|
||||||
|
// user sat in manual mode reading "land automatically" and waited forever).
|
||||||
|
autoSummarize, err := a.Store.GetAutoSummarize(r.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
a.serverError(w, r, "summarize mode", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.render(w, r, ListPage(buckets, f, stats, takeFlash(w, r), hasConnected, channels))
|
|
||||||
|
if isHTMX(r) {
|
||||||
|
a.render(w, r, summaryList(buckets, hasConnected, autoSummarize))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.render(w, r, ListPage(buckets, f, stats, takeFlash(w, r), hasConnected, channels, autoSummarize))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDetail renders one summary in full (highlights, takeaways, action group).
|
// handleDetail renders one summary in full (highlights, takeaways, action group).
|
||||||
|
|||||||
@@ -477,3 +477,36 @@ func postAction(t *testing.T, app *web.App, videoID, action string, htmx bool) *
|
|||||||
}
|
}
|
||||||
return do(t, app, req)
|
return do(t, app, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestListManualModeBannerCopy: a manual-mode user with un-summarized videos
|
||||||
|
// sees the manual prompt (click Summarize), NOT the "summaries land
|
||||||
|
// automatically" copy that misled the first pilot user into waiting forever.
|
||||||
|
func TestListManualModeBannerCopy(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
app := newApp(t)
|
||||||
|
p := rawPool(t)
|
||||||
|
resetDB(t, p)
|
||||||
|
seedVideo(t, p, videoX, "X Title", "https://x", time.Time{}) // pending, un-summarized
|
||||||
|
require.NoError(t, app.Store.SetAutoSummarize(ctx, userID, false))
|
||||||
|
|
||||||
|
html := body(t, do(t, app, httptest.NewRequest(http.MethodGet, "/", nil)))
|
||||||
|
require.Contains(t, html, "Manual mode")
|
||||||
|
require.Contains(t, html, "are not summarized automatically")
|
||||||
|
require.NotContains(t, html, "land gradually",
|
||||||
|
"manual-mode user must not be told summaries arrive automatically")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestListAutoModeBannerCopy: an auto-mode user with a backlog sees the
|
||||||
|
// gradual-delivery copy, not the manual prompt.
|
||||||
|
func TestListAutoModeBannerCopy(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
app := newApp(t)
|
||||||
|
p := rawPool(t)
|
||||||
|
resetDB(t, p)
|
||||||
|
seedVideo(t, p, videoX, "X Title", "https://x", time.Time{})
|
||||||
|
require.NoError(t, app.Store.SetAutoSummarize(ctx, userID, true))
|
||||||
|
|
||||||
|
html := body(t, do(t, app, httptest.NewRequest(http.MethodGet, "/", nil)))
|
||||||
|
require.Contains(t, html, "land gradually")
|
||||||
|
require.NotContains(t, html, "are not summarized automatically")
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func TestParseYouTubeVideoID(t *testing.T) {
|
|||||||
func TestListPageShowsPasteFormOnlyWhenConnected(t *testing.T) {
|
func TestListPageShowsPasteFormOnlyWhenConnected(t *testing.T) {
|
||||||
render := func(connected bool) string {
|
render := func(connected bool) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := ListPage(listBuckets{}, Filter{}, PipelineStats{}, "", connected, nil).Render(context.Background(), &buf); err != nil {
|
if err := ListPage(listBuckets{}, Filter{}, PipelineStats{}, "", connected, nil, true).Render(context.Background(), &buf); err != nil {
|
||||||
t.Fatalf("render: %v", err)
|
t.Fatalf("render: %v", err)
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ templ flashBanner(code string) {
|
|||||||
// #summary-list region; a non-HTMX request renders the whole page. flash carries
|
// #summary-list region; a non-HTMX request renders the whole page. flash carries
|
||||||
// a one-shot notification (e.g. "connected", "registered") surfaced on arrival
|
// a one-shot notification (e.g. "connected", "registered") surfaced on arrival
|
||||||
// after a POST→redirect.
|
// after a POST→redirect.
|
||||||
templ ListPage(b listBuckets, f Filter, stats PipelineStats, flash string, hasConnected bool, channels []string) {
|
templ ListPage(b listBuckets, f Filter, stats PipelineStats, flash string, hasConnected bool, channels []string, autoSummarize bool) {
|
||||||
@Layout("Tapir — Summaries") {
|
@Layout("Tapir — Summaries") {
|
||||||
@flashBanner(flash)
|
@flashBanner(flash)
|
||||||
if hasConnected {
|
if hasConnected {
|
||||||
@@ -116,14 +116,23 @@ templ ListPage(b listBuckets, f Filter, stats PipelineStats, flash string, hasCo
|
|||||||
if stats.RateLimited > 0 || stats.Pending > 0 || stats.NoText > 0 {
|
if stats.RateLimited > 0 || stats.Pending > 0 || stats.NoText > 0 {
|
||||||
@pipelineBar(stats)
|
@pipelineBar(stats)
|
||||||
}
|
}
|
||||||
if stats.RateLimited+stats.Pending > 0 {
|
if (stats.RateLimited+stats.Pending) > 0 && autoSummarize {
|
||||||
<p class="pipeline-note muted">
|
<p class="pipeline-note muted">
|
||||||
Tapir fetches captions slowly on purpose, to respect YouTube's limits —
|
Tapir fetches captions slowly on purpose, to respect YouTube's limits —
|
||||||
new summaries land gradually. Check back tomorrow.
|
new summaries land gradually. Check back tomorrow.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
if (stats.RateLimited+stats.Pending) > 0 && !autoSummarize {
|
||||||
|
<p class="pipeline-note muted">
|
||||||
|
You are in Manual mode: new videos appear here but are not summarized
|
||||||
|
automatically. Use the Summarize button on the ones you want.
|
||||||
|
</p>
|
||||||
|
<p class="pipeline-note muted">
|
||||||
|
<a href="/account">Switch to Automatic</a> to have new videos summarized for you.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
<div id="summary-list">
|
<div id="summary-list">
|
||||||
@summaryList(b, hasConnected)
|
@summaryList(b, hasConnected, autoSummarize)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,12 +212,16 @@ templ filterForm(f Filter, channels []string) {
|
|||||||
// and a single disclosure holding the older un-summarized back-catalogue. Cards
|
// and a single disclosure holding the older un-summarized back-catalogue. Cards
|
||||||
// reflow to a single column on mobile; an empty list shows a friendly first-run
|
// reflow to a single column on mobile; an empty list shows a friendly first-run
|
||||||
// state instead of a blank table.
|
// state instead of a blank table.
|
||||||
templ summaryList(b listBuckets, hasConnected bool) {
|
templ summaryList(b listBuckets, hasConnected bool, autoSummarize bool) {
|
||||||
if b.empty() {
|
if b.empty() {
|
||||||
if hasConnected {
|
if hasConnected {
|
||||||
<div class="empty empty-connected">
|
<div class="empty empty-connected">
|
||||||
<strong>Your account is connected</strong>
|
<strong>Your account is connected</strong>
|
||||||
<span>Tapir is finding your subscriptions and fetching captions — summaries appear here gradually. Check back later.</span>
|
if autoSummarize {
|
||||||
|
<span>Tapir is finding your subscriptions and fetching captions — summaries appear here gradually. Check back later.</span>
|
||||||
|
} else {
|
||||||
|
<span>Tapir is finding your subscriptions. You are in Manual mode, so videos appear here with a Summarize button — pick the ones you want, or switch to Automatic in your account.</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
|
|||||||
+271
-246
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user