
How Tessera was built
Paul Cooper, October 2026
Tessera started as a sketch: a grid of letters where you can see everything from the start, and the only question is how cleanly you put it back together. Five and a half months later it's a web game, an iPhone app and an Android app, with a backend I can retune without a store release and a marketing pipeline that renders its videos from code. This is how that happened, including the parts I got wrong.
I should say up front: I'm a product designer who codes, not a studio. I didn't build this the way one person would have built it five years ago. Most of the code was written with Claude Code, an AI coding tool, from the very first commit. More on what that actually means at the end.
638
commits since April
~75k
lines of TypeScript and Dart
130
test files, web and app
4
CI jobs on every change
28 Apr
First commit, and the first playable puzzle, on the same day
May
Share grids, email reminders, dark mode, Spanish, push, Hard 5x5, a daily social bot
Jun
Accounts, sync across devices, leaderboards and leagues
19 Sep
The Flutter app lands, with a versioned API and crash reporting
21 Sep
Tessera Chain, coins and the theme store
23 Sep
First TestFlight build
2 Oct
1.0 live on the App Store
Oct
1.0.1 on iPhone and Android, and the website points at the apps
Day one: the web
The first commit and the first playable puzzle went up on the same day, 28 April. Next.js on Vercel, a word list, and a generator that builds a 4x4 grid where every row and every column is a word, then scrambles it. The scramble is seeded by the date, so everyone gets the same puzzle.

May was a sprint of small, shippable things.
A share grid and an image for every solve. Email reminders. Dark mode. Spanish, with its own word list. Push. A 5x5 Hard mode, and a solver that works out the fewest swaps for every board, so a result can be graded fairly.
In June came accounts, sync across devices, leaderboards and leagues. That was the point it stopped being a toy: people's streaks now lived on a server, and losing one would be a real failure.
Writing the app down before building it
The native app started as a document, not code. The Flutter spec has a decision log, two rounds of review, and acceptance tests for each phase written before the phase was built. The levels-and-coins spec came with an economy simulator, so the numbers for coins, stars and prices were modelled over 90 days of play before a single coin existed.
Writing it down caught my worst early idea. The first plan generated puzzles on the phone, which meant shipping the generator to every device, and keeping two copies of it in step for ever. The spec moved generation to the server instead: one engine, one source of truth, and nothing on a device that can work out tomorrow's puzzle. The web and the app now share a set of fixture files built from the live web logic, and CI fails if the app's copy is out of date.
The stack
Nothing exotic. The interesting decisions are about where things live, not which framework.
Building it to fix itself
A solo developer can't be on call, so the system has to cope on its own, and most of the work in September went into that.
- Numbers live on the server. Coin prices, star lines, move caps and difficulty curves are served to the app at launch, so I can retune the game in minutes without a store review. A malformed setting is ignored rather than served.
- Feature flags fail safe.Every flag defaults to what shipped, so if the config can't be reached the app behaves exactly as it was tested. There's also a forced-update gate that is deliberately never behind a flag.
- Nothing is lost offline.Today's puzzle is cached on the device. A result that can't be sent is queued and retried on the next launch, and syncing an account is safe to repeat.
- Money is counted once.Every coin goes through a ledger where a retried request, a repeated ad callback or a re-sent store notification all credit once. Purchases are confirmed by the server before anything is credited. Those tests were written first, because a double payout would corrupt real players' data.
- Crashes report themselves, with debug symbols, and analytics are cookieless on the web and opt-in in the app.
Underneath it all, every change runs through CI: types, tests and a build for the web; analysis, tests and an Android build for the app; pixel-for-pixel golden images of the board in light, dark and colour-blind modes; and an iOS build when the native config changes.
The parts I got wrong
Plenty. A star gate between level sets was built, measured, and switched off, because, as the spec now says, gates produce replays, not spending. A fading tile hint looked like a bug on the first level it faded, so it went: a signal that's only sometimes true teaches players to stop reading it. Past level 100, every level was as hard as level 91, for ever, and players doing 10 to 20 levels a day reached that flat line in their first week; the curve now laps. A 5x5 replay took 38 seconds to load in production; it now takes about one.
App Review taught me the rest. A TestFlight build was rejected for not including a demo account. Version 1.0 had to be withdrawn and resubmitted because its in-app purchases weren't attached. There's a commit in the history called “Write down what today cost, so the next release does not pay it again”, and the release checklist grew out of it. 1.0 went live on 2 October.
Marketing as code
The same habits went into getting the word out. A bot posts the daily puzzle to X, Bluesky, Facebook and Instagram every morning, with a backup schedule and a lock so it never posts twice. Store screenshots are rendered by a script from raw simulator captures, at every size each store asks for.


Even the ads are built this way. The gameplay is recorded on the simulator, then each frame of the ad is rendered in a headless browser: the board, the headlines, and a white glove that taps each tile at the moment it was tapped in the recording. Changing a headline means changing a line of text and pressing render.
On the tools
Here's the honest version. Of the commits on the main branch, about four in five were co-written with Claude. It wrote most of the lines. What stayed with me was the part I'd always done as a designer: deciding what Tessera should be, writing it down precisely, reviewing everything, and saying no, often.
It changed what one person can take on. Specs, tests, CI, crash reporting, store screenshots, video: each of those used to be a reason a solo project stayed small. It didn't remove the judgement, and it made its own mistakes, as I made mine. Dropping one ad format, for example, quietly left the consent flow with nothing calling it for a day, until it was caught. The process caught it, because the process was written down.
I'm grateful for the tools, and for the people who played early versions and told me what was broken. Tessera is better for both.
What's next
1.0.1 is the first version on both stores. The plan from here is the same as it's been since April: ship small, measure what players actually do, and change one thing at a time. If you'd like to follow along, come and say hello on r/TesseraPuzzle, or try today's puzzle on iPhone or Android.
Paul