Skip to main content

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 dev works
  • 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

BeforeAfter
Feature does not exist; no backend endpointFeature built and approved against mocks, then promoted to a real app-lib feature
MOCK_API: false, screens hit the real backendMOCK_API flipped on during development, then off again against the new live endpoint
No handler, no DTO, no typed clientMSW 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.

tip

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:

  1. With MOCK_API: true and no backend running, the feature's page renders mocked data and the browser console shows no network error.

  2. After codegen, src/services/api/generated.ts contains a typed hook for the new endpoint whose response type matches the handler shape.

  3. With MOCK_API: false and the backend running on port 8000, the same page renders live data.

  4. The test suite still passes:

    cd web-client && npm test

    Expected: all test files pass.

Troubleshooting

ProblemLikely CauseFix
Page shows a network error in mock-UX modeNo handler for the endpoint, or the handler URL is absoluteAdd a handler in src/mocks/handlers.ts using a relative /api/v1/... URL
Mocks do not activate after setting MOCK_API: trueEditing config.js while a config.local.js overrides it on localhostSet MOCK_API: true in public/config.local.js, which loads after config.js on localhost
npm run codegen fails to fetch the schemaBackend not running on port 8000Start the backend (cd app-lib/src/app_lib/common && make dev) before running codegen
Typed hook shape does not match the mockDTO fields diverged from the handler JSONAlign 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