How I shipped a mobile app over a weekend (mostly while I slept)
I'm not a mobile developer. Over one weekend, much of it while I was asleep, I shipped a real, installable React Native app by writing specs and letting agents build in a loop.
Before this weekend, I had never shipped a mobile app. By Sunday evening, there was an installable app on my phone and my friend's phone. Large chunks of the codebase were built while I slept. It was my first attempt at writing specifications and running agents in a loop. This is what worked, and what broke at 6 am.
The app: a deliberately terrible idea, built well
The occasion was terriblehack, a hackathon where the entire premise is to build something bad on purpose: useless, absurd, and ideally a little bit cursed. I went in with a small team.
So we built Carrier Snail: a reminder app where every reminder is delivered by an on-screen snail physically crawling toward you across a real map at about 0.03 mph, or roughly 48 metres an hour. You write "buy milk", assign it to a snail, and the snail sets off from somewhere a few kilometres away. It arrives, optimistically, sometime next week. You cannot speed it up, and there is exactly one notification: the ping when it finally reaches you.
It is catastrophically useless for anything time-sensitive, which is the entire point. A week-long delivery is a filter: the to-dos that still matter when the snail arrives were the only ones that were ever real. The collectible angle, a stable of snails each agonisingly slow in its own way, is lifted in spirit from Roost Socia.


