From e32b4f889351f391ab4e2bacb3b4cff5913ee568 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Thu, 24 Sep 2026 05:48:26 -0700 Subject: [PATCH] feat: let a vertical wheel over a ScrollStrip at its edge scroll the page, name the seed section consistently in the deliverability guide, and keep the warmup placement live tests to their own receipts by dating the sweep fixture decades back and checking the live-counted flag directly --- docs/content/docs/guides/deliverability.mdx | 2 +- internal/repository/warmup_placement_live_test.go | 12 ++++++++---- web/src/components/ui/scroll-strip.tsx | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/guides/deliverability.mdx b/docs/content/docs/guides/deliverability.mdx index 58daf5139..fa6af803d 100644 --- a/docs/content/docs/guides/deliverability.mdx +++ b/docs/content/docs/guides/deliverability.mdx @@ -30,7 +30,7 @@ Use **Customize** to pick which sections show. Your choices and time window pers Placement figures only appear once seed data exists, and a small sample is a hint rather than a verdict. Warmbly waits for a meaningful sample before acting. -**Placement by provider is the check before scaling a campaign.** An aggregate rate can look fine while one provider quietly junks everything: Gmail at `95%` inbox with Outlook at `40%` is a provider-specific problem, usually authentication or content, not volume. +**Seed test placement by provider is the check before scaling a campaign.** An aggregate rate can look fine while one provider quietly junks everything: Gmail at `95%` inbox with Outlook at `40%` is a provider-specific problem, usually authentication or content, not volume. **Warmup placement is your continuous signal.** Seed tests are point-in-time probes, but every verified warmup delivery reports inbox, tab or spam, so drift appears here before it becomes complaints on a campaign. Click a mailbox in **Warmup inbox placement** to open its own history; see [seeing where warmup lands](/guides/warmup/#seeing-where-warmup-lands) for how the rate is counted. diff --git a/internal/repository/warmup_placement_live_test.go b/internal/repository/warmup_placement_live_test.go index 2cb47b657..bcfe0a390 100644 --- a/internal/repository/warmup_placement_live_test.go +++ b/internal/repository/warmup_placement_live_test.go @@ -2,6 +2,7 @@ package repository import ( "context" + "math/rand/v2" "testing" "time" @@ -121,9 +122,10 @@ func TestLiveWarmupPlacementRollup(t *testing.T) { t.Fatalf("rate = %+v, want 4 delivered, below the floor", r) } - // The sweep leaves receipts the live path counted alone, and a fresh one to it. - if n, err := repo.SweepUnplaced(ctx, time.Now().Add(time.Hour), 100); err != nil || n != 0 { - t.Fatalf("sweep over counted receipts = %d, %v; want 0", n, err) + // Every receipt the live path counted is marked, so the sweep never counts it again. + var unplaced int + if err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM warmup_received WHERE sender_account_id = $1 AND NOT placed`, sender).Scan(&unplaced); err != nil || unplaced != 0 { + t.Fatalf("receipts left unplaced after the live count = %d, %v; want 0", unplaced, err) } // The partner's organization sees none of it. @@ -198,7 +200,9 @@ func TestLiveWarmupPlacementSweep(t *testing.T) { t.Errorf("cleanup: %v", err) } }) - day := time.Date(2026, time.August, 3, 12, 0, 0, 0, time.UTC) + // The sweep claims the oldest receipts below its cutoff across the whole + // database, so the fixture sits decades back where nothing else lives. + day := time.Date(1975, time.January, 1, 12, 0, 0, 0, time.UTC).AddDate(0, 0, rand.IntN(3650)) for i, msg := range []string{"", "", "", ""} { exec(`INSERT INTO warmup_received (email_account_id, internal_id, message_id, sender_account_id, created_at) VALUES ($1, gen_random_uuid(), $2, $3, $4)`, recipient, msg, sender, day.Add(time.Duration(i)*time.Minute)) diff --git a/web/src/components/ui/scroll-strip.tsx b/web/src/components/ui/scroll-strip.tsx index 2d377b1ad..c63d28183 100644 --- a/web/src/components/ui/scroll-strip.tsx +++ b/web/src/components/ui/scroll-strip.tsx @@ -41,6 +41,10 @@ export default function ScrollStrip({ // A vertical wheel over an overflowing strip scrolls it sideways. const onWheel = (e: WheelEvent) => { if (el.scrollWidth <= el.clientWidth || Math.abs(e.deltaX) > Math.abs(e.deltaY)) return; + // At the end it cannot move toward, the wheel scrolls the page instead. + const atStart = el.scrollLeft <= 0 && e.deltaY < 0; + const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1 && e.deltaY > 0; + if (atStart || atEnd) return; e.preventDefault(); el.scrollLeft += e.deltaY; };