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 appHover 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.
--- 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
// 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. 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-16the spec requirement it covers -
dev_kit: ../layou-dev-kitthe 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
presentationnever importsdata.domainimports neitherdata, norpresentation, nor Firebase, nor Flutter UI.- Root
lib/= shell, router, startup, DI. Nothing else. - Every external SDK is wrapped before it reaches app code.
- Usecase required for writes, side effects, and business decisions.
- Implementation order: domain → data → app → presentation.
- UI: screen → connector → organism → molecule.
- Clean Architecture from day 1. Even for an MVP.
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