Skip to content

WI-017: Mobile search against the API

WI-017: Mobile search against the API

Problem

The scaffold rendered five hard-coded games because it had nowhere to ask. WI-012 gave it somewhere.

Its spec said the sample data should be deleted the moment that happened. This deletes it.

In scope

  • src/api.ts — a typed client that validates responses against the shared contract.
  • src/api-host.ts — device-side host resolution, kept separate for a reason below.
  • The list screen becomes a live search: debounced, cancellable, with loading, empty and error states.
  • The detail screen loads a real game.
  • src/sample-games.ts deleted.

Out of scope

  • Anything requiring a signed-in user — WI-013.
  • Caching, offline, retry-with-backoff. This talks to a dev server on a laptop; inventing a sync layer before there is an account is building for an imagined product.

Two things worth writing down

The API client imports nothing from Expo or React Native. expo-constants pulls in React Native’s Flow-typed entry point, and anything importing that becomes unparseable by vitest — the test suite failed with “Flow is not supported” pointing at react-native/index.js. Host detection moved to api-host.ts, which the root layout calls once; the client is told the answer. The client is now pure and testable.

Responses are validated, not trusted. The API and this app ship together today. They will not always: a phone keeps whatever build its owner last installed, so “client and server always agree” stops being true the first time somebody declines an update. A response that does not match the contract throws with a readable message rather than rendering undefined into a list.

Definition of done

  • Searching by title, designer, mechanic and theme returns real games.
  • A typo still finds the game — the trigram path reaches the screen.
  • Requests are debounced, and an in-flight request is aborted when the query changes, so a slow response for “cat” cannot overwrite a newer one for “catan”.
  • A blank query does not call the API at all — it would be a 400, and that is not the user’s mistake.
  • An unreachable API produces a readable message, not “Network request failed”.
  • A response that violates the contract throws rather than rendering undefined.
  • A slug is validated with slugSchema from packages/shared before a request is made.
  • src/sample-games.ts no longer exists.
  • The client is proven against the live Worker, not only against a stub.
  • Gate green.

Verification

Terminal window
pnpm --filter @tabletop/db run db:up && pnpm --filter @tabletop/db run seed
pnpm --filter @tabletop/api dev # terminal one
pnpm --filter @tabletop/mobile dev # terminal two, then press w

On a physical device, api-host.ts derives the laptop’s LAN address from Expo’s own hostUri, because localhost on a phone is the phone. EXPO_PUBLIC_API_URL overrides it.