BLITZ

Sprint 2 · 23 Sep–6 Oct 2026 · Sprint 1 · the strategy

The model is the camera.

A stranger asks a model for a page, puts it in the feed through the door that already exists, it waits on a bench that only a person's tap on a phone releases, it remembers, and its maker sees what strangers did with it. Nothing of ours generates anything.

Wednesday 23 September to Tuesday 6 October 2026, the fortnight after Sprint 1's Day-14 readout. One founder with an agent, about thirty founder hours. Drafted on 8 September, before Sprint 1 ran, by a three-proposal panel (the creation loop, supply and trust, composition and return) scored by three judges and synthesised; so it is written as a core that holds under every Sprint 1 read plus swaps keyed to that read (section 5). Re-read it on 22 September with the numbers in hand.

≥ 3
Complete loops
Strangers who uploaded, were flipped live by a person on a phone, drew ten sessions from three networks, and opened their numbers. Stretch five.
≥ 20
Stranger uploads
From at least ten distinct non-founder accounts, through the + sheet and the brief.
0
Unreviewed cards live
Every live card by a stranger has an admin card_set_status row; every flip is logged, even from the SQL editor.
< 24h
Bench to live
Median wait for a person's verdict; no bench card older than 48 h at any day's end.
≥ 50%
Fit pass rate
Stranger pages that fit 375×667 on the automatic check. Under 30% the brief is wrong, not the makers.
01

The sprint in one paragraph