The joke worked, but the app was also polished: real map journeys, a collectible economy, four playable minigames, a coherent pixel-art design system, and cloud sync. A couple of teammates owned whole pieces of that. We gave a terrible idea an embarrassingly serious build process.
I owned the specifications and the overnight build loop, so that's what this post covers.
The shift: write specs before code
I didn't need to know React Native well enough to write every line. I needed to be unambiguous about what to build and why, and to keep the project honest. The agent needed to know that expo-location requires a config plugin and that MapLibre won't run in Expo Go.
This is spec-driven development, and most of the scaffolding I used comes from the Claude Code skills Matt Pocock publishes in mattpocock/skills. The core idea is simple and slightly unfashionable: before you let an agent write code, you write the spec it builds against, and you make the agent help you write it.
Vibe-coding is "build me a reminder app" and praying. Spec-driven development is closer to running a very fast, very literal engineering team whose entire shared context is text you control.
The loop I settled into over the weekend looked like this:
- Constitution: write down what the product is and the rules it can never break.
- Grill: have the agent interrogate each feature idea until there's no ambiguity left.
- Decompose: turn the resolved spec into small, independently-testable issues an agent can grab.
- Loop: hand the issues to an autonomous agent and let it build, one green commit at a time. Overnight, if you like.
- Polish: run a design pass over what came out.
Step 1: A constitution before a single line of code
My first prompt, at 5:25 pm on Friday, created a constitution: three files in a specs/ directory called mission.md, tech-stack.md, and roadmap.md.
mission.md did the most work all weekend. It records the project's laws, including one inviolable rule:
> ### The Delivery Floor
> No carried to-do, moved by any snail, at any level, for any amount of money, can
> ever arrive sooner than a hard floor:
> - At least 24 hours, and
> - never under ~40% of its honest distance-time.
>
> Money and grind can buy a nicer, slightly-less-glacial menagerie. They can
> never buy urgency. The Floor is enforced server-side; no client can cheat the clock.
The agent rereads the constitution at the start of every iteration. When an autonomous loop is grinding through twenty issues unsupervised at 3 am, that file stops the agent from "helpfully" adding a speed-up button because it makes the reminder app more useful. The mission file lists that button under non-goals, so the agent never built it.
The constitution keeps the project pointed in one direction when you're not in the room.
Write down the things that are obvious to you and invisible to the agent: the soul of the thing, the lines you'd never cross, and the reason it exists. Agents can solve how while missing why. Put the why in the constitution.
Step 2: Grill the idea until it stops being vague
A spec is only as good as the thinking behind it, and my first draft of any feature was always too vague to build. So before each feature I ran /grill-me, a skill that turns the agent into a relentless interviewer and asks one question at a time until the ambiguity is gone.
I did this nine times over the weekend, and those sessions saved the most rework. A typical one started like this, in prompt #27:
Okay, I would like for our next big feature to be a classic navbar at the bottom edge of the screen. Have 4 nav icons: "My Snails", "Map", "To Dos", "Notifications"…
/grill-metill we have little to no ambiguity with this feature addition.
The agent responds with questions. Where does the current view live in the new structure? Is a "reminder" the same object as a "journey", or does splitting them change the data model? What happens to the 59 KB monolith this assumes? Each answer I gave went into a resolved spec, which the agent then treated as authoritative. They all carry the same header:
> Resolved from a `/grill-me` session. Authoritative for this batch.
> Honors the constitution (specs/mission.md; Delivery Floor inviolable).
The grilling is where I added value. I wasn't writing the navbar. I was making decisions only the product owner can make, and the questions dragged every one of them out of my head and onto disk before a line got written. Correcting a bullet point is free. Correcting half-built code is not.
Step 3: Turn the spec into agent-ready issues
A resolved spec is still too big to hand an agent whole. The next move, borrowed wholesale from the Pocock flow, is to decompose it into small, independently-testable GitHub issues. Each is labelled ready-for-agent and lands on a long-lived integration branch (build/v1) rather than main.
The "give the snails real characters" batch became eight issues in a fixed order:
1. Species catalog + model migration (foundation)
2. SnailSprite + sprite generation + display wiring
3. Select-a-snail picker for to-dos
4. Custom snail names (rename)
5. My Snails detail page
6. Egg-hatching reveal
7. Trail visibility overhaul
8. Selectable map skins
Loop order: 1 → 2 → (3,4,5,6) → (7,8). Each = one branch + PR into build/v1, merged on green.
Two rules made this safe to run unattended:
The green gate. Nothing merges unless it's green. Every issue ends with four commands passing, or it doesn't land:
npm run typecheck
npm run lint
npm test
npm run build
State what the gate can't check. The specs explicitly carve out what the gate cannot see. From the snail-batch spec:
The green gate cannot see visuals. Build + green-gate the plumbing (model, use-cases + tests, components, wiring). Generate art where needed and flag all visual output for human on-device sign-off — never self-certify visuals on the green gate.
That instruction stops the loop from claiming a sprite looks right. It can prove the tests pass. It cannot prove the snail looks like a snail. That stays my job.
Step 4: The overnight loop
By Friday night I had a constitution, a roadmap, and a backlog of ready-for-agent issues. I had a working app skeleton. And I had eight hours where I intended to be asleep.
In prompt #9, I asked how to set up an extremely long-running /goal in a separate Codex session that would keep building. I called it "loop engineering".
The answer became a file: docs/agents/autonomous-loop.md. It's an operating manual for a restartable agent that advances the app one issue at a time and never leaves the repo broken. The logic lives in the file; the prompt just points at it. Nine steps, run in a loop:
0 BOOTSTRAP · ensure branch + backlog exist
1 SYNC · git fetch, re-read the constitution + specs
2 SELECT · pick the next ready issue (lowest phase, smallest scope)
3 DECOMPOSE · if too big to finish green in one pass, split into sub-issues
4 BRANCH · issue/<n>-<slug> off the integration branch
5 IMPLEMENT · tests first, at the confirmed seam, then build to green
6 VERIFY · the green gate. red after 3 tries → escalate, don't spin
7 INTEGRATE · small commit, PR, merge on green — never to main
8 RECORD · comment on the issue, close it
9 REPEAT · from SYNC
The kickoff prompt I pasted into a fresh Codex session and walked away from:
You are the autonomous build agent for Carrier Snail.
GOAL: Ship Carrier Snail v1 — implement every `ready-for-agent` issue across
roadmap phases 0–8, in order.
Operate EXACTLY by the loop in docs/agents/autonomous-loop.md. Before every
iteration, re-read AGENTS.md, the loop, and the relevant specs/. The
constitution is law; the Delivery Floor in specs/mission.md is inviolable.
- All durable state lives in GitHub issues + git. You may be restarted at any
time — reconstruct context from there and resume.
- Work ONE ready-for-agent issue at a time, lowest roadmap phase first. TDD at
the seam fixed in specs/prd.md.
- Leave the repo green after every issue. One branch + PR per issue, merged to
build/v1 only on green; NEVER merge to main.
- If an issue is too big to finish green in one pass, decompose it into child
issues and finish those first.
- Stop and escalate when blocked or when no ready issues remain. Never spin;
never violate the constitution.
Begin: sync, ensure build/v1 and a backlog exist, then SELECT and implement the
next issue. Keep going until a stop condition is met.
This works while you're unconscious because state lives in GitHub, not in the agent's head, so the agent can restart from the open issues, and the integration branch stays green, so the worst case is a stalled issue rather than a broken repo.
What I actually woke up to
The results were genuinely mixed.
Real features had landed. The species catalogue, rename flow, detail page, and egg-hatching reveal were built, tested, merged, and green, with issue comments explaining what was done. I scrolled the morning's commits like I was reading someone else's stand-up. Seeing work that would have taken me days in an unfamiliar stack running before coffee felt like magic.
I also woke up to a white screen on my phone and a stack trace. Supabase anonymous sign-ins are disabled. A missing Java runtime. SDK location not found. Later came game lag, and a stretch where the app cheerfully forgot all my data between launches because cloud sync had quietly fallen back to local-only. The loop could build features that passed the green gate and still hand me something that didn't boot on a real device because, exactly as the spec admitted, the gate can't see the screen.
My mornings started with figuring out why the thing the robot had proved correct was showing me a blank white rectangle. A lot of my weekend prompts are unglamorous because of this:
God damnit. I keep running
npx expo run:androidbut getting a white screen followed by an error on my phone.
The overnight loop front-loaded the building, leaving my waking hours for the things that needed a human holding a physical phone: integration, device quirks, and taste.
Step 5: Design without a designer, using /impeccable
I am not a designer either. Left alone, agents produce UIs that look like a settings menu — technically correct, joyless, grey.
I used /impeccable, a design-critique skill separate from the Pocock kit, three times. It critiques visual hierarchy, spacing, type, and token discipline. On this project it also produced a machine-readable design system snapshot (.impeccable/design.json) containing the colour ramps, two-font rule, chunky bevels, and dos and don'ts for later screens.
The snapshot became a "design quality bar" in the feature specs, so the autonomous loop built new screens against the same standard. I ran it on the My Snails page, the shop, and the map's collapsed detail sheet. Each pass reinforced the same calm pixel-art language.


