Skip to main content
Omnibeds screens never build a URL or call fetch for an action. They call api() from lib/client/api.ts with a command’s name, and the compiler checks the input and the answer against that command.

Call a command

api() never throws. It returns { ok: true, data } or { ok: false, problem }, where problem is what the server explained: a code, a message, and any extra fields in details (for example the refreshed price check when an offer changed).

How it finds the URL

ROUTES maps every command to its method and path, for example cancel: POST /api/cancel and rebookBook: POST /api/bookings/:reference/rebook. api() fills :params from the input, then sends the rest as a query string for GET and DELETE, or as a JSON body otherwise. For a link the browser opens itself, such as a PDF download, apiUrl(name, input) returns the URL of a GET command.
Only types cross into the browser: lib/client/api.ts imports the command modules with import type, so no server code is bundled into a screen.

Keep logic out of screens

A page places its parts and holds no business rules. When a page grows, its state and its calls move into a hook, and its sections into components: Rules that aren’t about display live in lib/, where they can be tested without a browser: search filtering and merging in lib/search/, and the traveller helpers (a party from saved travellers, age checks on arrival) in lib/travellers/.

Calls that stay outside api()

A few calls aren’t commands and keep their own fetch: the search stream and its sentence parser, presenter unlock, the user switcher (session), the quota counter, the access gate, the guided tour and the client’s accept link on a shared quote.