# ZapiszPrzepis: how I use AI on real projects

An app built and shipped from scratch with AI — the 10xDevs 3.0 final project, passed first time with a Best Project distinction. What that process looks like.

**Canonical:** https://spoko.space/blog/zapiszprzepis-ai-first-app/  
**Language:** en  
**Published:** 2026-08-23  
**Tags:** AI, Next.js, React, TypeScript, Supabase, PWA, Cloudflare  
**Category:** AI

---
**AI has been part of my daily work for a long time — but saying so proves nothing, so here is a project instead.** I built [ZapiszPrzepis](https://zapiszprzepis.pl) from scratch and shipped it to production: the app runs, the code is public, and as the final assignment of the **10xDevs 3.0** programme it passed first time and earned a **Best Project** distinction.

It is still a test version: a few people close to me use it day to day, and registration is not open.

This post is about what that process actually looks like — because the process is what I bring to client work, not just to my own side projects.

## What the app does

Recipes circulate as posts, reels and videos. The reflex is to bookmark them or use the platform's own "Save" button. The trouble is that **all of those store a pointer, not the content**.

After a few months a good share of saved links stop working. The author deletes a reel, a cooking group vanishes, a blog goes dark, a post flips to private. A saved collection quietly turns into a list of 404s — and you find out at the moment you actually want to use it.

ZapiszPrzepis works the other way round — **archive-first**. Every link becomes a permanent copy: title, ingredients, steps and image land in the user's own database. The source URL is stored with them, so the original is one click away for as long as it lives. When it goes, you lose nothing but that button.

That copy is not a snapshot of the page. **An OpenAI model writes the text** — it keeps the ingredients and the steps, and strips the navigation, the ads and the long preamble you have to scroll past to reach the actual recipe. English sources get translated into Polish along the way.

I designed for a non-technical person — someone who does not type ingredients by hand, fill in forms, or manage folders and tags. That constraint turned out to be the most useful filter in the whole project: it immediately ruled out most of the "obvious" features.

## How it works

The entire entry path is one gesture. The app registers as a **Web Share Target**, so it shows up on the system share sheet next to Messenger and WhatsApp. The user sends it a link and does nothing else.

![ZapiszPrzepis home screen on a phone](../../assets/images/blog/zapiszprzepis-mobile.webp)

Everything after that happens in the background:

Extraction can take a dozen seconds and is occasionally flaky, so it does not run inside the request cycle. It emits an event to **Inngest**, which handles queueing and retries. The user gets an immediate response and the recipe appears shortly afterwards.

Web recipe search with voice input came later — archiving only solved half the problem. The other half is finding something you do not have yet.

## The stack, and why

<TableLookup
  headers={['Layer', 'Choice and reason']}
  rows={[
    { term: 'Framework', detail: '<strong>Next.js 15</strong> (App Router) — Server Actions, so forms need no separate API' },
    { term: 'Database and auth', detail: '<strong>Supabase</strong> — Postgres with RLS plus passwordless magic-link login' },
    { term: 'Background jobs', detail: '<strong>Inngest</strong> — retries and queueing without running my own infrastructure' },
    { term: 'Extraction', detail: '<strong>Firecrawl + OpenAI</strong> (gpt-4o-mini) — fetch the content, then pull structure out of it' },
    { term: 'Hosting', detail: '<strong>Cloudflare Workers</strong> (OpenNext) — the rest of the project already lived there' },
    { term: 'Client layer', detail: '<strong>PWA + Web Share Target</strong> — the only way to get a genuine one-gesture entry point' },
  ]}
/>

One decision determined the rest: **hosting on Cloudflare Workers means no Node.js APIs at runtime**. Every integration had to work over plain `fetch`. That ruled out several libraries before I wrote a line of code — which was a gift, because discovering it mid-deployment would have cost one of the few days available.

I break down the choice between Next.js and lighter generators in my [comparison of web application frameworks](https://spoko.space/blog/web-application-frameworks-2026/) — here the need for dynamic server-side state settled it.

## How I actually work with AI

This is the part that carries over to client projects one to one.

### Project context as long-term memory

The repository carries a directory with the requirements, the roadmap, per-change plans and a register of lessons learned. This is not documentation written for humans after the fact — it is **input for the model before every change**.

The difference is tangible. A model without context produces code that looks reasonable but ignores a decision made three days earlier. A model with the requirements and the plan in hand proposes solutions consistent with what already exists — and flags it when you ask for something that contradicts an earlier call.

For a client that has a mundane consequence: those documents stay. You are not buying a black box, you are buying a project where it is visible what was decided and why.

### A plan before code, every time

Every meaningful change starts with a document laying out phases, acceptance criteria and risks — not with an instruction to "add search". Only an approved plan goes to implementation.

It sounds like bureaucracy. In practice it is what shortens the whole thing, because it removes the most expensive category of mistake: building something that works and solves the wrong problem. The July search feature is the clearest example — it had a week of budget and took three days, because the plan was finished before the first line.

### Tests where the logic is sneaky

The project carries **over 70 unit tests** plus an end-to-end suite in Playwright. They are not spread evenly, and that is deliberate — they sit where silent mistakes are easiest: source-type detection, ingredient normalisation, judging whether fetched content is even a recipe, parsing video identifiers.

I cover how I wire AI into E2E tests in my [guide to E2E testing with Playwright](https://spoko.space/blog/e2e-tests-ai-playwright-guide/).

### Review as a gate, not a formality

Code written with a model's help is rarely wrong in obvious ways. It is wrong where something **works but rests on a false assumption**: a regular expression that looks watertight. A match that holds for today's inputs. A privilege granted just in case.

Those are exactly the things that sail past the compiler and the test suite. So review is not the last item on a checklist here — it is where I spend a disproportionate amount of time, and the more code a model writes, the better that trade gets.

## What your project gets out of it

Concretely, without the platitudes:

- **Requirements written down before the code.** You know what is being built and how we will know it is finished.
- **A faster route to a working version.** AI shortens the typing, which leaves more time for what actually decides the outcome.
- **Tests where things break quietly.** Not for a pretty coverage number, but so a regression does not reach production.
- **The code and the docs are yours.** Repository, decisions and change history — you can take them further, with me or without me.
- **One person accountable for the whole thing.** From architecture through deployment to support after launch.

## Summary

- **AI does not replace the thinking, it shortens the typing.** The difference is made where we settle what is being built at all.
- **Project context in the repository is infrastructure**, not documentation — and it stays with the client.
- **Review and tests matter more the more code a model writes**, because mistakes shift from "does not compile" toward "works, but on a false assumption".
- **Learn the platform's constraints before the first line**, not during deployment.

ZapiszPrzepis was built as part of [10xDevs 3.0](https://10xdevs.pl/), passed first time with a Best Project distinction — [verify the certificate](https://credsverse.com/credentials/d9f99ad8-ed75-4c81-a8ae-2a014d385725) — and the code is public on [GitHub](https://github.com/spokospace/zapiszprzepis).
