STORYPRINTS — A story you print and colour in

Video 1/2 — STORYPRINTS — A story you print and colour in
Video 2/2 — STORYPRINTS — A story you print and colour in

Eight answers from a parent turned into a physical object in two minutes · SaaS · B2C · EdTech

Client
Side project — RBT Studio’s own product
Role
Product, design and full-stack development (a team of one)
Timeline
March 2026 – ongoing
Stack
Next.js 15, React 18, TypeScript, Supabase, Claude, OpenRouter, Upstash Redis + BullMQ, pdfkit, Lemon Squeezy

The gap sat between the made-to-order book and the chat window

A shop-bought colouring book is generic by definition: the hero is never your daughter. The personalised alternatives are print-on-demand books — expensive, with weeks of waiting and a single print run. At the other end, an AI chat can write you a story, but it returns text in a window: it is not a book, it has no cover, it doesn’t print, it can’t be coloured in, it doesn’t stay on the shelf.

The problem: Three constraints that shaped the technical design

The final artefact is an A4 PDF, not a screen: everything generated has to survive a home printer. The audience is children aged 3 to 12, so content nobody has vetted cannot reach a printed page. And every book costs real money in model calls — the product’s economics had to be in the code from day one, not bolted on afterwards.

How it was approached

  1. The wizard: eight questions, not a prompt. The first product decision was not to expose a free-text field. A parent doesn’t want to write a prompt; they want to answer questions with their daughter next to them. Name, age, setting, companion, value, identity, length and narrative style — each answer is a card. That validated StoryConfig is the only contract that enters the pipeline: nothing downstream asks the browser anything again.
  2. A queue, and a cron that rescues it. Generations take minutes and cost money, so they go into a queue (BullMQ on Upstash Redis) with progress streamed to the browser. It is drained two ways: in-process right after responding, and by a cron every 5 minutes. The cron isn’t decorative redundancy — it is the only thing that lets a stuck queue fix itself. The endpoint fails closed if there is no CRON_SECRET, because every call spends money on models.
  3. The illustrations cannot contain text. These are colouring pages: a letter baked into the bitmap can’t be removed, and the page prints as is. The image model is called via chat completion, with no negative prompt, so everything travels inside the text — the instruction is repeated in three different ways and quoted dialogue is stripped from the scene before the model sees it, because quotation marks are the strongest signal for a model to draw letters. It is prompt-level mitigation, not a guarantee, and the documentation says so.
  4. The economics live in the code. Every image is paid for in credits, and the plan buys a monthly allowance that expires when the period closes: an allowance that accumulates is not an allowance. For a while the subscription webhook wrote the plan and nothing else, so a subscriber had zero credits and got the same generic drawings as the free plan. Today grantPlanCredits() runs on creation, on update and on every monthly payment — idempotent by description, with a unique index in Postgres that actually enforces it.
  5. Two deliberate business decisions. The free plan delivers a complete, printable book: holding back the PDF would leave the free plan with nothing to judge the product by — what the subscription removes is the watermark. And every user’s first story gets real AI illustration, whatever their plan; otherwise the only book a visitor judges the product by is rendered with six generic SVGs, that is, with none of the personalisation they are being asked to pay for. A sign-up costs one image call: it is cheap advertising.

wizard → PDF: Wizard · 8 steps → Validated StoryConfig → BullMQ queue · Redis → Narrative Engine · Claude → Content Safety → Illustration Engine · OpenRouter → Supabase Storage → SVG reader → A4 PDF on demand

Solution: A complete SaaS, from landing page to PDF, built and run by one person

An eight-step wizard with an optional photo upload of the child, with explicit consent and never written to the story’s config column. A narrative engine on Claude with age-appropriate scenes and lengths from 6 to 18 pages derived from the number of scenes. An illustration engine on OpenRouter, black line on white and consistent across scenes. A content filter applied both to what the model returns and to the product’s only free-text field. An SVG web reader and PDF export with pdfkit. Four plans read from a single limits table, a custom share card, an admin panel and subscriptions with Lemon Squeezy. The PDF is not in the pipeline: it is composed on demand from the saved story, so a generation never blocks waiting on layout.

Results

The product is built and deployed, and not yet validated in the market. There are no user, conversion or revenue metrics to report, and I’d rather not make them up. What can be stated: the full loop works in production, from the eight wizard answers to the downloadable PDF, with live payments and subscriptions. The business invariants are locked by tests, not by discipline — the limits table and the pricing page cannot diverge because there is a test that fails if they do. And the queue self-heals: a generation whose in-process trigger is interrupted is picked up by the cron within the next 5 minutes. What remains to be validated is what matters: whether a parent will pay €6.99 a month for this.

  • 376 — tests across 30 suites, all green
  • 113 — commits in five months of one person’s work
  • 4 — plans read from a single limits table

"A flag no route reads is not a limit: it is a comment that looks like one."

What was learned

  1. A flag no route reads is not a limit. The plans table existed long before the routes queried it. The credits bug — a subscriber getting the same drawings as the free plan — was not a code error: it was configuration nobody executed. Now every limit has a route that reads it and a test that proves it.
  2. What can’t be removed afterwards has to be prevented beforehand. A letter baked into a bitmap that will be printed has no later fix, so the mitigation has to live in the prompt and in the scene preprocessing — and be documented as mitigation, not as a guarantee.
  3. Taking the PDF out of the pipeline is what made the system robust. Composing on demand from the saved story means a layout failure never blocks a generation that has already been paid for.

See StoryPrints in production

Product in production, pending market validation.

More cases

  • VAMPMAKER — A SaaS that prepares the role-playing session before the session starts
  • BRANDAI — Brand identity that is portable across AI tools
  • RBT.STUDIO — When the product is you
  • AIMPLAS — A 3D tablet app that accompanies a real helmet and scooter
  • D-GO — A website for D-Go, a direct-drive motor for cargo bikes
  • THE SMART LOLLIPOP — A landing page for The Smart Lollipop, a children’s health device
  • EONIA — The missing layer of biological decision between measuring and intervening
  • EL ÚLTIMO ZARPE — A roguelike without meta-progression, where only understanding accumulates
  • PHOTOGRAPHY · ART DIRECTION — A visual grammar, not a style — from the medium-format negative to the printed book
  • NOTJUSTCODE — Vision, design and market reviewed inside the editor, against the repository
  • SDD HARNESS — A 5-phase wizard so agents build what I had in my head

The fourth ink

Tell me about the project. I reply within 24 hours, from Barcelona.

contact@rbt-studio.com