Blitz is for billions of people discovering AI-generated content, and on 22 September the only person who has ever put a model-made page into the feed is the founder. Sprint 1 measured whether a stranger stays and comes back; Sprint 2 makes the other half of the loop real and measurable without building a generator: 'Make one' hands anyone a copyable brief that yields a page fitting 375x667 and speaking the card protocol; the answer is pasted back through the + sheet that already exists; the page can remember (window.blitz.save/load through the html viewer's probe, host-kept per device, because a card BLITZ leaves is unloaded now); every upload and link — the founder's included — waits on a bench that only a person's tap on a phone releases into feed(), with a trigger that logs every status flip whoever made it, so 'no unreviewed card in front of strangers' is a fact the readout reads rather than a convention; an automatic phone-fit check (headless 375x667 render, painted-pixel test, CSP lint) writes a verdict on the row and never flips anything; 'regenerate with a new title' is the same card (content_hash); and the maker sees host-measured numbers on their own profile. Nothing here generates anything for anyone: the model is the visitor's, the brief is text, the account door is exactly where it is today, no endpoint of ours calls a model, and a person admits what executes in front of strangers. Images and clips go through the same door with the same numbers. The core (bench + status log + save channel + brief + fit check + card_stats, 9.75 engineering days) is the same under every Sprint 1 fork because it is the supply mechanism the goal needs whatever the first minute showed; what swaps is where the second week's ~1.5 engineering days and the founder's content hours go — card 0 and the chrome under (a), the founder's own pages with an audition slot under (b), a two-sided composition-lite for a short deck under (c), one more wave and a Day-7 read under (d). Linked human-made sites stay in the deck as the comparison, not the thesis; the harvest, the claim flow, auto-hides, bench-cap enforcement, composition outside (c), add-to-home-screen and every launch post are deliberately not this fortnight.

The goal this serves. Blitz is for billions of people discovering new AI-generated content. Sprint 1 asked whether a stranger stays and comes back; this sprint asks whether a person can make something with a model, put it in the feed, and see what strangers did with it — the loop every later number depends on.

02

The outcome

Metric

Completed creation loops by non-founder accounts in the fortnight, from a new admin-only loop_funnel(days) RPC (migration 0021): an actor that is not @rand, not is_internal, not is_probe and not ua_class='headless' who (1) uploaded a page, image or clip (card.creator_id set, source 'hosted'), (2) whose card an admin flipped bench->live through card_set_status (a card_status_log row with to_status='live', via='rpc' and an admin actor), (3) whose card then got >= 10 browser sessions from >= 3 distinct networks (ip_hash) other than its creator's, and (4) who opened their own numbers (ui_event 'stats_open'). Reported as a funnel with n at every step — brief copies -> make_open -> uploads -> fit ok -> live -> 10 sessions / 3 networks -> stats opened — beside bench_summary() (live, bench, oldest bench age, the cap reported not enforced) and the guard rails carried from Sprint 1 (position-0 hold, reached-end, back taps, D1 by via with its interval) on the same via filters, so the grown deck cannot silently make the first minute worse.

Target

= 20 non-founder uploads from >= 10 distinct non-founder actors; >= 5 non-founder cards flipped live by a person on a phone; >= 3 complete loops (stretch 5); exactly 0 non-founder cards in feed() without an admin card_set_status log row and exactly 0 card_status_log rows with to_status='live' and via='sql'; median bench->live under 24 h and no bench card older than 48 h at any day's end; fit pass rate on stranger pages >= 50% (under 30% the brief is wrong, see kill); the save channel shows 0 cross-card reads and 0 accepted saves over 4 KB in save-channel.spec.ts and in the localStorage audit on the founder's phone; position-0 hold and reached-end not below Sprint 1's Day-14 row on the same via filter by more than the interval.

Baseline

Zero on every loop count as of Tue 22 Sep: the founder is the only uploader (select count(distinct creator_id) from public.card where creator_id is not null and creator_id <> @rand's actor = 0), no card has a fit verdict (the column does not exist), no maker has ever seen a number (card_stats does not exist), no card has ever waited on a bench, no status flip has ever been logged. On Day 1, before any code ships, Sprint 1's Day-14 row (pos-0 hold, reached-end, back taps, D1 with interval, n, by via) is copied into a 'Sprint 2 baseline' column of docs/RETENTION.md beside those zeros, and Sprint 1's carried-to-sprint-2 list is read the same morning.

Read on

Day 14, Tue 6 Oct, no deploy: /stats gains a loop panel (loop_funnel(14) and bench_summary()) beside retention_daily(14,'make'), retention_daily(14,'pilot') and retention_daily(14,null); tools/sql/loop-readout.sql writes the funnel with n at every step, cards by status x uploader class (founder / stranger), fit pass rate by brief_version (pages carrying <meta name='blitz-brief'> versus not) and by made_with, median hours bench->live from card_status_log, and the kill criteria answered one by one into docs/RETENTION.md. A mid-read on Day 7, Tue 29 Sep (loop_funnel(7), bench_summary), is also Sprint 1's day-21 D1 read; under fork (d) it decides week 2.

03

Engineering

Days are engineering days for one engineer with an agent, verification included. "Holds under" says which Sprint 1 reads the item survives; the swaps are in section 5.

Item 1

Migration 0020 — the bench with a log of every flip: status 'bench' for every upload and link, card_set_status as the only door to live, an AFTER UPDATE trigger that logs even the SQL editor, content_hash and 'Made with' through p_meta, card_by_slug for unlisted cards, my_cards, bench_feed, bench_summary, the fit columns; on the client the share-link fix, the honest copy and the landing line

2.25 days

Why

STRATEGY section 9: no card goes live unreviewed on a phone, and a model never flips a card live. Sprint 1 ran the by-hand daily check because submit_link/submit_media insert 'live' and the shipped copy promises 'it is live, and it is next'; Sprint 1's own kill criterion made the bench sprint 2's first item whatever the numbers. Strangers' pages cannot come in at any volume without the mechanism, the trigger is what makes 'zero unreviewed cards' readable (an RPC-only log cannot see a direct update), the hash keeps a model's regenerate-with-a-new-title from filling the bench with duplicates, and the loop's promise to the maker (it is next, for you) needs the card reachable by its own link while it waits — the same code path as STRATEGY bet 1's share-link fix (a /c/<slug> outside the feed page opens card 0 today with entry_surface='share' on the wrong card). Holds under every fork.

Done when

supabase/migrations/20260923090000_bench.sql applies three times on a fresh Postgres 15 through bash tools/pg/verify.sh and tools/pg/check-bench.sql passes. Schema: card.status check becomes ('live','bench','hidden','blocked'); card gains fit jsonb, fit_ok boolean, content_hash text with a unique partial index where not null, generation jsonb; card_status_log(log_id, card_id, from_status, to_status, reason, actor_id, via check in ('rpc','sql'), ts) with all client grants revoked; trigger card_status_logged AFTER UPDATE OF status ON public.card writes one row whoever wrote — reason from current_setting('blitz.status_reason', true), via from current_setting('blitz.status_via', true) defaulting to 'sql', actor from current_actor() (null from the SQL editor) — so a direct update is logged at write time. RPCs: card_set_status(p_card_id, p_status, p_reason) SECURITY DEFINER raises 42501 unless is_admin(), sets both GUCs with set_config(..., true) so its row reads via='rpc', returns {ok, status}, never reads fit; submit_media(p_kind, p_path, p_title, p_meta jsonb default null) and submit_link re-created from 0015's and 0016's bodies (translators, consent and the per-network upload trigger kept) insert status='bench' for every caller, the founder included (ruling 2), and return {ok, card_id, status:'bench'}; submit_media reads content_hash (validated ~ '^[0-9a-f]{64}$') and made_with (text under 40 chars into generation) from p_meta, and a hash already present answers {ok:true, card_id:<existing>, existing:true} with no new row; card_set_fit(p_card_id, p_fit jsonb) with no client grant (owner role only, called by the fit-check workflow through SUPABASE_DB_URL) sets fit and fit_ok and never touches status; card_by_slug(p_slug) SECURITY DEFINER for anon+authenticated returns the card_public shape for a live card, for a bench card only to its creator or an admin until fit_ok is true and to anyone with the link after (ruling 1's default), null for hidden/blocked; my_cards() for authenticated returns the caller's own cards in every status with status, fit_ok, fit->>'summary', generation->>'made_with', created_at and the latest log reason; bench_feed() behind is_admin() returns bench cards oldest first as card_public plus status, fit, generation, handle; bench_summary() behind is_admin() returns {live, bench, hidden_7d, oldest_bench_age_h, cap} where cap is STRATEGY section 5's greatest(8, least(50, floor(votes_per_day_7d * 28 / 20))) reported, never enforced. RLS card_live_readable is untouched, so card_public, feed(), cards_by() and the end card's re-read serve no bench card. ui_event's kind check and log_ui's whitelist are re-created with 'brief_copy','make_open','stats_open' added (log_ui stays granted to authenticated only, one bucket token each). check-bench.sql asserts: submit_media as a handle-holding account lands status 'bench'; feed() and cards_by() return 0 rows for it; card_by_slug returns null for a second actor before fit_ok and the row after card_set_fit sets fit_ok true; my_cards() returns it for the owner and nothing for another actor; card_set_status as a non-admin raises 42501 and as an admin flips to live with a log row via='rpc' carrying the admin's actor; a direct update public.card set status='hidden' writes a row via='sql' with actor null; card_set_fit is permission denied for authenticated; the same content_hash twice returns existing:true and one row; select rank, card_id from card_rank(14) where rank <= 1 still returns deepstate-demo, chess-program-tutor (verify.sh's standing assertion). Client: src/api/cards.ts gains cardBySlug(slug) and myCards(); src/api/media.ts uploadMedia computes SHA-256 with crypto.subtle and sends p_meta {content_hash, made_with}; the Upload tab gains an optional 'Made with' input (data-testid upload-made-with); App.tsx resolves a /c/<slug> absent from the feed page through cardBySlug and splices it at index 0 before the carousel mounts so entry_surface='share' lands on the shared card; the upload and link success copy and the drop-to-publish toast read 'Added under @handle. It is next for you; everyone else sees it once it has been looked at on a phone — usually today.'; public/landing/index.html's creators section loses 'Paste a link. It's live.' for 'Paste a link or a page. It's yours, and it's next — opened on a phone before it joins everyone's feed.' Tests: tests/unit/cards.test.ts covers cardBySlug, myCards and the p_meta shape against a fake client; tests/e2e/link.spec.ts, comments-upload.spec.ts and pages.spec.ts assert the new copy and the absence of 'It is live' and 'It's live'; new tests/e2e/share-link.spec.ts: /c/<slug> for a fixture card outside the first page opens on that card (src/api/mockApi.ts gains bySlug over MOCK_GAMES). Post-deploy (Deploy 1, Day 2): a page uploaded from a non-admin test account is absent from /feed in a fresh profile and opens at /c/<slug> on the founder's phone signed in as its creator; select public.card_set_status('<id>','live','reviewed: phone') as @rand flips it and card_status_log has the row with via='rpc'; a direct update in the SQL editor leaves a via='sql' row; the Migrate workflow shows 0020 applied.

Holds under

every fork

Touches

supabase/migrations/20260923090000_bench.sql (new), tools/pg/check-bench.sql (new), src/api/cards.ts, src/api/media.ts, src/api/mockApi.ts, src/App.tsx, src/components/LinkSite.tsx, public/landing/index.html, tests/unit/cards.test.ts, tests/e2e/link.spec.ts, tests/e2e/comments-upload.spec.ts, tests/e2e/pages.spec.ts, tests/e2e/share-link.spec.ts (new), docs/ACCOUNTS.md

Depends on

Rulings 1 and 2 by Day 1 noon; Sprint 1's 0016–0019 applied (0020 re-creates submit_* and log_ui from their bodies)

Item 2

The save channel: window.blitz.save/load through the html viewer's probe, host-kept per device, 4 KB, keyed by card id

1.5 days

Why

Since the sleep/wake work a card BLITZ leaves is unloaded and reloads on return with its state gone, and there is no save channel (DESIGN-NOTES section 3). The brief asks for pages that remember — a counter, a drawing, a puzzle's progress, 'your best' — and those are the families whose loop is come-back-and-see; without the channel every such page the loop produces is a one-session card and the D1 read is set against it. STRATEGY bet 2's mechanism, scoped to the html viewer because that is the only hosted class strangers can make. Holds under every fork.

Done when

public/cards/html/index.html: on start it posts {blitz:'load'} to the host in parallel with the fetch and waits up to 300 ms for {blitz:'state', data}; the injected PROBE then defines window.blitz = { load(): the parsed object or null, synchronous, from the state embedded in the probe (JSON with </script and U+2028/2029 escaped); save(obj): JSON-serialised, returns false and posts nothing over 4096 bytes, otherwise posts {blitz:'save', data:<string>} upward; touch() }; the viewer relays upward only 'touch', 'beat' and a 'save' whose data is a string <= 4096 bytes, and forwards a late {blitz:'state'} from the host (e.source === parent) down to the nested frame so a page that asks after start still gets it. src/lib/WebCardHost.ts: BlitzCardMessage gains 'save' | 'load'; onWindowMessage answers 'load' by posting {blitz:'state', data} to frame.contentWindow with targetOrigin '' from localStorage key blitz:state:<meta.id> (null when absent, in private mode or when storage throws) and on 'save' writes that key through new src/lib/cardState.ts (cap 4096 bytes, trailing-edge throttle to one write a second per card, last wins, every read and write in try/catch); keyed by meta.id so no card can read another's; never forwarded to telemetry; sleep(), warm() and resume() are unchanged — the reloaded page asks for 'load' again. tests/unit/cardState.test.ts: cap, throttle, key isolation, a storage that throws. tests/e2e/save-channel.spec.ts on the default touch project against a fixture at public/cards/e2e/remember.html (a tap counter using the three calls), inserted through __blitz's api.insertGame with page_url /cards/html/index.html?src=<bucket>/e2e/remember.html and page.route fulfilling that bucket URL from the fixture with a CORS header, so the no-backend build exercises the channel: three taps show 3; BLITZ then the chevron back reloads the frame (the unload web-card-sleep.spec.ts observes is asserted here too) and it shows 3 within 2 s; a second card id with the same fixture shows 0; a 5 KB save leaves the key unchanged; ten saves in 200 ms produce one write; tests/e2e/web-card-sleep.spec.ts stays green unchanged. docs/EMBEDDING-A-SITE.md gains 'Make your page remember' — the three calls, the 4 KB cap, 'this phone only; nothing of it ever ranks' — and the one sentence to hand a model: 'If window.blitz exists, restore from window.blitz.load() at start and call window.blitz.save(state) whenever state changes; keep state under 4 KB.'; docs/DESIGN-NOTES.md section 3 records the channel; docs/RANKING.md gains 'saved state never ranks'. Phone check after Deploy 2: the founder's counter page keeps its count across BLITZ and the chevron on the phone; localStorage on the feed origin holds only blitz:state: keys, none over 4 KB.

Holds under

every fork

Touches

public/cards/html/index.html, src/lib/WebCardHost.ts, src/lib/cardState.ts (new), public/cards/e2e/remember.html (new), tests/unit/cardState.test.ts (new), tests/e2e/save-channel.spec.ts (new), docs/EMBEDDING-A-SITE.md, docs/DESIGN-NOTES.md, docs/RANKING.md

Depends on

Nothing (Deploy 2); ruling 4

Item 3

'Make one': the brief in src/lib/brief.ts, a third tab in the + sheet, paste-back with fence-stripping and 'Made with', ?make=1 as the door, and the end card's honest count

1 day

Why

The record button. Today the Upload tab says 'something a model wrote for you' and leaves the person to guess what to ask; the pages that come back are desktop layouts that squeeze, pages that fetch, pages that scroll. A brief the person copies into any model is the cheapest possible creation path and it is not ours: no endpoint, no cost, no anonymous prompt, no model deciding anything on our side. The brief is readable by anyone (it is text); adding remains gated exactly as upload is today. The in-sheet 375x667 preview is dropped (two judges: it needs a preview mode in the viewer and a scaled frame in a sheet on a 375-wide phone) — the card itself, next in the maker's own feed and unlisted, is the preview, and the fit verdict on the profile is the check. Holds under every fork.

Done when

src/lib/brief.ts exports BRIEF_VERSION = 1 and three briefs (page, image, clip) as plain text. The page brief says, in full: one self-contained .html file; designed for a 375x667 portrait phone, fills the viewport, no page scroll, no horizontal overflow; dark ground #0b0e14; does something on its own within 1 s (it is seen before it is touched) and answers the first touch within 100 ms; touch-first (pointer events, touch-action: none where it handles drags, no hover-only affordances, no long-press menus); no network calls of its own — scripts and styles inline, the only external scripts and styles from the html viewer's CSP list, images as data: or https; no <form>, <iframe>, <base>, meta refresh or navigation; if window.blitz exists, restore from window.blitz.load() at start and call window.blitz.save(state) whenever state changes, under 4 KB; a <title>; <meta name='blitz-brief' content='1'> in <head>; under 200 KB ideal, 25 MB max. The image brief: portrait 9:16 or 3:4, PNG/JPEG/WebP, under 50 MB. The clip brief: portrait MP4 (H.264) or WebM, <= 30 s, made to work muted. tests/unit/brief.test.ts asserts the page brief names 375, 667, window.blitz.save and blitz-brief, and that its CDN list equals the list parsed from public/cards/html/index.html's CSP meta (the two cannot drift). src/components/LinkSite.tsx: the mode tabs (Link a website / Upload / Make one) render for everyone; Link and Upload keep their account doors; Make one shows the brief in a read-only textarea with 'Copy the brief' (navigator.clipboard with a select-all fallback; logs ui_event 'brief_copy' through src/api/ui.ts), the line 'Ask any model for a page with this. Paste what it gives you back here.', the paste textarea (the same account door as Upload appears on Add for anonymous visitors, ruling 6), the optional 'Made with' input, and 'Add to Blitz', which is the Upload tab's html path (htmlFile + uploadMedia with p_meta); a model's answer wrapped in ```html fences is unwrapped before upload. App.tsx: ?make=1 opens the sheet on the Make tab at boot (logs 'make_open') and syncUrlToCard drops it; the end card's line becomes 'You played {cards with a session this visit} of {deck length}' (client-side, from the carousel's visit record). New tests/e2e/make-one.spec.ts: an anonymous visitor sees the brief on the Make tab and the account door on Add; the copy button fills the clipboard (Chromium clipboard permission granted in the spec) and uiLog carries 'brief_copy'; a fenced answer is unfenced; /feed?make=1 opens the sheet on the Make tab and uiLog carries 'make_open'; 'Add to Blitz' on the no-backend build says there is no backend; tests/e2e/end-card.spec.ts asserts the count line after two cards. Phone check after Deploy 3: the founder copies the brief on the phone, pastes a model's answer, adds; the card is next in the founder's own feed at 375x667.

Holds under

every fork

Touches

src/lib/brief.ts (new), src/components/LinkSite.tsx, src/components/Carousel.tsx (end-slide), src/App.tsx, src/api/ui.ts, src/index.css, tests/unit/brief.test.ts (new), tests/e2e/make-one.spec.ts (new), tests/e2e/end-card.spec.ts

Depends on

Item 1 deployed (the bench and the copy); 0019's logUi call sites

Item 4

The phone-fit check: tools/fit-check.mjs, a gated workflow that writes a verdict on the row and can never change a status

1.5 days

Why

STRATEGY's Deepstate lesson: framing permission is not fit, and only a phone-sized render can tell. Reviewing every stranger page by hand on a phone is the founder's bottleneck at 15 h/week; a check that paints the page at 375x667, counts pixels, taps it and lints its text turns the review into a glance at a verdict, and gives the maker an honest reason when their page does not fit. It runs where Playwright already runs (CI) with the secret the Migrate workflow already has, so no function, no new host, no anonymous endpoint. The 15-minute schedule is gone (all three judges: a private repo's metered minutes, delayed crons, a flaky 30-minute assertion): hourly with an early exit, dispatch on demand, by hand as the fallback. It writes a verdict; a person flips status. Holds under every fork — under (a) it is also the card-0 diagnostic.

Done when

tools/fit-check.mjs (npm run fit:check -- <file|url> [--json]) reuses tools/probe-webcard.mjs's magenta-backdrop pixel count and tools/verify-cards.mjs's two-origin shape: the candidate is served under the html viewer's exact CSP meta inside a sandbox='allow-scripts' frame at 375x667 in a touch context (the iPhone SE descriptor); it screenshots at 1 s and 3 s and counts non-background pixels, evaluates documentElement.scrollWidth and scrollHeight inside the frame, taps the centre and screenshots again, records console errors and securitypolicyviolation events, and lints the text: size, <base>, <form>, <iframe>, meta refresh, external <script src>/<link rel=stylesheet> off the CSP list (a violation), top-level location assignments (a warning), the blitz-brief meta (brief_version). Verdict {ok, painted_1s, painted_3s, overflow_x, overflow_y, reacts, errors[], violations[], lint[], brief_version, summary, tool_version, checked_at} where ok = painted_3s >= 60% and not overflow_x and violations is empty and errors is empty; summary is one phrase a maker can read ('fits' / 'wider than a phone' / 'painted 12% at 3 s' / 'loads a script off the list'). tests/unit/fit-lint.test.ts covers the lint on tests/fixtures/fit/{good,wide,blank,evil}.html; .github/workflows/ci.yml gains a 'fit-check fixtures' step (about 2 minutes) asserting good=ok, wide=overflow_x, blank=painted_3s<60, evil=violation. .github/workflows/fit-check.yml: workflow_dispatch plus an hourly schedule that skips entirely when SUPABASE_DB_URL is unset (the Migrate pattern) and exits before installing Playwright when select count(*) from public.card where class='web' and source='hosted' and fit is null and created_at > now() - interval '14 days' is 0, so an empty hour costs a checkout and a psql; otherwise it selects card_id, media_url for up to 20 such cards, runs the tool per card and writes select public.card_set_fit(:'id', :'fit'::jsonb); it never calls card_set_status — tests/unit/workflows.test.ts greps the workflow file for card_set_status and fails if present. FIT GATE before the workflow writes anything (Day 6 morning): the tool's Day-2 hand-run verdicts on the live deck agree with the founder's Sprint 1 phone verdicts on every card but at most one, else the tool is fixed first and my_cards()/the strip say 'not checked yet' honestly. tools/column-check.sql gains: every hosted web card older than 24 h since Deploy 3 has fit not null; card_status_log has no row with to_status='live' and via='sql'. Images and clips get no automated check this sprint (the media viewer letterboxes; the brief's variants are text). Phone check: a deliberately 900 px-wide page uploaded from a test account shows 'wider than a phone' in my_cards() and on the strip within the hour.

Holds under

every fork

Touches

tools/fit-check.mjs (new), tests/fixtures/fit/{good,wide,blank,evil}.html (new), tests/unit/fit-lint.test.ts (new), tests/unit/workflows.test.ts (new), .github/workflows/fit-check.yml (new), .github/workflows/ci.yml, tools/column-check.sql, package.json (fit:check)

Depends on

Item 1 deployed (card.fit, card_set_fit); SUPABASE_DB_URL present in the repository secrets (it is, for the Migrate admin grant); ruling 11

Item 5

Migration 0021: card_stats and loop_funnel; 'Your cards' with host-measured numbers on the profile; the loop panel and bench_summary on /stats

1.25 days

Why

The maker's payoff, per STRATEGY bet 6 and the goal: what a maker cannot get anywhere else is honest, host-measured hold from a phone audience. Nothing shows a creator a single number today. The loop's outcome metric (stats_open) does not exist until this does, and the readout's funnel is the same RPC. Aggregates only, creator or admin only, the creator's own sessions excluded, no actor id leaves the function, and none of it ranks. Holds under every fork.

Done when

supabase/migrations/20260928090000_card_stats.sql applies three times and tools/pg/check-stats.sql passes: card_stats(p_card_id, days default 14) SECURITY DEFINER for authenticated, 42501 unless the caller is the card's creator or an admin; over the window and excluding actor.is_internal, card_session.is_probe, ua_class='headless' and the creator's own actor: impressions (distinct session_uuid), viewers (distinct actor_id), networks (distinct ip_hash), held (span_ms >= 5000 and no CRASH), blitz_past (SWIPE_UP under 5 s), comments (live), likes (like minus unlike), shares, as counts plus held_rate with n. loop_funnel(days) behind is_admin(), per UTC day and total, excluding @rand's actor, internal, probe and headless: actors who logged brief_copy; make_open; actors who uploaded and uploads (card.creator_id set, source 'hosted'); uploads with fit_ok; uploads flipped live by an admin (a card_status_log row to_status='live', via='rpc', actor is_admin); live uploads with >= 10 non-creator browser sessions from >= 3 distinct ip_hash; actors who logged stats_open; complete loops = actors with uploaded, flipped live, reached 10/3 and stats_open all true. check-stats.sql: a creator reads their own card's stats; another authenticated actor gets 42501; the creator's own sessions do not count; loop_funnel raises 42501 for a non-admin and returns every column for an admin on a seeded loop. Client: src/api/cards.ts cardStats(); src/api/stats.ts loopFunnel(), benchSummary(); src/components/AccountSheet.tsx's profile view gains 'Your cards' (data-testid my-cards): one row per card — title, a status word ('next for you · waiting for a look on a phone' / 'live' / 'hidden: <reason>'), the fit summary, made_with when set, and for live cards impressions, held %, BLITZ-past and comments over 14 days; a row links to /c/<slug>; opening the panel logs ui_event 'stats_open' once per visit; the line 'Excludes you on this device; your other devices count as viewers.' sits under the table (ACCOUNTS.md's known limitation written beside the number, ruling 8). src/components/StatsPage.tsx gains the loop_funnel(14) table and bench_summary() beside retention_daily, and a 'make' via filter. tests/unit/stats.test.ts renders both from fixture JSON; tests/e2e/account.spec.ts covers my-cards on the mock with a card in each status word. Post-deploy (Deploy 4): the founder's profile shows numbers for a card the pilot played; a test account's card shows its bench state and fit line; select * from loop_funnel(14) returns every column for @rand and 42501 otherwise.

Holds under

every fork

Touches

supabase/migrations/20260928090000_card_stats.sql (new), tools/pg/check-stats.sql (new), src/api/cards.ts, src/api/stats.ts, src/components/AccountSheet.tsx, src/components/StatsPage.tsx, src/api/ui.ts, tests/unit/stats.test.ts, tests/e2e/account.spec.ts, docs/CONTENT.md numbers table

Depends on

Items 1 and 4 deployed (status log, fit); 0019's ui_event and ua_class

Item 6

The review strip on the founder's phone (/feed?bench=1): play it, read the verdict, tap Live or Hide

0.75 days

Why

The bench only works if the flip is ten seconds on a phone: the founder opens the bench as a feed, plays each card, reads the fit verdict, taps Live or Hide with a reason. Without it card_set_status lives in the SQL editor and the median time-to-live is whatever the founder's laptop hours are. The image/clip aspect line that shared this item is dropped up front (three judges: the media viewer letterboxes and the brief's variants are text). This is the item that funds the swap under (a) and (c): the SQL editor is its fallback, and the bench, the log and the 24-hour rule stay whatever happens to the strip.

Done when

App.tsx: when location.search has bench=1 and the account is an admin, the feed resource is bench_feed() instead of feed(), the transport still runs (the founder is internal, so nothing votes), and src/components/ReviewStrip.tsx sits below the meta strip and never over the surface: @handle · made_with · the fit summary ('fits' / 'wider than a phone' / 'painted 12% at 3 s' / 'not checked yet') · bench_summary()'s 'live 11 · bench 4 · oldest 6 h', and two 44 px buttons Live and Hide (Hide asks a one-word reason from a short list: does-not-fit, blank, broken, not-for-strangers, takedown) calling card_set_status with reason 'reviewed: touch=y fit=y show=y' or 'hidden: <word>'; on success the strip advances to the next bench card; non-admins with ?bench=1 see the normal feed and no strip. src/api/cards.ts gains benchFeed() and setCardStatus(); src/api/mockApi.ts gains them with a statusLog exposed on __blitz. tests/e2e/review-strip.spec.ts on the no-backend build with __blitz's admin fixture flag: the strip renders only under ?bench=1 for the admin fixture; Live pushes {card_id,'live',reason} to __blitz.statusLog and the next bench card becomes active; Hide requires a reason; the strip's box and the surface's box are disjoint. docs/CREATING.md documents the words a maker sees. Phone check after Deploy 4: the founder reviews the day's bench on the phone in under a minute a card, and card_status_log shows each flip with via='rpc' and the reason.

Holds under

b, d — dropped under a and c to fund the swap (card_set_status and bench_feed() in the SQL editor from the laptop each evening; the bench, the log and the 24-hour rule stay)

Touches

src/App.tsx, src/components/ReviewStrip.tsx (new), src/api/cards.ts, src/api/mockApi.ts, src/index.css, tests/e2e/review-strip.spec.ts (new), docs/CREATING.md (new)

Depends on

Item 1 deployed; item 4 writing verdicts; bench_summary from 0020

Item 7

Docs, the loop readout query and the column check, during the freeze

0.5 days

Why

The readout is the deliverable and is not written on the morning it is read; docs ship during the freeze because they do not change the client bundle. The card protocol (window.blitz) is now something strangers' models are asked to speak, so it must be written down where the brief points.

Done when

docs/CREATING.md (new): the brief, the three calls, the bench and its words, the fit verdict's fields, what numbers a maker sees and what never ranks; docs/EMBEDDING-A-SITE.md's 'Make your page remember' finished; docs/RANKING.md gains 'fit, saved state, card_stats, made_with and the status log never rank'; docs/CONTENT.md's numbers table gains card_stats, loop_funnel, bench_summary, fit, card_status_log, content_hash; docs/ACCOUNTS.md gains 'Your cards'; docs/STRATEGY.md section 5 gets a two-line amendment (the bench exists with a log; the brief is the first creation path, remix-by-pack a later one). tools/sql/loop-readout.sql: the Day-14 block (loop_funnel(14), bench_summary(), cards by status x uploader class, fit pass rate by brief_version and made_with, median hours bench->live and the max bench age per day from card_status_log, select count(*) from card_status_log where to_status='live' and via='sql', retention_daily(14,'make') beside (14,'pilot') and (14,null) with the binomial interval) rehearsed on Day 13 against real rows; tools/column-check.sql's fit and status-log assertions pass on Day 13.

Holds under

every fork

Touches

docs/CREATING.md (new), docs/EMBEDDING-A-SITE.md, docs/RANKING.md, docs/CONTENT.md, docs/ACCOUNTS.md, docs/STRATEGY.md, tools/sql/loop-readout.sql (new), tools/column-check.sql

Depends on

Items 1–6

Item 8

The maker-wave fix day (reserved, Day 10): P0 only; when there is no P0 the day goes to the fork's swap-in, or on the core path to the trust floor (Report and Remove it, no auto-hide)

1 day

Why

The first pages strangers paste will break things no fixture did — a 3 MB inline font, a page that saves 4 KB every frame, a model's answer wrapped in prose, a status word that lies. Reserved rather than pretended away. If nothing is P0, the day is where the Sprint 1 fork lands under (a) and (c); under (b) and (d) it goes to the one trust piece that becomes necessary the day a stranger's page is live in front of other strangers — a button, with no auto-hide behind it, so three phones can never remove a card (two judges dropped B's auto-hides as a bigger lever than demote-only ever gave a forger).

Done when

docs/RETENTION.md has the Day-10 loop row (brief copies, make_open, uploads by class, fit pass rate, hours bench->live, live non-founder cards, stats opens, n) written before any fix; each P0 has a failing test first, e2e green, ships in Deploy 6 the same day and is phone-checked within the hour; the freeze gate in the evening — tools/column-check.sql clean, fit-check.yml green on its last three real runs, no bench card older than 24 h, no open P0 — passes before Day 11's second send; anything not P0 is listed under 'carried to sprint 3'. With no P0: under (a) or (c) the day is that fork's swap item (9 or 11); under (b) or (d) it is migration 0022 (supabase/migrations/20261002090000_trust.sql: card_report(report_id, card_id, actor_id, reason check in ('broken','not_for_phones','offensive','spam','mine','other'), note <= 200, ip_hash, created_at, status) and card_request(request_id, card_id, kind check in ('claim','remove'), contact <= 200, note <= 500, actor_id, ip_hash, created_at, status), report_card() and request_card() for any authenticated caller at 5 a day per actor and 20 per network via ip_hash, unique (card_id, actor_id) on reports, NO status change in either, ui_event kinds 'report','remove_request','menu'; tools/pg/check-trust.sql; a fourth 40 px rail button 'More' in GameViewer.tsx opening src/components/CardMenu.tsx with Copy link, Report (reason radios + note; toast 'Thanks — a person looks at every report') and Remove it ('Yours and you want it gone? Leave a way to reach you. It is hidden today.'); open rows shown on the ReviewStrip and cleared by the founder the same day; tests/e2e/card-menu.spec.ts on the mock; the concierge DM's 'reply remove and it is gone today' is then a button as well as a promise).

Holds under

every fork (the filler swaps: a and c take the swap item, b and d take the trust floor; under b the day shrinks to 0.5)

Touches

whatever the wave names; docs/RETENTION.md; on the core path supabase/migrations/20261002090000_trust.sql (new), tools/pg/check-trust.sql (new), src/components/GameViewer.tsx, src/components/CardMenu.tsx (new), src/api/cards.ts, src/api/ui.ts, tests/e2e/card-menu.spec.ts (new)

Depends on

Day 8's sends; the Day-10 loop row written first

Item 9

Fork (a) swap — card 0 and the chrome: the deck's fit read on Day 1, card_editorial re-decided on Day 3 by ruling, migration 0023 card_zero with the fixtures in one commit, first-minute.spec.ts, the carried chrome P0

1.5 days

Why

Sprint 1's read under (a) says the first minute is the problem — card 0's phone fit and the chrome, not supply, not composition. The fit tool built for strangers' pages is exactly the card-0 diagnostic (Deepstate's paint and overflow at 375x667, read from data on Day 1), and the second-week days that would have gone to the strip and the fix day's slack go to card 0 and the one chrome P0 the pilot named. Supply keeps its bench, its brief and the founder's own uploads — those hold — but no founder hour goes to the makers' hunt until card 0 holds.

Done when

Day 1: the fit tool's hand run on the live deck (Deepstate, chess, the Sprint 1 pages, the platform embeds) is written into docs/RETENTION.md before any ruling. Day 3: if Deepstate's verdict is painted_3s < 60% or overflow_x (ruling 9), card_editorial is re-ordered in the SQL editor so the deck opens on the best-fitting founder page by fit then card_rank(14) hold — an editorial act RANKING.md permits, nothing promoted on telemetry. Days 8–10: supabase/migrations/20260930090000_card_zero.sql rewrites card_editorial to the ruling with MOCK_GAMES, feed.json and the e2e fixture in the same commit (chain x3; verify.sh's rank assertion updated to the new pair in that commit); the one chrome P0 Sprint 1's carried list names first (the chevron not found, the slide-thumb blank before a frame paints, the meta strip over a card's control) with a failing test first; new tests/e2e/first-minute.spec.ts on the touch project: card 0's host is 'running' under 1.5 s from a cold context on the no-backend build, the meta strip's box never intersects the surface, BLITZ and the chevron hit areas are disjoint. Deploy 6 carries it with a phone verdict line. Funded by: the strip (0.75), the fix-day filler (0.5), the hourly fit schedule (0.25, dispatch after each bench pass instead). Founder: four pages instead of six to eight (two that remember), 2 h re-reviewing every deck card for fit at 375x667 and hiding by hand what fails, the makers-already-posting hunt shrinks to five and the Day-11 send is skipped.

Holds under

a

Touches

supabase/migrations/20260930090000_card_zero.sql (new), src/api/mockGames.ts, public feed.json, tests/e2e/helpers.ts, tests/e2e/first-minute.spec.ts (new), tools/pg/verify.sh, src/components/TelemetryDisplay.tsx or Carousel.tsx (the named P0), docs/RETENTION.md

Depends on

Sprint 1's Day-14 read = (a); the Day-1 fit run; ruling 9

Item 10

Fork (b) swap — the founder's pages get 'New today': actor.house and the audition pick over house uploads, riding migration 0021

0.75 days

Why

Under (b) people finish and do not return: the deck is too short, and the supply is the founder's own pages through the brief plus the concierge. STRATEGY section 6 names the fault that makes that supply invisible: card_rank's explore_pool requires the uploader not to be internal and holds one ticket per uploader, so the founder's account — internal since Sprint 1 Day 1 — has zero tickets and every founder page sits at the tail. 'New today' is the only distribution a bench graduate gets; composition reads editorial data only, no rate, so it opens no fabrication path. Kept to (b) (two of three judges): elsewhere it touches the fabrication-proof ranker for no reader.

Done when

Inside 20260928090000_card_stats.sql (or a 0022 the same day): actor gains house boolean not null default false and @rand's row is set; card_rank re-created from 0008's body so explore_pool applies the is_internal test to voters only (the session CTE already does), skips k_uploader_holds and k_uploader_age for house uploaders, and draws one ticket per house card with the day's pick md5(current_date || card_id) over eligible live house cards under 30 votes and 30 days; outside uploaders keep the existing rule; k_explore_grad and the slot stay. tools/pg/check-audition.sql: a seeded house upload with zero votes appears at slot 'explore' in card_rank(28) with the uploader marked internal; a visitor upload with no held cards does not; deepstate 0 / chess 1 unchanged. Production: after one house page is flipped live, select card_id, slot from card_rank(28) where slot='explore' returns a house card the next UTC day — if it returns nothing, nothing about 'New today' is true and it is fixed before Deploy 4. docs/RANKING.md's audition paragraph updated. Funded by: the hourly fit schedule (0.25, dispatch after each bench pass) and the fix day shrinking to 0.5 d. Founder: ten to twelve pages instead of six to eight (+3 h), five known or already-posting makers DMed the brief by Day 6 (+2 h), the image and clip dropped and the Day-11 send folded into Day 8 (−1.75 h).

Holds under

b

Touches

supabase/migrations/20260928090000_card_stats.sql (the actor.house and card_rank blocks), tools/pg/check-audition.sql (new), docs/RANKING.md

Depends on

Sprint 1's Day-14 read = (b); item 1 deployed

Item 11

Fork (c) swap — composition-lite for a short deck, two-sided: feed(n, seed, exclude) with one hard rule, the client twin, one fixture asserted on both sides, and the Day-10 sequencer gate

1.75 days

Why

Under (c) D1 holds provisionally and Sprint 1 says composition rescoped for a short deck (bet 4) plus supply (bet 6), still no launch post. On a deck of twelve to twenty the shuffle is real; verb families belonged to the hidden toys, so the one rule the live deck can satisfy is kind (page, image, clip, site). Two-sided (all three judges): a feed that differs by path cannot be read, so the no-backend fallback must produce the server's order for the same seed, and the hash must be synchronous on the client because helpers.ts derives order at module load. Reads tiers and a seed, never a rate; demote-only untouched.

Done when

supabase/migrations/20260930090000_feed_seed.sql (chain x3): card_public gains kind ('site' when source='external', 'clip'/'image' by class, else 'page'); feed(n default 60, seed text default null, exclude text[] default '{}') keeps slots 0–1 from card_rank and orders the live tail by md5(coalesce(seed,'') || current_date || card_id) under one hard rule — no two consecutive cards of the same kind — in a plpgsql greedy with an explicit fallback flag; seed accepted only if ~ '^[a-z0-9]{8,32}$', else null; exclude capped at 200 ids, applied then discarded, never stored or logged; the old feed(integer) dropped first so the file re-runs. src/api/sequence.ts is the client twin with an inlined synchronous md5 (no crypto.subtle); tests/fixtures/sequencer.json (9 cards: 2 sites, 4 pages, 2 clips, 1 image) is asserted by both tools/pg/check-sequencer.sql and tests/unit/sequence.test.ts for seed 'abcdef12' on '2026-09-30'; src/lib/deck.ts mints the device seed once into localStorage in try/catch; fetchFeed sends seed and exclude and the no-backend build sequences MOCK_GAMES with ?seed=; the end card's BLITZ re-reads feed with the ids held this visit in exclude and says 'Nothing new since you looked' when the result is empty (Again reshuffles on device). tests/e2e/feed-seed.spec.ts: two seeds differ past slot 1 with the same card 0; no two adjacent kinds when the pool allows; the end card's re-fetch carries exclude (through __blitz.getState().lastFeedRequest). Day-10 SEQUENCER GATE on production before Deploy 6: feed() for two seeds in the SQL editor — same card 0, different past it, no adjacent kinds. Still no launch post: the day-21 bar on >= 200 browser arrivals is not met by a 100-person pilot. Funded by: the strip (0.75), the fix-day filler (0.5), the /stats loop panel (0.25 — loop_funnel read in the SQL editor), the hourly fit schedule (0.25).

Holds under

c

Touches

supabase/migrations/20260930090000_feed_seed.sql (new), tools/pg/check-sequencer.sql (new), tests/fixtures/sequencer.json (new), src/api/sequence.ts (new), src/lib/deck.ts (new), src/api/cards.ts, src/api/mockApi.ts, src/App.tsx, src/components/Carousel.tsx, tests/unit/sequence.test.ts (new), tests/e2e/feed-seed.spec.ts (new), tests/e2e/helpers.ts, docs/RANKING.md

Depends on

Sprint 1's Day-14 read = (c); Deploy 3 live by Day 6; ruling 12

Item 12

Fork (d) swap — one more founder-network wave into the Sprint 1 build on Day 2, the day-21 block, and the Day-7 read that selects week 2

0.25 days

Why

Under (d) D1 sits in the gray zone at n≈100 and Sprint 1 prescribes one more founder-network wave with the day-21 read deciding — nothing more, and not into a changed feed (two judges dropped C's wave into a composed deck for confounding the read). Deploy 1 changes nothing a visitor sees (feed() still serves only live rows; the chrome is untouched; only the + sheet's copy and the landing line changed), so wave 3 lands on the Sprint 1 build in all but name, and every visible client change waits for the read.

Done when

Day 1: tools/sql/retention-readout.sql gains the day-21 block (D1 with its interval on pilot plus wave-3 browser arrivals, first sessions Sprint 1 Days 9–12 and Sprint 2 Days 2–3, r=-referred arrivals excluded from share-to-arrival as Sprint 1 did) and it is rehearsed on Sprint 1's rows. Day 2: wave 3 of ~50 goes out 1:1 as /feed?via=pilot&r=<the founder's u_ id> after Deploy 1's phone check, nobody from waves 1 or 2 re-sent. Deploy 2 (the save channel) changes nothing for a visitor whose deck has no page that saves; Deploy 3 (the Make tab, the end card's count line) waits until after the Day-7 read and ships that afternoon with Deploy 4. Day 7: the read with n near 150 selects (a), (b) or (c) for Days 8–14 and that fork's add and drop lists apply from Day 8; if D1 is still in the gray zone at n≈150, week 2 takes the (a) swap, because another wave cannot narrow it and the first minute is the cheapest place to look. The pilot-replier 'make one' send waits for the Day-7 read so the wave-3 cohort's first card is not changed under it.

Holds under

d

Touches

tools/sql/retention-readout.sql, docs/RETENTION.md, the founder's list

Depends on

Sprint 1's Day-14 read = (d); Deploy 1's phone check

Total: about 10 engineering days for the core (items 1–8) plus the one fork swap the read selects (items 9–12, between a quarter of a day and 1.75 days).

04

Founder hours

Rulings by noon and the Sprint 2 baseline column before any code (1 h)

Why. Every number in the sprint is read against a row that exists before code ships, and rulings 1 and 2 shape migration 0020, which is written on Day 1.

Done when. Day 1 by noon: rulings 1–12 answered or explicitly defaulted in docs/RETENTION.md (0.5 h). Day 1 morning with the agent: the 'Sprint 2 baseline' column in docs/RETENTION.md — Sprint 1's Day-14 row (pos-0 hold, reached-end, back taps, D1 with interval, n, by via) beside the loop's zeros (distinct non-founder uploaders 0, cards with fit 0, makers who saw a number 0, cards that waited on a bench 0, logged status flips 0) — and Sprint 1's carried-to-sprint-2 list read and its first chrome P0 named (0.5 h).

Founder 1

The founder is the first stranger: six to eight pages through the brief, two that remember, one image and one clip

7.5 h

Why

The brief is only as good as the pages it produces in the hands of a person with fifteen minutes, and the founder is the first stranger. The deck grows by the pages the loop makes, which is what the readout's guard rails are read against; the goal names pages, images and clips, so one of each of the latter goes through the same door.

Done when

Six to eight pages by Day 8 (stretch ten), each made by pasting the brief into a model (from src/lib/brief.ts by hand until Deploy 3, the Make tab after), pasting the answer into the sheet, adding, and reviewing on the phone against the three questions — does it respond to the first touch, does it fit 375x667, would you show it to someone — with a one-line verdict in docs/RETENTION.md's review table, about 45 min each (6 h); at least two use window.blitz.save per the docs sentence and are confirmed on the phone after Deploy 2 to keep state across BLITZ and back; one image and one clip through the same door with the brief's variants (0.75 h); every failure that teaches a brief edit is noted for a BRIEF_VERSION bump (0.75 h). Under (a) four pages; under (b) ten to twelve and no image or clip.

Founder 2

The daily bench on the phone, emptied every day

2.5 h

Why

The bench only means anything if it is emptied daily; nothing goes live unreviewed on a phone, and the median bench->live is one of the readout's numbers.

Done when

Days 2–14 the bench opened on the phone every day (bench_feed() in the SQL editor with card_set_status until Deploy 4, the strip after) and emptied — nothing older than 24 h — with a phone play before every Live and a reason on every Hide, five to fifteen minutes a day. Verified at each day's end by select max(extract(epoch from now() - created_at) / 3600) from public.card where status = 'bench' < 24 and select count(*) from card_status_log where to_status = 'live' and via = 'sql' = 0.

Founder 3

The phone check of every deploy, the FIT GATE, and the brief's versions

4 h

Why

Nothing goes live unreviewed on a phone, including the product itself; the fit tool is trusted to write verdicts only once its verdicts match the phone; the brief is a living document whose version history is read beside the pass rate.

Done when

A one-line phone verdict in docs/RETENTION.md within the hour of each of the six planned deploys (3 h; rollback is Netlify's previous deploy). Day 6 morning: the FIT GATE — the tool's Day-2 verdicts on the live deck read against the Sprint 1 phone verdicts, agreeing on every card but at most one, or the workflow does not write card.fit yet (0.25 h). Every brief edit a page's failure teaches is committed as a BRIEF_VERSION bump with the date beside it (0.75 h).

Founder 4

The makers, in order: the known makers, the makers already posting AI-made pages, the pilot's repliers, the rest of the list

7.5 h

Why

Three complete loops need twenty or so strangers who try. The pilot's repliers are a favour; people already posting model-made pages on r/vibecoding, r/ClaudeAI 'I built', Bolt and Lovable showcases, X or Bluesky already hold the page and the brief is natural to them — the single change most likely to lift non-founder uploads toward twenty (all three judges). DM only, one to one, nothing posted anywhere, nobody's page linked before a yes.

Done when

Day 5: the two or three makers the founder knows DMed the brief itself, before anything of theirs is linked (1 h). Days 5–7: ten makers already posting AI-made pages found where they post and listed in concierge.csv (who, where, date sent, reply, uploaded, live) (1.5 h). Day 8: the three-sentence 'make one' message — here is a brief, ask any model, paste it in; you will see it on a phone next and your numbers after — to those ten and to the pilot's ~30 repliers, one to one, by DM only, never a post, never where a community forbids it, the link /feed?via=make&r=<the founder's u_ id>&make=1 (2.5 h). Day 11: the second send to the rest of the pilot list only if the freeze gate passed (1 h). Replies answered the same day and any maker who asks handed their numbers (1 h); any takedown honoured the same day by Hide with reason 'takedown' (0.5 h). Verified: concierge.csv complete with dates; >= 20 non-founder uploads by Day 12 or the shortfall written down with n. Under (d) the Day-8 send waits for the Day-7 read; under (a) the hunt shrinks to five and the Day-11 send is skipped; under (b) five makers are DMed by Day 6.

Founder 5

The reads: Day 7 mid-read, the Day-10 loop row, the Day-14 readout

3 h

Why

Every number is read against a row that exists before code ships, by the person who decides the next fortnight; under (d) the Day-7 read is Sprint 1's day-21 read and selects week 2.

Done when

Day 7 before any deploy: loop_funnel(7) and bench_summary() written into docs/RETENTION.md and, under (d), Sprint 1's day-21 D1 with its interval on pilot plus wave-3 browser arrivals, with the week-2 swap named (1 h). Day 10: the loop row written before the fix day starts (0.5 h). Day 14: the readout from /stats and tools/sql/loop-readout.sql into docs/RETENTION.md with n at every funnel step and the interval on every rate, the kill criteria answered one by one, Sprint 3 named (1.5 h).

Founder 6

Fork (d) only: wave 3 of ~50 on Day 2 into the Sprint 1 build

2 h

Why

Sprint 1's gray-zone prescription is one more founder-network wave and the day-21 read; it goes into a build whose visitor path is unchanged so the read is clean.

Done when

Day 2 after Deploy 1's phone check: ~50 sends 1:1 as /feed?via=pilot&r=<uid>, nobody from waves 1 or 2 re-sent, the Sprint 1 message reused (1.5 h); replies answered (0.5 h); >= 40 browser actors with entry_source='pilot' whose first session is Days 2–3, read on Day 7 with n near 150.

Total: about 27.5 hours.

05

The forks: what Sprint 1's read changes

The core above holds under every read. On 22 September, the readout picks one of these and the plan swaps accordingly.

  • a. Sprint 1's Day-14 row shows position-0 hold < 40% and reached-end < 50%: the first minute is the problem — card 0's phone fit and the chrome, not supply, not composition Add: Day 1, before any ruling: tools/fit-check.mjs run by hand against the live deck (Deepstate, chess, the Sprint 1 pages, the platform embeds) and the verdicts written into docs/RETENTION.md — card 0's phone fit read from data, not memory; Day 3 (ruling 9): if Deepstate's verdict is painted_3s < 60% or overflow_x, card_editorial re-ordered in the SQL editor so the deck opens on the best-fitting founder page by fit then card_rank(14) hold — an editorial act RANKING.md permits; nothing promoted on telemetry; Days 8–10, item 9 (1.5 d): migration 0023 card_zero rewriting card_editorial with MOCK_GAMES, feed.json and the e2e fixture in one commit; the one chrome P0 Sprint 1's carried list names first, failing test first; tests/e2e/first-minute.spec.ts (card 0 'running' under 1.5 s from a cold context, meta strip never over the surface, BLITZ and chevron hit areas disjoint); Deploy 6 with a phone verdict; Founder: 2 h re-reviewing every deck card for fit at 375x667 on the phone and hiding by hand what fails; the deck may shrink to six; The outcome is read as written, but position-0 hold on first visits >= 50% becomes the headline beside it, with complete loops reported. Drop: The review strip UI (item 6, 0.75 d) — card_set_status and bench_feed() in the SQL editor from the laptop each evening; the bench, the log and the 24-hour rule stay; The fix day's no-P0 filler (0.5 d) — item 9 is the filler; The hourly fit-check schedule (0.25 d) — workflow_dispatch after each bench pass; Founder: pages shrink from six to eight to four (two that remember); the makers-already-posting hunt shrinks to five and the Day-11 second send is skipped — no hour goes to supply until card 0 holds; the trust floor ('…' with Report / Remove it) waits for Sprint 3.
  • b. Reached-end >= 50% but D1 < 5%: people finish and do not return — the deck is too short; supply through the founder's own uploads and the concierge, with the bench ruling in hand Add: Day 6, item 10 (0.75 d, riding migration 0021): actor.house and the audition pick over house uploads — the internal exclusion applies to voters not uploaders, one ticket per house card, md5(current_date || card_id) — so the founder's newest page gets 'New today' distribution at slot 2; tools/pg/check-audition.sql; the production check select card_id, slot from card_rank(28) where slot='explore' returns a house card the next UTC day or it is fixed before Deploy 4; Founder: ten to twelve pages through the brief instead of six to eight (+3 h); five known or already-posting makers DMed the brief by Day 6 instead of Day 8 (+2 h), each handed a /c/ link that opens playing on a phone once their card is live; The fix day's no-P0 filler is the trust floor (item 8's 0022: Report / Remove it, no auto-hide) if the half day remains. Drop: The hourly fit-check schedule (0.25 d) — workflow_dispatch after each bench pass; The fix day shrinks from 1 d to 0.5 d; Founder: the image and the clip (0.75 h) — pages are the supply the read asked for; the Day-11 second send folds into Day 8 (−1 h).
  • c. D1 >= 10% at n≈100 with the four guard rails met: the bar holds provisionally — composition rescoped for a short deck (bet 4) plus supply (bet 6), still no launch post Add: Days 8–10, item 11 (1.75 d): composition-lite, two-sided — feed(n, seed, exclude) with slots 0–1 from card_rank and a seeded per-device order over the live tail under one hard rule (no two consecutive cards of the same kind: page, image, clip, site) in a plpgsql greedy with an explicit fallback; src/api/sequence.ts as the synchronous client twin; tests/fixtures/sequencer.json asserted by both tools/pg/check-sequencer.sql and tests/unit/sequence.test.ts; the end card's BLITZ re-reads minus the ids held this visit; the Day-10 SEQUENCER GATE on production before Deploy 6; demote-only untouched, no tier reads a rate; The readout adds the fork's own number: two seeds differ past slot 1 in production, no adjacent kinds, the end card's exclude working — reported, not targeted; Still no launch post, no community post, no seed cohort: the day-21 bar on >= 200 browser arrivals is not met by a 100-person pilot; D1 is read again on Sprint 3's cohort. Drop: The review strip UI (item 6, 0.75 d) — SQL editor; The fix day's no-P0 filler (0.5 d) — item 11 is the filler; The /stats loop panel (0.25 d of item 5) — loop_funnel and bench_summary read in the SQL editor; 'Your cards' and card_stats stay; The hourly fit-check schedule (0.25 d) — workflow_dispatch after each bench pass.
  • d. D1 between 5% and 10% at n≈100 (the gray zone): one more founder-network wave, the day-21 read decides Add: Founder, Day 2 (Thu 24 Sep) after Deploy 1's phone check: wave 3 of ~50 through /feed?via=pilot&r=<uid> into Deploy 1's build, whose visitor path is behaviour-identical to the frozen Sprint 1 build (feed() still serves only live rows; the chrome is untouched; only the + sheet's copy and the landing line changed) — 2 h; Day 1, item 12 (0.25 d): the day-21 block in tools/sql/retention-readout.sql (D1 with its interval on pilot plus wave-3 browser arrivals, first sessions Sprint 1 Days 9–12 and Sprint 2 Days 2–3), rehearsed on Sprint 1's rows; Day 7: the read with n near 150 selects (a), (b) or (c) for Days 8–14; that fork's add and drop lists apply from Day 8 with what is left of the fix day; if D1 is still in the gray zone at n≈150, week 2 takes the (a) swap — another wave cannot narrow it and the first minute is the cheapest place to look. Drop: Nothing in week 1: the core ships as written through Deploy 2; Deploy 3 (the Make tab and the end card's count line, the first visible client change) waits until after the Day-7 read and ships that afternoon with Deploy 4; the fit-check workflow still runs from Day 6 because it changes nothing a visitor sees; The pilot-replier and makers-already-posting 'make one' send waits until Day 8, after the read, so the wave-3 cohort's first card is not changed under it.
06

Day by day

  1. Day 1Wed 23 Sep

    Rulings 1–12 by noon (defaults otherwise). Founder with the agent, before code: the Sprint 2 baseline column in docs/RETENTION.md (Sprint 1's Day-14 row plus the loop's zeros) and Sprint 1's carried-to-sprint-2 list read, its first chrome P0 named. Agent: migration 0020 written — the bench, the AFTER UPDATE trigger, card_set_status, submit_* re-created inserting 'bench' with p_meta (content_hash, made_with), card_set_fit, card_by_slug, my_cards, bench_feed, bench_summary, the ui_event kinds — and chained x3 with tools/pg/verify.sh + tools/pg/check-bench.sql; the fit tool's lint half (tools/fit-check.mjs lint, tests/unit/fit-lint.test.ts, the four fixtures). No deploy: the fork is read, not shipped, on Day 1. Fork (a): the fit tool's render half run by hand against the live deck by evening, verdicts into RETENTION.md. Fork (d): the day-21 block added to retention-readout.sql and rehearsed; the wave-3 message ready.

  2. Day 2Thu 24 Sep

    0020's client side: cardBySlug and the /c/<slug> splice at index 0, myCards, uploadMedia's SHA-256 and the 'Made with' input, the 'Added under @handle … once it has been looked at on a phone' copy and toast, the landing line, link.spec, comments-upload.spec and pages.spec updated, share-link.spec.ts green; CI green. DEPLOY 1 (0020 + client). Migrate applies 0020; the card_rank assertion holds (deepstate 0, chess 1). Phone check within the hour: a page from a non-admin test account is absent from /feed in a fresh profile and opens at /c/<slug> for its creator; the founder's own upload lands on the bench too; select public.card_set_status(...) flips it and card_status_log has the row with via='rpc'; a direct update in the SQL editor leaves a via='sql' row. Agent: the fit tool's render half run by hand against the live deck (all forks) — verdicts into RETENTION.md for the Day-6 FIT GATE. Fork (d): WAVE 3 sent after the phone check. Agent starts the save channel.

  3. Day 3Fri 25 Sep

    Save channel: the viewer's load/state/save relay and the probe's window.blitz, WebCardHost + src/lib/cardState.ts, public/cards/e2e/remember.html, cardState.test.ts and save-channel.spec.ts green with web-card-sleep.spec.ts unchanged -> main. DEPLOY 2 if green by evening, else it rides Deploy 3 on Day 6. Phone check: the counter fixture (uploaded from the founder's account as the first 'page that remembers') keeps its count across BLITZ and the chevron; localStorage holds only blitz:state:* keys. Fork (a): card_editorial re-ordered in the SQL editor from Day 1's verdicts if ruling 9's condition holds.

  4. Day 4Sat 26 Sep

    No deploy. Agent draft only, to be verified Monday: src/lib/brief.ts and brief.test.ts (the CSP-list equality), the Make tab in LinkSite.tsx with fence-stripping and 'Made with', ?make=1 and 'make_open', the end card's 'You played N of M' line, make-one.spec.ts; fit-check.yml (workflow_dispatch + hourly with the early exit) and the CI fixtures step; tests/unit/workflows.test.ts. Founder (3 h): the first two pages by hand — the brief pasted from src/lib/brief.ts into a model, the answer pasted into the Upload tab, added, reviewed on the phone with a verdict line; one uses window.blitz.save per the docs sentence; brief edits noted.

  5. Day 5Sun 27 Sep

    No engineering. Founder (3.5 h): two more pages, one image and one clip through the same door with the brief's variants; the bench reviewed with bench_feed() and card_set_status in the SQL editor and emptied; the two or three known makers DMed the brief itself; the hunt for ten makers already posting AI-made pages begun in concierge.csv. Fork (b): five makers' DMs drafted for Day 6.

  6. Day 6Mon 28 Sep

    Morning: the FIT GATE — Day 2's tool verdicts on the live deck against the Sprint 1 phone verdicts; on pass, fit-check.yml dispatched by hand and its hourly schedule left on; on fail the tool is fixed first. Saturday's drafts verified (typecheck, vitest, e2e); migration 0021 (card_stats, loop_funnel) chained x3 with check-stats.sql. DEPLOY 3 (Make one + 0021 + the save channel if it slipped). Phone check: the Make tab shows the brief, copy works and 'brief_copy' lands, a pasted answer adds and is next in the founder's feed, the 900 px fixture from a test account gets 'wider than a phone' within the hour, card_stats returns for a card the pilot played. Fork (b): actor.house and the audition pick ride 0021, check-audition.sql green. Fork (d): Deploy 3 waits for tomorrow's read; only the fit workflow starts.

  7. Day 7Tue 29 Sep

    MID-READ first (1 h): loop_funnel(7) and bench_summary() into docs/RETENTION.md, and Sprint 1's day-21 D1 with its interval — under fork (d) this selects week 2 and that fork's add/drop lists apply from Day 8. Then 'Your cards' on the profile with stats_open, the /stats loop panel and bench_summary, the ReviewStrip under /feed?bench=1 (b, d) -> main. DEPLOY 4 (under (d), Deploy 3's items ship here too). Phone check: the profile shows the founder's numbers and a test account's bench state and fit line; the strip flips a bench card Live in one tap and the next comes up; a Hide needs a reason; select * from loop_funnel(14) returns every column for @rand and 42501 otherwise. Fork (b): select card_id, slot from card_rank(28) where slot='explore' returns a house card or 0021's audition block is fixed before this deploy.

  8. Day 8Wed 30 Sep

    The makers' build gate: tools/column-check.sql clean (fit on every hosted web card older than 24 h, zero to_status='live' rows with via='sql', 0019's stamps still zero missing on a headless tour), fit-check.yml green on its last three real runs, the cold walk in a fresh phone profile (landing -> feed -> + -> Make one -> brief -> paste -> the account door; /feed?make=1 opens on the tab). DEPLOY 5 only if anything changed; else Deploy 4 is the makers' build. Founder (2.5 h): the 'make one' message to the ten makers already posting and the pilot's ~30 repliers, one to one, via /feed?via=make&r=<uid>&make=1. Deck >= 12 live (Sprint 1's deck plus the founder's pages), every one flipped from a phone. Fork (a): item 9 starts (card_zero, first-minute.spec, the chrome P0). Fork (c): item 11 starts (feed_seed, the client twin, the fixture on both sides).

  9. Day 9Thu 1 Oct

    Makers arrive. No deploy. The bench opened morning and evening on the phone; fit verdicts read on the strip; every Live tap on a phone; takedowns the same day. Agent on standby: fit-check runs and their verdicts, ingest_batch accepted/rejected, card_status_log (no via='sql' to live), localStorage keys audited on the founder's phone (blitz:state:* only, none over 4 KB), the maker replies' failure modes collected for tomorrow. Forks (a) and (c): the swap item continues.

  10. Day 10Fri 2 Oct

    FIX DAY, P0 only: the Day-10 loop row written first (brief copies, make_open, uploads by class, fit pass rate, hours bench->live, live non-founder cards, stats opens, n); fixes with a failing test first -> DEPLOY 6, phone-checked within the hour. With no P0 the day goes to the swap-in — (a) item 9 with first-minute.spec green and a phone verdict; (c) item 11 after the SEQUENCER GATE on production (two seeds: same card 0, different past it, no adjacent kinds); (b), (d) the trust floor (0022 Report / Remove it, no auto-hide, rows on the strip). Freeze gate in the evening: column-check clean, fit-check green, no bench card older than 24 h, no open P0.

  11. Day 11Sat 3 Oct

    Founder: the second send to the rest of the pilot list, only if the gate passed (1 h); the bench emptied; replies answered. FREEZE after the last send: the client bundle does not change again this sprint except by rollback, so the loop's numbers are one build; status flips are RPC calls and continue; the fit workflow keeps writing verdicts.

  12. Day 12Sun 4 Oct

    Nothing ships. Founder (1 h): answers makers, hands numbers to anyone who asks (the panel does it; the SQL editor if the panel was cut), empties the bench, honours any takedown. Last arrival day whose next day is complete inside the window for the D1 diagnostic by via.

  13. Day 13Mon 5 Oct

    Docs-only commits, the bundle byte-identical: docs/CREATING.md, EMBEDDING-A-SITE.md's 'Make your page remember', RANKING.md's never-ranks line, CONTENT.md's numbers table, ACCOUNTS.md's 'Your cards', STRATEGY section 5's two-line amendment. tools/sql/loop-readout.sql rehearsed against real rows with the interval formula; tools/column-check.sql's fit and status-log assertions pass; anything unfixed listed as carried to sprint 3; the concierge.csv tally and the per-page review table closed.

  14. Day 14Tue 6 Oct

    READOUT, no deploy: /stats -> loop_funnel(14) and bench_summary() beside retention_daily(14,'make'), (14,'pilot') and (14,null); tools/sql/loop-readout.sql's block; the row into docs/RETENTION.md with n at every funnel step and the interval on every rate; the rule read first (zero unreviewed cards in feed(), zero via='sql' flips to live), then the funnel, the fit pass rate by brief version, the bench latency, the guard rails, the diagnostics; the kill criteria answered one by one; Sprint 3 named with both headlines written (ruling 10); the brief's version history written beside the pass rate.

07

What is cut first

In this order, when the hours run out:

  • The hourly fit-check schedule (item 4, 0.25 d) — workflow_dispatch after each day's bench pass, or npm run fit:check by hand on the founder's machine with the verdicts pasted through card_set_fit over the DB URL; the tool itself is never cut
  • The review strip (item 6, 0.75 d) — bench_feed() and card_set_status in the SQL editor from the laptop each evening; the bench, the trigger, the log and the 24-hour rule stay
  • The /stats loop panel and bench_summary on the page (0.25 d of item 5) — both RPCs stay and are read in the SQL editor
  • 'Your cards' on the profile (0.5 d of item 5) — card_stats and loop_funnel stay; makers who ask are handed their numbers by hand; stats_open then cannot be logged and the loop's last step is reported as n/a, not as 0
  • The 'Made with' input and the end card's count line (0.2 d across items 1 and 3) — generation stays a column; the readout splits by brief_version only
  • The trust floor on the fix day (item 8's 0022) — a takedown stays a same-day Hide with reason 'takedown' by hand, which is what the DM already promises
  • The founder's one image and one clip (0.75 h) — pages first
  • The second send on Day 11 — only if the freeze gate fails; the sprint then reports fewer than twenty stranger uploads honestly
  • Never cut: migration 0020 with the trigger and card_set_status (the bench is the constraint that lets strangers' pages in at all, and the share link that resolves them), the save channel's host and viewer path with its e2e, the brief in src/lib/brief.ts with the CSP-list test, tools/fit-check.mjs runnable by hand and the Day-6 FIT GATE, the phone review of every deploy and of every bench card before Live, the Day-1 baseline column, the Day-7 mid-read, the Day-14 read
08

The readout and the kill criteria

Day 14, Tue 6 Oct, from /stats (loop_funnel(14) and bench_summary() beside retention_daily(14,'make'), (14,'pilot') and (14,null)) and tools/sql/loop-readout.sql, written into docs/RETENTION.md beside the Sprint 2 baseline column and the Day-10 row, n at every funnel step and a binomial interval on every rate. Read in this order: (1) the rule — select count(*) from public.card c where c.status='live' and c.creator_id is not null and c.creator_id <> @rand's actor and not exists (select 1 from card_status_log l join actor a on a.actor_id = l.actor_id where l.card_id = c.card_id and l.to_status='live' and l.via='rpc' and a.is_admin) must be 0, and select count(*) from card_status_log where to_status='live' and via='sql' must be 0; the trigger makes both facts rather than conventions, and they are read before any number; (2) the funnel: brief copies -> make_open -> uploads -> fit ok -> live -> 10 sessions from 3 networks -> stats opened, by non-founder actors, complete loops against the >= 3 target (stretch 5) and non-founder uploads against >= 20; (3) fit pass rate on stranger pages, split by brief_version present versus absent and by made_with; (4) median hours bench->live and the oldest bench card at each day's end from card_status_log and bench_summary, with the cap reported beside the live count; (5) the guard rails carried from Sprint 1 on the same via filters — position-0 hold, reached-end, back taps — not below the Sprint 1 Day-14 row by more than the interval, with D1 by via reported with its interval and not targeted this sprint; (6) diagnostics, reported never targeted: save-channel keys on the founder's phone, uploads per brief copy, Hide reasons by kind, card_stats reads per maker, the fork's own number (a: card 0's hold from first-minute.spec and card_rank; b: whether the audition slot showed a house card each day; c: two seeds differing in production and the end card's exclude working; d: the Day-7 D1 with its interval), DAU with internal excluded, card_rank(14) for the deck (whether any founder page has 20 votes yet). Not read: any ranking number as a verdict under 20 votes, any promotion, any A/B, D7, anything a card self-reported. Demote-only stays exactly as written; the only thing this readout decides is where Sprint 3's engineering days and content hours go, and there is still no launch post until D1 holds on >= 200 browser arrivals at a day-21 read.

  • Any non-founder card live in feed() without an admin card_set_status log row, or any card_status_log row with to_status='live' and via='sql', or any status change by anything but card_set_status or a hand Hide: the section-9 rule broke in code — nothing else ships until the path that skipped it is closed, and Sprint 3 starts there
  • The FIT GATE fails on Day 6 (the tool disagrees with the founder's phone on two or more deck cards): the workflow does not write card.fit until it matches; the strip and my_cards say 'not checked yet' honestly and the founder's review is the only fit verdict that fortnight
  • Fewer than 20 brief copies by Day 14 with both sends made: the door was never seen — the end-card 'Make one' door returns first in Sprint 3, and the sprint opens with where makers actually are, not with more supply tooling
  • Twenty or more brief copies and zero complete loops: the loop is too long for a stranger — read where it drops (copy -> upload: the model's answers do not paste; upload -> fit ok: the brief is wrong; fit ok -> live: the founder's review hours; live -> 10 sessions: the deck has no traffic, a Sprint 1 problem again; 10 -> stats opened: the numbers are not found) and Sprint 3 fixes the single largest drop, nothing else
  • Fit pass rate on stranger pages under 30%: the brief is wrong, not the makers — it is rewritten before another DM goes out, and BRIEF_VERSION 2's pass rate is read separately
  • Median bench->live over 24 h or any bench card older than 48 h: the founder cannot review at this rate; the cap is real and honest, and Sprint 3 adds no supply channel until review hours exist — no content team, so the cap is the answer, not a hire
  • A saved-state page fails to restore on the phone after BLITZ-away-and-back, or save-channel.spec.ts flakes twice: the channel ships dark the same day — the host path kept, window.blitz and the brief's save lines pulled — rather than half-remembering; a key over 4 KB accepted, or one card's state readable by another (audited on the phone and in the spec): the viewer is rolled back to Deploy 1 the same day
  • Position-0 hold or reached-end below Sprint 1's Day-14 row by more than the interval, on the same via filter, after the founder's pages entered the deck: the pages the loop produces are worse than the linked sites for a stranger's first minute — the three questions become hard lines in the brief and card 0 is re-decided from card_rank, an editorial act
  • The founder's own pages take over 45 minutes each through the loop: the brief is too long for a person; it is cut to a screen before the Day-8 send
  • Fewer than 20 non-founder uploads with both sends made: the D1-style n problem — the loop is reported on the makers there were, the funnel with n, and no rate is spun into a verdict
  • Fork (d): D1 still in the gray zone at the Day-7 read on ~150 browser arrivals — week 2 goes to the first minute (the (a) swap), because another founder wave cannot narrow it; no seed cohort, no post
  • A takedown not honoured the same day, or a stranger's page found live that was never opened on a phone: the trust floor (Report / Remove it) becomes Sprint 3's first item regardless of the numbers
09

Deliberately not this sprint

  • Any generation endpoint, any model call from our side, any 'make anything' prompt box, any auto-flip to live on a fit verdict — never for anonymous visitors, and not for accounts this sprint: the brief is the creation path; a person admits what executes in front of strangers
  • The in-sheet 375x667 preview — it needs a preview mode in the viewer (a srcdoc frame inside the feed inherits the feed's CSP, not the viewer's) and a scaled frame in a sheet on a 375-wide phone; the card itself, next in the maker's own feed and unlisted, is the preview, and the fit verdict on the profile is the check
  • The end card's 'Make one' door and the image/clip aspect line — ?make=1 and the + sheet's third tab are the door this fortnight; the media viewer letterboxes; both return in an hour if the brief-copies kill criterion fires
  • Composition outside fork (c): feed(n, seed, exclude) as a core item, the on-device returning order and held record, tier-drop demotion, verb families, 'N new since <day>', the rewrite of every e2e spec off the slots-0-to-4 contract — over an eight-card deck the shuffle is inert and Sprint 1's fork (a) says not composition
  • Add-to-home-screen on the end card, web push, D7, the return-read columns (unseen_at_0, return_pos0_hold, card_exposure) — second-visit mechanics for a cohort that does not exist yet; unreadable at five to ten return visits
  • The sixty-candidate platform harvest and tools/harvest/check.mjs — human-made platform embeds are the comparison, not the thesis; tools/fit-check.mjs runs on a URL by hand for the one or two the founder links through the + sheet
  • bench_cap() enforcement with an 'override:' path — at pilot traffic the formula's floor binds and would throttle AI-page supply exactly when fork (b) says the deck is too short; the cap is reported by bench_summary() and the founder's daily phone pass is the cap
  • The blocked_term filter and pageSummary as an admission gate — a client-computed string refusing an upload is theatre and contradicts 'hint, never the gate'; the phone review is the gate and content_hash is the part worth keeping
  • Auto-hide on removal requests or on three reports from three networks — three phones would then remove any card, a bigger lever than demote-only ever gave a forger; if the trust floor ships it is a queue the founder clears the same day
  • The claim flow (claim_card, ?claim= tokens, 'This is mine'), 'Open in browser' and source_url — nothing harvested means nothing to claim; a DM yes is relabelled by hand and the trigger logs it
  • The nightly recheck_external_cards() under pg_cron and the pg_cron dashboard step — two or three external cards; a weekly hand run of embed_check until the deck has more than a handful
  • The card_rank house-ticket rewrite outside fork (b) — elsewhere it touches the fabrication-proof ranker for no reader
  • Per-account server-side saved state (Supabase rows for window.blitz.save) — the localStorage relay is per device; accounts get rows when a maker's page needs to remember across devices, not before
  • Remix-by-pack (STRATEGY bet 7) — it was for the eleven hosted toys, which are hidden; one possible later creation path, not this sprint's
  • Promotion on telemetry, per-actor affinity, any change to card_rank beyond the exclusions already in it (and (b)'s audition tickets), anything a card or client reports entering any rank — fit, saved state, likes, made_with, card_stats included
  • The launch post, Show HN, any community post, the 200–300 seed cohort, paid dollars — no launch post before the day-21 D1 bar on >= 200 browser arrivals
  • A topic taxonomy, an onboarding screen, a pick-your-interests step, coach marks — the brief is the only text a maker reads and the first card is the only onboarding a visitor gets
  • A content team or paid generation spend — the founder's ~15 h/week inside the loop is the supply; hiring for content is the signal the loop failed
  • The hosted-card 'Still there? Tap to continue' restyle and the wasm idle redesign — no wasm card is served; the html viewer's probe keeps touched pages alive
  • Mirror mode for anyone outside mirror_host — consent only, as 0016 wrote it
  • Merging actors across devices (ACCOUNTS.md's known limitation) — card_stats excludes the creator's own actor on this device only; the limitation is written under the number
  • growth_weekly, sprint_readout, a chart on /stats — one admin RPC (loop_funnel) beside retention_daily, tables not charts
10

Rulings only the founder can make

Each with the default the sprint runs on if nothing is said.

  • 1. Bench visibility (the one place section 9 bends): a bench card reachable at its /c/<slug> link for anyone versus creator-or-admin only — default: creator-or-admin only until the fit tool has written fit_ok = true, then anyone with the link (unlisted: absent from feed(), cards_by(), the end card's re-read and the audition), so no unreviewed stranger page executes for a third party before a verdict exists; hidden and blocked never resolve; the founder can widen or tighten card_by_slug with one line
  • 2. Who skips the bench: nobody (the founder's own uploads wait too and are flipped from the strip in one tap — one code path, the rule kept literally, the strip dogfooded) versus admin uploads live directly — default: nobody skips
  • 3. What a failed fit does: accept to the bench with the verdict visible to the maker and the reviewer (never auto-hide, never refuse) versus refuse the upload with the reason — default: accept to the bench; a person decides, and a page that paints 40% might be a page whose first touch does everything
  • 4. The save channel's scope: this device only (localStorage on the feed's origin, keyed by card id, 4 KB, one write a second) versus per-account rows now — default: this device only; the founder can name the first page that needs cross-device memory
  • 5. The brief's external allowance: the html viewer's CSP list as it stands (cdnjs, jsDelivr, unpkg, esm.sh, skypack, jspm, Google Fonts) versus inline-only — default: the list as it stands, because the viewer already permits it and a model asked for p5 will reach for a CDN; brief.test.ts keeps the two identical
  • 6. Make one for anonymous visitors: the brief readable by anyone with the account door on Add (default) versus the whole tab behind the door — default: readable; a brief is text, and the person has to leave for minutes to ask a model before they could possibly need an account
  • 7. The maker ask, in order: the two or three known makers (Day 5), then ten makers already posting AI-made pages on r/vibecoding, r/ClaudeAI 'I built', Bolt and Lovable showcases, X or Bluesky together with the pilot's repliers (Day 8), then the rest of the pilot list (Day 11), by DM only, never a post, never where a community forbids it, nobody's page linked before a yes — default: that order; under (d) the Day-8 send waits for the Day-7 read
  • 8. What a maker sees: 14-day counts with the creator's own sessions on this device excluded, impressions as distinct sessions and viewers as distinct actors, shown from the first impression with no minimum n hidden behind a dash, and the cross-device limitation written under the table — default: yes; a maker who sees '3 people, 2 stayed' has been told the truth
  • 9. Under fork (a), whether card 0 is re-decided by editorial act on Day 3 from the fit verdicts and card_rank(14), or Deepstate stays until outvoted at 20 votes as Sprint 1's ruling said — default: re-decided on Day 3 only if the fit tool says Deepstate does not fit 375x667 (painted_3s < 60% or overflow_x); otherwise the Sprint 1 ruling stands and 0023 card_zero on Days 8–10 writes whatever the ruling is with the fixtures in one commit
  • 10. Sprint 3's headline, chosen now so the readout cannot be spun: complete loops by strangers (this sprint's number) or D1 on browser arrivals at day 21 (STRATEGY section 7's bar) — default: both are written, D1 is the bar for any launch post, loops are the bar for the creation path, and neither substitutes for the other
  • 11. The fit-check cadence on a private repo's metered Actions minutes: hourly with an early exit when nothing is unchecked (a checkout and a psql per empty hour) plus workflow_dispatch, versus dispatch only after each bench pass, versus by hand — default: hourly with the early exit; if the minutes bill by Day 5 says otherwise, dispatch after each bench pass, and the column-check assertion stays at 24 h either way
  • 12. 'Made with' as an optional self-declared field on the Upload and Make tabs stored in card.generation, shown on the review strip and the maker's own profile only, never verified, never in the visitor strip — default: yes, optional; the model name is the maker's word and the readout splits the fit pass rate by it
  • 13. Under fork (c), whether composition-lite ships this sprint (if Deploy 3 is live by Day 6 and the Day-10 sequencer gate passes) or waits for Sprint 3 — default: ships, because a deck that differs per device is the first thing a second visit needs and it reads no rate; it is the first thing dropped if the fix day has a P0