Other skills and tools that earned their place over the weekend:
/to-prdand/ask-matt: the rest of the Pocock spec-driven kit, for turning a rough idea into a product doc and sanity-checking the approach.image-2(via Codex): generated all the pixel-art snail sprites and the app's illustrations, deterministically, straight intoassets/.- A custom Expo config plugin I had to hand-write (well, have written) to swallow a React Native 0.85 crash on Android backgrounding — a reminder that the bleeding edge still draws blood, agent or no agent.
The weekend in numbers
I logged every prompt I sent across Claude Code and Codex into a CSV:
- 161 prompts, from Friday 5:25 pm to Monday 8:55 am: about 63 hours of wall-clock time, a healthy fraction of it spent asleep.
- 131 prompts in Claude Code, 30 in Codex. Claude did the thinking: grilling, specs, decomposition, and planning. Codex did the building, including the overnight runs. A handful of Codex kick-offs each unfolded into hours of autonomous work.
- 9
/grill-mesessions, 3/impeccablepasses. - ~18,500 characters typed in total. That's the entire human input that produced the app, or about 3,000 words of English. A lot of those words were not clever:
proceed
merge
go
git add, commit, push
promote build/v1 to main
do the phase 6 design polish pass
Once the spec was good and the loop was running, my job collapsed into a small vocabulary: grill, decompose, kick off the loop, verify on a real device, merge. The 3,000 words that mattered were the specs. Most of the other commands only kept the loop moving.
The honest calibration
I am not a no-code success story. I'm an experienced engineer who was new to this stack. I could read a stack trace, reason about why cloud sync was silently failing, smell when an agent's plan was wrong, and know what "green" should actually mean. The workflow didn't replace fifteen years of knowing how software fails. It let me point that judgment at an unfamiliar domain instead of spending the weekend learning the Expo build system by hand.
The realistic version of "anyone can ship a mobile app now" is this: if you can think clearly about what you want and recognise when something's wrong, the specific framework matters far less than it used to. Judgement still mattered; prior React Native knowledge mattered much less. Writing the spec and running the loop became the skills worth practising.
What actually shipped
By Monday, I had:
- A real, installable Android APK running on my phone and on a friend's, which I installed by plugging their phone in and pushing the same build.
- A public GitHub repo with a real README, a design system doc, and a wall of screenshots.
- An actual terriblehack submission, a deliberately terrible product built with a process I'd happily defend in a code review.


It didn't place. A terrible app losing a terrible hackathon is almost on-brand. I kept the workflow.