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

This commit is contained in:
Matthew Meszaros
2026-09-24 05:48:26 -07:00
parent b196b3c8be
commit e32b4f8893
3 changed files with 13 additions and 5 deletions
+1 -1
View File
@@ -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.
</Callout>
**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.
@@ -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{"<a@x>", "<b@x>", "<c@x>", ""} {
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))
+4
View File
@@ -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;
};