Methodology

AI agents write the code.
Our method makes it right.

Every feature starts with a story that settles the decisions, and the agents code against the same architecture on every app. Within that frame, about 90% of the code they produce is right the first time. Youcef catches the other 10% in review and puts the agent back on the right track.

Every example on this page comes from UpDrive, the app for ride-hailing drivers that over 1,000 drivers pay for every month.

Talk about my app
1 🧭 Product spec the language of the business decisions, settled 2 📋 ⓘ Stories 1 story = 1 feature out of scope, written failure case, written 3 🤖 Dev agent follows the layou-dev-kit domain → data → UI 4 🧪 Quality gate lint, tests, architecture translations and components reviewed by Youcef 5 🚀 Release web + TestFlight the app is deployed 6 🔍 ⓘ QA agent replays the story on the live app light and dark themes PASS → story done FAIL → new story The story the contract

Architecture first

  • the layou-dev-kit is re-read before coding
  • the same patterns on every screen

Gate red: it loops back

  • automatic fix, 3 rounds at most
  • past that, a human takes over

One pipeline, four targets

  • web, iOS, Android, macOS
  • the same commit for all of them

Hover a stage to isolate it, a dot • for the rule it carries. The ⓘ stages open the real file. Drag the diagram. Tap an ⓘ stage to open the real file.

The story

Decisions are made before the code.

A story describes a feature from the user's point of view, then settles everything that could be debated during development. If a decision changes along the way, we fix the story before the code. Six months later, it still explains why the code looks the way it does.

What UpDrive's "auto distance and duration" story settles

  • The technical choice, with its reason. Routes API (New), because the legacy JavaScript SDK is unusable from Dart.
  • What stays out of scope. No map, no new database field. That line stops anyone from rebuilding half the app on the way.
  • What happens when it fails. The API doesn't answer? The field stays empty and the driver types it in. No crash, no €0.00 shown without warning.
  • Numbered tasks. T0 to T7. A story that won't split is too big.
Read the full story

What the file header says

  • status: where the story sits in the loop
  • fr: FR-16 the spec requirement it covers
  • dev_kit: ../layou-dev-kit the architecture re-read before writing a line
  • depends_on: what must exist first, so two stories can move in parallel without getting in each other's way
---
title: "Story: auto distance and duration (Google Routes API)"
status: ready-for-review
created: 2026-06-17
fr: FR-16                  # the PRD requirement this story covers
epic: "E4: Calculator"
author: John (PM)
dev_kit: ../layou-dev-kit  # the architecture, re-read BEFORE coding
depends_on: story-address-autocomplete.md
---

## Story
As a **ride driver filling in a Transfer**,
I want **distance and duration to fill themselves in**,
so that **I stop typing them and my price matches the real trip.**

## Product decisions (settled with Youcef on 2026-06-17)
| Topic | Decision |
|---|---|
| API | Routes API (New), CORS-friendly from Flutter web |
| Editability | HELP, never a lock: the driver corrects, we never overwrite |
| Round trip | 2 calls (A→B + B→A) then sum. No naive ×2 |
| API failure | fill nothing. Empty field, manual entry, no crash |

## Out of scope
Cloud Function proxy · polyline/map · new Firestore field.

## Tasks
T0 verify the key with curl · T1 domain (entity + usecase)
T2 data (Routes API repo) · T3 calculator · T4 the 3 forms
T5 price recompute · T6 tests · T7 analyze

The architecture

The same architecture on every app, from the first version.

It lives in its own repository, the layou-dev-kit, and every story points to it. So a first version has no throwaway code: when the app grows, we build on what's there.

The eight rules no story can route around
  1. presentation never imports data.
  2. domain imports neither data, nor presentation, nor Firebase, nor Flutter UI.
  3. Root lib/ = shell, router, startup, DI. Nothing else.
  4. Every external SDK is wrapped before it reaches app code.
  5. Usecase required for writes, side effects, and business decisions.
  6. Implementation order: domain → data → app → presentation.
  7. UI: screen → connector → organism → molecule.
  8. Clean Architecture from day 1. Even for an MVP.
See the architecture in detail →

The 10%

About 90% of the agents' code is right the first time. The rest goes through Youcef.

The frame does most of the work: a precise story and an architecture the agent re-reads before writing. Whatever still drifts off course, Youcef catches in review, drawing on ten years of apps in production, and steers the agent back to the right way of doing it.

  • A red gate sends the agent back to fix it, three rounds at most. Past that, a human takes over.
  • The spec, the product trade-offs and the go-live call stay with us.

Acceptance

A story is done when it works on the live app.

Green tests aren't enough. The QA agent opens the deployed app in a browser and replays the story screen by screen, the way a user would.

  • On the production address, with no mock and no emulator.
  • Every screen in light theme and in dark theme.
  • A screenshot at every step, so the whole journey can be reviewed at a glance.
  • A failure becomes a story again, with its decisions, and goes back into the loop.
For technical teams: driving a Flutter app

A Flutter web app renders into a <canvas>: there is nothing to click in the DOM. So every interactive element carries a stable Semantics(identifier:), which makes the app drivable from the outside.

One test suite per epic (epic-1-auth, epic-4-calculator…) and a run-all that chains them. Whatever isn't built yet is written in the test plan, so no phantom bugs get reported.

The QA agent reads the same story as the dev agent: it only tests what was decided in writing.

// Flutter web renders into a <canvas>: there is no DOM to click.
// So every interactive element carries a stable
// Semantics(identifier:) → flt-semantics-identifier="nav_fab_create"
// THAT detail is what makes the app drivable from the outside.

const URL = process.env.UPDRIVE_URL || 'https://updrive-prod.web.app';

function bySemantics(page, identifier) {
  return page.locator(`[flt-semantics-identifier="${identifier}"]`).first();
}

async function fillField(page, identifier, value) {
  const node = bySemantics(page, identifier);
  await node.waitFor({ state: 'visible', timeout: 5000 });
  await node.click({ force: true });      // focus the RIGHT field
  await page.keyboard.press('End');
  for (let i = 0; i < 60; i++) await page.keyboard.press('Backspace');
  await page.keyboard.insertText(value);  // the only reliable path here
}

// The acceptance run then replays the story, screen by screen:
// sign-up → dashboard → calculator → invoice → listing.
// In light AND dark. A screenshot at every step.

Let's talk about your app.

30 minutes to decide what the app must unlock and what ships first. You leave with a price and a date.

Book the scoping call