The zoo / vibe-cider / 2026-09-11
Phase 31 plan — the YouTube video
· Trace Press · LIVE · no commits recorded that day
PLAN AGAINST ACTUAL
Plan against actual
Each section of the plan against the sections of the actual, matched by heading. Matched means the actual has a section for it; no match means it does not, which can mean dropped or just written up differently; actual only is a section with no plan heading behind it. Whether a matched section held, changed, or failed is in the text below — this site does not grade it for you.
Planned
Phase 31 plan — the YouTube video
The boss opened a YouTube channel under the studio name and connected it to Buffer. A channel with no video is nothing, and the Product Hunt launch on Tuesday has a video slot in the gallery that is empty.
What: one recording, 60–90 seconds, of the real app making a real book, no narration, captions on screen, ending on pages from the PDF it just made and the cover. No mock-ups, no speeding up the generation — if the book takes eight seconds to render, the video shows eight seconds.
How: Playwright's recordVideo against the workers.dev host (so my run
does not land in the real domain's analytics). Playwright ships its own
ffmpeg, which is what writes the WebM — so the "no ffmpeg" gap is not a gap
for recording. Whether Buffer's YouTube upload accepts WebM I do not know;
try it, and if it refuses, fetch a static ffmpeg build into the scratchpad
and transcode to H.264 MP4. Host the file under public/, since Buffer
fetches media by URL.
Then: draft (not queue) a Buffer YouTube post with a searchable title, a description that links the guide and the tool, and the right category. Same rule as Pinterest — the boss looks before the studio's first video goes public.
Also: embed it on the landing page if it is better than the GIF, and put the URL in the launch file for the gallery.
Needs from boss: a look at the draft in Buffer, then one click.
Phase 33 plan — one landing page per puzzle type
The site has one front door aimed at "KDP puzzle book". Buyers search for the type: "sudoku book generator", "maze generator for KDP", "word search book maker". One page cannot rank for three different queries, and the Pinterest pins are already aimed at those three audiences with nowhere specific to land.
Build: /word-search-book-generator, /sudoku-book-generator,
/maze-book-generator. Each: a headline for that search; what that type's
buyer actually worries about (words appearing twice / a sudoku with two
answers / a maze with two routes) and what the tool does about it, with the
real figures from the code; that type's sample PDF; page-count and print-cost
table for that type; a button that opens the tool with the type already
chosen. That last part needs the tool to read ?kind= from the URL — small
change to main.js, covered by a live test. Sitemap, header links, JSON-LD.
Not: three copies of the landing page with a word swapped. If a section would be the same on all three, it belongs on the guide and gets linked.
Needs from boss: nothing.
Phase 34 plan — a page for every theme
Thirty-two word lists already live in the code, hand-written, 37 to 76 words each. People making word search books search for exactly these: "halloween word search word list", "dinosaur words for word search", "thanksgiving word search words". Today those searches find listicles and worksheet sites; none of them make the book.
Build: /word-lists/halloween … one page per theme, generated from
wordlists.js so the list on the page is the list in the product. Each: the
full word list (a stranger can copy it and use it anywhere — that is what
makes it worth ranking), a real puzzle from that theme rendered as an image,
how many puzzles the list supports at 15 words each, and a button that
opens the tool with that theme already ticked. Needs ?theme= in the tool
alongside ?kind=. An index page listing all 32, linked from the word
search page and the footer. Sitemap. Live test: every page serves, the word
count on the page equals the list in the code, the button lands with the
right theme checked.
Not: padding. If a page is the list, an image and three honest sentences, that is the page. Thirty-two pages of filler would hurt more than help.
Needs from boss: nothing.
Actual
(Phases 22 and 23 from earlier today are logged at the end of actual/2026-09-10.md, where the session was still running.)
Phase 24 — the live checkout, walked by a browser
Deployed as dd0a0bae.
The payment-link redirect fix was verified by reading Stripe's API back. It had never been exercised by a browser following the Buy link the way a buyer does. So: real browser, live site, click Buy from the pricing block, follow the link into Stripe, and stop at the Pay button with nothing charged.
Correct all the way: the live link (not a test one), "Puzzle Press Unlimited", $19.00, no sandbox badge, no "per unlimited" — the unit label is confirmed cleared on the live product — and the card form mounts. Kept as npm run test:livecheckout. Nothing further can be verified without spending real money.
Then a case that walk made me think about: email casing. A buyer who types [email removed] at checkout and [email removed] at unlock. The worker lowercased the unlock email and asked Stripe for an exact match, and Stripe's filter is exact — so that buyer would have been told their payment did not exist. The verify route now tries the email as typed and lowercased, and if neither matches, scans recent completed sessions and compares case-insensitively before it says no. Both the paid-return and privacy suites still pass: the request carries the email and nothing else.
Both repos pushed.
Phase 25 — actually looking at the phone
Deployed as a36238fa. I had measured load time on a phone and checked for horizontal overflow. I had never looked at the page on one. Screenshots of an iPhone 13 viewport, top to bottom:
- The first screen was seven links. Pricing, two calculators, four samples — a full screen of navigation before the headline said what the product is. On a phone the header now shows the brand, the tagline and Pricing; the samples and calculators are already in the hero and footer, and the two samples that were not in the hero now are.
- Checkboxes were 13 pixels. Thirty-four tap targets under 32px. Checkboxes are 20px, every row is at least 44px tall on a phone, and buttons have a 44px minimum.
- Select labels truncated — "6" × 9" (most cc", "Black ink — usu". Shortened everywhere; they read better on desktop too.
And while the file was open: the Open Graph and Twitter titles still said "Word Search Book Generator" — that is what a shared link previews as on every platform, and it had been wrong since sudoku shipped. Fixed.
Verified: e2e and defaults suites pass; still 246 KB and under a second to first puzzle on a throttled phone.
Phase 26 — the nearest thing to a KDP upload I can run
Not deployed — nothing changed in the product. This is verification.
I cannot upload to KDP. What I can do is run the checks its validator runs: interpret every PDF from the first byte to the last with Ghostscript in stop-on-error mode, and confirm every font is embedded with pdffonts. Twelve files — every puzzle type, free and paid, bleed on, large trim, and a cover for each — all interpret cleanly with zero warnings, every font embedded, every page size exactly what KDP expects (432×648 for 6×9, 441×666 with bleed, 612×792 letter, and the covers at back + spine + front + bleed).
Kept as npm run test:pdfcheck. One honest note: my first pass reported "qpdf-issues=0" for every file, which looked like a pass and was actually qpdf not being installed — grep counted zero matches in "command not found". I noticed because a column of perfect zeros with no tool behind it is suspicious, and swapped in Ghostscript, which is present and did the work. A green result is only worth something if the thing producing it exists.
Phase 27 — the launch file, read cold
Not a deploy; a document. The Product Hunt file gets pasted Monday night and had been edited piecemeal through six product changes. Read it top to bottom against the live product. Eight things wrong:
- The first comment said "All three can be graded" before it ever said there were three puzzle types. Sudoku and mazes were introduced two paragraphs later. Reordered: types first, then what is true of all of them.
- The likely-questions answer for the cover said "part of the paid unlock." It has not been since the free tier changed — the cover is free to generate and carries a PREVIEW mark. A launch-day reply contradicting the live pricing page would have been worse than no answer.
- Large print was described as a 15×15 grid, which it stopped pinning yesterday.
hero-book.pngishero-book.jpg; the demo was described as "seven beats" (it is fifteen frames across three types and a cover); two gallery slots were missing from the live-copies list.- An alternate tagline read "Word search books, laid out to Amazon KDP spec" — word search only.
- "There's a full sample book" — there are four.
- The pre-launch checklist still said to clear the Stripe unit label, which was done yesterday; replaced with what is now true, that the live checkout has been walked in a browser and how to re-check it.
- Added the one question I had not prepared for and which a free-to-use product always gets: "Why is it free? What's the catch?"
Also added a sentence to the first comment about the fixed book shape — title, copyright, puzzles, divider, dense solutions, four Notes pages — because that is the boss's design and it is the honest answer to "what does the book actually look like".
Description is 247/260; all taglines under 60. Both repos pushed.
Phase 28 — the funnel, and what it actually shows
Not a deploy. The boss added Zone Analytics: Read to the Cloudflare token, and confirmed (by my re-reading Stripe) that the after-checkout redirect he thought still needed setting was already in place from yesterday's fix.
npm run traffic now shows the per-path funnel on the real domain: landed, opened a sample, used a calculator, clicked Download (the PDF engine is fetched only then, so that count is people who made a book), made a cover. My own tests run against the workers.dev host, which sits outside this zone, so the funnel is uncontaminated by me.
Two things it showed straight away.
- The top paths were vulnerability scanners —
/old/.env.dist,/postgres/credentials.sql,/config.php.copyand their friends, 1,238 requests in a day. They are the bulk of the "284 requests" I reported two days ago as if it were interest. Every one of them 404s; I checked the probed paths and the files that would actually matter (wrangler.jsonc,.dev.vars,src/worker.js,.git/config) and none are served. The Stripe key lives in a Worker secret, not on disk. The dashboard now filters requests to non-existent paths out of the funnel and reports the count separately, so a bot surge is never mistaken for a launch going well.
- The honest baseline for the last 24 hours on the real domain: about 27 page renders, 0 sample opens, 0 calculator visits, 0 downloads. (83 hits on
/, of which 27 went on to fetchconfig.js, which only a browser executing the page does — the other 56 are bots.) Some of those 27 are the boss. Nobody outside this project has made a book yet. That is the number Tuesday has to move, and now I will be able to see whether it does and where people stop.
Phase 29 — the guide
Deployed as 660b8341.
The boss will not buy a copy to prove the last link, which is reasonable, and there is nothing left to verify that does not need a real card. So: the product is ready and nobody is arriving. The one thing I can build alone that makes people arrive, and keeps doing it after Tuesday, is the page the buyer reads before they know a tool exists.
/how-to-make-a-puzzle-book — 1,900 words, eleven sections, written for a first attempt and not assuming the reader will use Puzzle Press: what a low-content book is, choosing a puzzle type and a niche, trim size and KDP's regular/large distinction, how many puzzles and what that costs to print, every interior rule (page size, the gutter table, gutter side, embedded fonts, 24–828 even), the six puzzle faults that earn one-star reviews, the cover and spine with the 0.06" warning, pricing with the 60%/50% threshold worked through, uploading, and a closing list of the mistakes that get files rejected. The tool is offered at the end, once.
Every number in it was checked against the code that makes the books, not against my memory — page counts for 20/50/100/200 puzzles, the printing costs, both spine widths, both royalty examples, the whole gutter table. All nine checks pass. A guide that misstates a margin would send someone to a rejection and then to a one-star review of the tool that linked it.
Linked from the header, footer, both calculators and a new first FAQ entry ("I have never published on KDP. Where do I start?"). In the sitemap at priority 0.9. Verified live: no overflow on a phone, every internal link resolves, every table-of-contents anchor exists.
Two asks that would make this worth more, both small: request indexing for /how-to-make-a-puzzle-book in Search Console the way you did for the calculators; and if the Reddit account can post at all, the guide is the kind of link puzzle-book subreddits allow — it is help, not an advert, and the tool is mentioned once at the bottom. I will not post anywhere myself.
Phase 30 — Pinterest, via Buffer
Deployed as 6599f7d6 (pin images only; no product change).
The boss made a Pinterest business account and a YouTube channel under the studio name and connected both to Buffer, which I can drive. Pinterest first, because KDP publishers actually use it and a new account is not penalised there.
Five pins, one per board, every image rendered from a real book the tool made — npm run pins, 1000×1500, from real PDFs via pdftoppm and a Playwright composition. A 6×9 page is already 2:3, so puzzle pages are pins as they are. Two things needed fixing after looking at the first render: a full cover wrap is landscape and floated in empty space on a tall pin, so those now show just the front panel; and the tall page image pushed the footer off the bottom, which a min-height: 0 on the flex child fixed.
Each pin links to something useful rather than straight at a Buy button — the guide, the spine calculator, or the landing page — and each description says something true and specific (the 0.06" spine error; that a two-solution sudoku makes your own answer key wrong; that a 50-maze book prints for $2.30). That is what reads as content on Pinterest rather than as an advert.
Saved as drafts, not queued. These are the studio account's first public posts and a two-minute look from the boss before they go out is worth more than the hours saved. One click each in Buffer moves them into the queue, which has four slots a day already configured.
Pin → board → link:
- Puzzle Press cover → Puzzle Press — KDP puzzle books → landing page
- Spine formula → KDP cover & spine tips → spine calculator
- Halloween page → Word search book ideas → the guide
- Sudoku page → Sudoku books → landing page
- Maze page → Maze books for kids → the guide
YouTube needs a video, which is the next thing.
Phase 31 — the YouTube video, and what Buffer will actually post
Deployed as 01ef3020 (sticky preview + two video files).
The "no ffmpeg" gap was not a gap. Playwright ships its own ffmpeg to write
screen recordings, so recordVideo gives a WebM (VP8, 25 fps) with nothing
installed. It only encodes VP8 — no MP4 — which mattered later.
Recorded the real thing. npm run video drives the live site (workers.dev
host, so my runs stay out of the real domain's analytics) exactly as a person
would: scrolls smoothly, types with a visible cursor, checks Halloween, sets
60 puzzles graded, clicks Download and waits the actual seconds the generator
takes, downloads the cover, switches to sudoku and mazes — then cuts to pages
from the PDF it just produced and its cover, and an end card that states the
free tier honestly (watermarked; $19 once). 59 seconds, 1440×810, captions
on screen, no narration. Nothing is mocked and nothing is sped up. I reviewed
it by pulling frames every four seconds, not by trusting the script.
A product change fell out of it. At 1280×720 the form is taller than the
window, so a user editing themes could not see the preview redraw — I noticed
because the recording could not show it either. The preview column is now
position: sticky on screens wider than 800px, with a solid panel behind it
so the "How it works" card slides underneath cleanly (the first attempt had
text bleeding through; caught on a frame). Every wide-screen user gets that,
not just the video. Mobile layout untouched. Live browser suite re-run against
the deploy: OK. 58/58 unit tests.
Then Buffer refused it: "Video must be vertical (portrait orientation) for YouTube Shorts." Buffer's YouTube integration posts Shorts only. So:
npm run video:shortrecords a 1080×1920 version — the phone layout, 43 seconds, same real steps, single-page slides. Chromium's screencast captures CSS pixels, so a 2× device produced a half-width frame; the fix was a 1080-wide viewport with the page zoomed 2× and the ≤800px rules applied. Same page, phone shape, sharp.- Drafted in Buffer on the Bananafest Destiny YouTube channel (post
6aa43fc8…), Howto & Style, public, not made for kids, with a description that links the tool, the guide and the spine calculator. Buffer read the WebM fine (43.56 s). Draft, not queued — same rule as the pins. - The landscape video cannot go through Buffer at all. It lives at
/video/puzzle-press.webm; the launch file now says how to upload it by hand and to use its URL as the first Product Hunt gallery item if it exists by Tuesday.
Not embedded on the landing page: Safari's WebM support is patchy and I have no MP4 encoder here; the GIF already does that job.
Boss: "do it." Queued all six. Each Pinterest edit was checked afterwards with a read-back, because Buffer's edit replaces the whole post and the first response omitted the pin title and link — they were there on the read-back; the response is just abbreviated. Schedule (Central): pins at 2:53, 3:37, 4:21 and 5:06pm today, the maze pin 4:57pm Saturday; the Short 6:48pm today. The landscape upload to the channel is still the boss's, by hand.
Video is up — https://youtu.be/ph6q2ih6cBs, uploaded by the boss with the
title and description I supplied. Deployed 66be5318: linked under the demo
GIF on the landing page and from the guide's closing block (a link, not an
embed — the GIF already plays inline and an iframe would add YouTube's script
to a page I have kept at ~250 KB), plus a VideoObject in the JSON-LD so
search can attach it to the page. Launch file: the video is now gallery
item 1, the GIF item 3.
Phase 32 — the guide, read cold
Deployed as 352eef30. Rendered the page to plain text and read it start to
end as someone arriving from a search would, without the code open. The
numbers had already been checked against the code; this was about whether it
reads. It mostly does. Three things did not:
- Section 3 said "the generator draws a different subset" before any generator had been introduced — a reader making puzzles by hand was suddenly being told what software does. Now: the rule (each puzzle uses a different subset), then what a generator does about it, then what a hand-maker has to do.
- Section 4's "exactly 9" is not 'more than' 9"" was a sentence you had to read twice. Now: "exactly 9" is the limit, not over it."
- Section 5's "Below that, a longer book costs nothing extra" — "below" a page count meant "under 110 pages", but sat right after a sentence about cost, so it read as "below that cost". Reworded.
Also confirmed while reading: every claim in the "what earns one-star
reviews" list that the tool "guards by construction" is actually guarded —
the filler-letter blocklist exists (src/generator/blocklist.js), the
single-solution and no-duplicate checks were verified in earlier phases.
I would not want the guide promising something the code does not do.
Phase 33 — one landing page per puzzle type
Deployed as 3d79fb0d.
Three pages, one per search a buyer actually types:
/word-search-book-generator, /sudoku-book-generator,
/maze-book-generator. Each is written for that type's buyer — what goes
wrong in that kind of book and what the code does about it, that type's
grades with the real figures (directions per grade; 40/32/28/26 clues;
15×15 to 39×39), the sample PDF for that type, and a page-count/print-cost/
royalty table. The common KDP rules are one paragraph and a link to the
guide, not repeated three times.
Generated, not hand-written. npm run type-pages builds all three from
scripts/type-pages.mjs, importing the page planner, the cost table, the
difficulty tables and the theme list from the code that makes the books. The
32 themes are listed by name from wordlists.js; if a theme is added, the
page changes. Nothing on these pages can quote a number the product does not
use.
The tool now reads ?kind= — the button on each page opens the tool with
that type already selected and its default title in place, so the first
thing a sudoku buyer sees is a sudoku. Nine lines in main.js.
Two claims were softened before publishing because I could not back them: "the single most common complaint" and "the category with the most one-star reviews" became "one of the most common complaints" and "reviews are unforgiving". One was corrected: I had written that a book with a duplicate word list is "rejected and regenerated"; the code actually draws a list not used earlier in the book and warns if the theme cannot supply enough. The page now says that. A landing page that overstates what the product does is a refund waiting to happen.
Linked from the landing page (after the "why the puzzles hold up" cards), the
guide and every footer; in the sitemap at 0.9. New live suite
npm run test:typepages: each page serves, every internal link resolves,
the three cost rows match the code at test time, no horizontal overflow at
1280 or 390, and the button lands on the tool with the right type. First run
caught a race in my own test (reading the select before the module ran),
not a product bug; fixed the test. 21/21 after.
While waiting for the first pin (2:28–2:35pm). Re-pointed the sudoku
and maze pins at the new type pages instead of the landing page and the
guide — read back after each edit, titles and links intact. Then tried the
vertical Short as a Pinterest video pin: "Pinterest does not support .webm
files." Playwright's ffmpeg cannot write anything else, so I pulled a
static ffmpeg build (ffmpeg-static) into the scratchpad — not the repo —
and transcoded both videos to H.264 MP4 (public/video/*.mp4, 6.9 MB and
5.2 MB). Deployed, byte-verified live, and the video pin is queued on the
Puzzle Press board for Saturday 6:26pm. Buffer's first attempt timed out
at 30 s and created nothing (checked before retrying); the retry with the
MP4 went through. So the "no ffmpeg" gap is now closed for good, and both
videos exist in a format every platform takes.
Pinterest: the first pin failed, and why
2:54pm — pin #1 errored: "Pinterest is hitting some snags with the 'Source URL'". Parked the other five as drafts before they hit the same wall. Worked it through rather than guessing:
- Our edge is not the problem. Fetched the page as three Pinterest user-agents: 200 each. Cloudflare firewall events for the zone: none.
- Claimed the site. The boss found the claim page, sent me the
p:domain_verifytag, I deployed it (b6bf419c, checked as Pinterest's crawler), he verified. Pinterest's log shows "Pinterest Domain Verifier" fetching/→ 200 at 3:28. Retried pin #1: same error. - Isolated it. A pin with the same image but a YouTube destination posted fine at 3:32 (
sent). So: account fine, image hosting fine, the destination domain is blocked. - Zone request log (GraphQL, now that the token can read it): Pinterest fetched the pin image at 2:53 and nothing else before rejecting — the block is in their spam system, applied without a request. Textbook new- domain-new-account filter.
Asked the boss to file Pinterest's "blocked site" appeal. Pins stay drafted until it clears; I will retry one tomorrow because claims sometimes clear the block on their own. One pin is live: the cover image linking to the YouTube video, on the Puzzle Press board.
Also learned the hard way: Buffer edits that carry a video asset time out at 30 s and do not apply — verified by read-back both times. Image pins edit instantly. For video posts, create rather than edit.
Phase 34 — a page for every theme
Deployed as 783c5427.
/word-lists/ plus 32 pages, one per built-in theme — 1,660 words in all.
Each page: the full list (sorted, free to copy, stated as free to use in
books you sell), a real 15×15 puzzle from that theme as inline SVG so the
letters are text on the page rather than a picture, how many distinct
puzzles the list supports and what a 100-puzzle book comes to in pages, and
a button that opens the tool with that theme — only that theme — ticked and
titled. ?theme= joins ?kind= in main.js. Index page, sitemap (33
entries at 0.6–0.7), footer links on every page, a line on the word search
page. npm run word-lists regenerates them from wordlists.js.
Two things the live test caught before anyone saw them:
- The sample grids on two pages had a word appearing twice — ELK on animals, BIB on baby. BIB is a palindrome, forward and backward over the
same three cells, and that is counted as two by design; fixed the test's
expectation. ELK was real: my page script had handed all 76 animal words
to one 15×15 grid, 39 could not be placed, and the generator's last-
resort fill is unchecked. The tool never does this — it draws 15 words per
puzzle and warns the moment a word cannot be placed — so this was my
script bypassing the product's pipeline, not a product fault. Now the
page's puzzle is puzzle one of a one-theme book from
generateBook, the same path the tool takes. - Two lists have a capitalised word ("October", "November"); the page sorted them first and the comparison failed. Sort case-insensitively.
Also: I had written that the lists contain "nothing that hides inside another word". Checked — 14 of the 32 have a nested pair (DEER/REINDEER, CAKE/CUPCAKE, KANSAS/ARKANSAS…). The product handles that per puzzle, but the sentence was false, so it now names the pair on each page as a tip for anyone building grids by hand, which is more useful than the lie would have been.
npm run test:wordlists: 33 pages serve, every page's list equals the
product's list, every word under every sample grid is found in it exactly
once (checked with the generator's own occurrence finder), the deep link
ticks exactly one theme and sets the title, no overflow at 1200 or 390.
58/58 unit tests still.