← Writing

How I'm Learning to Code with AI (the Technical Version)

Let's start with the context and the constraints I imposed on myself. The whole path is five phases of increasing technical complexity, arranged so that each project introduces exactly one new dimension of difficulty:

  • Phase 1, fundamentals: pure frontend (HTML/CSS/JS + Vite, no framework), git, deploys, localStorage. Project: Focus Space.
  • Phase 2, backend + database: Next.js, Supabase (database + auth), sending email, scheduled jobs.
  • Phase 3, SaaS with AI and payments: integration with the Anthropic API, copywriting frameworks as business logic, Stripe.
  • Phase 4, real-time: Supabase Realtime / WebSockets, state sync across multiple users, conflict handling.
  • Phase 5, mobile: Swift/SwiftUI, push notifications, and eventually the Screen Time API on iOS.

A deliberate constraint in Phase 1: no backend, no framework. Vanilla JS on Vite. The point is to understand the foundation before the abstractions come in. Focus Space has no database at all. All persistent state is two fields in localStorage (the default mission and break durations). The "mission" itself lives purely in memory and is created from scratch on every launch.

Focus Space, the technically interesting parts

On paper it's a Pomodoro timer. In practice, a few things called for real decisions:

Distraction detection via the Page Visibility API. "Do Not Disturb" mode listens for visibilitychange. Losing tab visibility for more than 10 seconds (a grace period, so you're not punished for an accidental blip) fails the mission. It's the only "system-level" hook in all of Phase 1, and deliberately a simple one, because a full system-level blocker isn't until Phase 5 on iOS (FamilyControls / ManagedSettings / DeviceActivity). The web version is intentionally weaker, but it teaches the mechanism.

State as a state machine. A mission moves through a defined set of statuses: config → focus → focus-overtime → completed → break → break-overtime → failed. The key design decision: "break" and "mission goal" are not separate entities, they're fields on the Mission entity, because they make no sense without it. The timer doesn't stop at zero; it goes into overtime and counts up, with its own visual indicator. No hard limit in v1 (a conscious decision, logged as a risk).

The cooldown bar during breaks. During a break, the bar "cools the drive" at a rate that matches the break length exactly: a 5 minute break means full cooldown after 5 minutes. Manual ±1 minute adjustment refills the bar proportionally and applies only to the current break, with nothing written back to settings. Once it hits zero the clock goes into overtime and the bar resets to empty.

None of this is rocket science. But every one of those points is a place where "write me a Pomodoro timer" fans out into a dozen or so concrete decisions, and those decisions are exactly where the learning happens.

Workflow, the most important thing in this whole piece

I work with two AI tools: Claude Design for UI mockups and Claude Code for implementation. Between them sits a PRD (Product Requirements Document) that I write myself before every project.

The order is non-negotiable: PRD → screen designs → only then code. I break implementation down into numbered steps, each with its own "how do I verify this works" criterion, and I don't move on until the previous step meets that criterion. Prompts to Claude Code are written directively: scope limited to the current step, explicit "out of scope" boundaries, an instruction to "describe the planned changes before writing any code", and "stop and ask instead of guessing".

The trap that turned out to be the most interesting lesson

The biggest discovery of this project isn't about code, it's about keeping design and implementation in sync.

I noticed that if I don't paste the mockup from Claude Design directly into the prompt, Claude Code will invent its own design, and it will do so quietly, without a warning. The code will work, but its appearance won't come from any approved design. A drift you can't see until you walk into it.

That's exactly what happened with one screen (the failure screen, "Mission Failed"): it was built without a pasted mockup, so it works, but Claude Code came up with the look on its own.

The conclusion I've now baked into my PRD system: pasting the mockup into the prompt is a separate, deliberate move, not something that "follows from context". And further: there are two independent failure modes on two different axes:

A single status column won't catch both. So my screen inventory has two separate columns: "Design done?" and "Implemented?". The rule: if "Implemented?" is ahead of "Design done?", that's a signal of silent drift and it needs flagging. It's a small thing in a spreadsheet, but for me it's the single most important outcome of Phase 1: AI tools won't maintain consistency for you, you have to design a process that forces it.

Where I am now

Focus Space is halfway through Phase 1. Steps 1-6 are done (configuration, the hyperspace animation, the timer with overtime, the success and break screens, DND mode). Right now: deploying to Vercel plus a "lite" PWA setup (manifest, icons, display: standalone, no service worker, since full offline support is a separate, later step), so I can test DND on a real phone. After that: the settings screen, landscape/desktop responsiveness, and closing out the definition of done.

I'll be documenting the phases as they come. If you're interested in AI-driven learning to code, not the "type a prompt, get an app" version but one with real process discipline around it, stick around for the next posts.

Liked this one?
Get the next letter in your inbox.