UX-First Acceleration
Overview
This guide walks through building a feature UX-first: design and refine the screens against mocked API responses with no backend, then scaffold the real backend from those mocks and switch to live data. By the end, a new feature exists end-to-end — built and approved as a mock, then promoted to a real app-lib feature with a typed client and mocking turned off.
When to Use This Guide
Use this guide when:
- A new feature needs UX iteration before its backend exists, or in parallel with backend work
- A stakeholder demo is needed quickly and waiting for real services would block it
- An in-progress feature must be developed in the open but kept hidden ("dark") until ready
- The mocked screens are approved and the feature should be promoted to a real backend
Do not use this guide for changes to an already-live feature whose backend exists — edit the feature directly.
Before You Start
Before starting, confirm the following:
- The web-client runs locally:
cd web-client && npm install && npm run devworks - Node.js 22+ and npm are installed
- Python 3.12 and uv are installed (needed only for Stage 3, when the backend is scaffolded)
- Familiarity with the UX-First Mocking reference and Feature Flags
Before / Target State
| Before | After |
|---|---|
| Feature does not exist; no backend endpoint | Feature built and approved against mocks, then promoted to a real app-lib feature |
MOCK_API: false, screens hit the real backend | MOCK_API flipped on during development, then off again against the new live endpoint |
| No handler, no DTO, no typed client | MSW handler authored as the contract; matching Pydantic DTO; typed client regenerated by codegen |
Steps
1. Enter mock-UX mode
To run the web-client against mocks with no backend, set the data-source switch in the runtime config and start the dev server. In web-client/public/config.js (or the gitignored public/config.local.js):
window.__CONFIG__ = {
// ...
MOCK_API: true,
features: {
// ...
trips: true, // reveal the in-progress feature while building
},
};
In the web-client/ directory, run:
npm run dev
The MSW browser worker registers and intercepts every /api/v1/* request. The Settings page (/settings) shows MOCK_API: true, confirming the mode is active.
2. Build the page and author its handler together
Build the page under src/pages/, wire its route in src/routes.ts, and gate its sidebar item behind the feature flag (see Feature Flags). In the same change, add the matching handler to src/mocks/handlers.ts using a relative URL and a realistic, DTO-shaped response:
// src/mocks/handlers.ts
http.get('/api/v1/trips', () => {
return HttpResponse.json([
{ id: 't1', destination: 'Lisbon', date: '2026-07-01', status: 'BOOKED' },
])
}),
Iterate on UX — empty states, loading, errors, pagination — by editing the mock response and watching the UI react. The handler is quietly becoming the API contract.
To ship the feature dark, set its flag to false. The code stays in the tree but the nav item disappears. MOCK_API is independent of feature flags — leave it as you need it.
3. Scaffold the real backend from the handler
When the UX is approved, turn the handler into a real feature. Copy and adapt app-lib/src/app_lib/features/passengers/ into a new feature directory, defining a Pydantic DTO in routes/{name}_dto.py whose fields mirror the handler JSON exactly. The full recipe is in app-lib/src/app_lib/features/CLAUDE.md. Register the router with two lines in common/app.py, then run the backend:
cd app-lib/src/app_lib/common && make dev
The backend serves on localhost:8000.
4. Regenerate the typed client
To re-type the RTK Query client against the real endpoint, run codegen from the web-client/ directory while the backend is running:
cd web-client && npm run codegen
This downloads the backend's openapi.json and regenerates src/services/api/generated.ts with typed hooks matching the previously-mocked shape. No client code is written by hand. See RTK / Codegen.
5. Switch off mocking
To leave mock-UX mode and hit the real backend, set MOCK_API: false in config.js and reload:
window.__CONFIG__ = {
// ...
MOCK_API: false,
// ...
};
The worker does not register; requests fall through the Vite proxy to the backend on port 8000. The same screens, untouched, now render live data.
Verification
To confirm the loop is closed, perform these checks:
-
With
MOCK_API: trueand no backend running, the feature's page renders mocked data and the browser console shows no network error. -
After codegen,
src/services/api/generated.tscontains a typed hook for the new endpoint whose response type matches the handler shape. -
With
MOCK_API: falseand the backend running on port 8000, the same page renders live data. -
The test suite still passes:
cd web-client && npm testExpected: all test files pass.
Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
| Page shows a network error in mock-UX mode | No handler for the endpoint, or the handler URL is absolute | Add a handler in src/mocks/handlers.ts using a relative /api/v1/... URL |
Mocks do not activate after setting MOCK_API: true | Editing config.js while a config.local.js overrides it on localhost | Set MOCK_API: true in public/config.local.js, which loads after config.js on localhost |
npm run codegen fails to fetch the schema | Backend not running on port 8000 | Start the backend (cd app-lib/src/app_lib/common && make dev) before running codegen |
| Typed hook shape does not match the mock | DTO fields diverged from the handler JSON | Align routes/{name}_dto.py field names, types, and nullability with the handler response |
Next Steps
- Mocking reference — see UX-First Mocking for the mechanism, mode detection, and the production-safety guards
- Feature visibility — see Feature Flags to manage which features are visible per environment
- Codegen details — see RTK / Codegen for the OpenAPI-to-client pipeline
- Local development — see Local Development to run the full stack against deployed AWS infrastructure