Skip to content

Views

A view is a named screen in the app, identified by a URL route. Views are where components and requests get scoped, and where most authoring happens.

version: 1
views:
- name: FlightSearch
route: /search
description: Main search page with date and origin/destination pickers
source: src/pages/FlightSearch.tsx
components:
- name: DepartureDatePicker
selector: '[data-picker="departure"]'
requests:
- name: SearchFlights
route: /api/flights/search
method: POST

name and route are required. description and source are documentary — they aren’t surfaced at runtime but pay for themselves in PR review and future maintenance. See Schema reference for every field.

Routes use glob patterns against the URL pathname.

  • * matches exactly one path segment: /users/* matches /users/42, not /users/42/edit.
  • ** matches any depth of segments: /admin/** matches /admin, /admin/users, and /admin/users/42/edit.
  • Literal segments match themselves.
  • Matching is case-sensitive. Query strings and fragments are ignored. Trailing slashes are normalized away before matching.

For requests, Express-style :param segments are normalized to *, so /api/users/:id/orders and /api/users/*/orders are equivalent. See Requests.

When more than one view’s route matches the URL, the most specific route wins — not the first one listed. Definition order does not set priority; it only breaks ties between routes of equal specificity. You don’t have to hand-order routes specific-before-general.

Specificity is the sum of per-segment scores:

SegmentScore
Literal (e.g. users)3
:param (e.g. :id)2
* (single segment)1
** or empty0

The root route / scores 1 — more specific than any wildcard-only pattern. When two routes score equal, the first declared view wins.

views:
- name: UserProfile
route: /users/* # score 3+1 = 4 — listed first, still loses
- name: UserSettings
route: /users/me/settings # score 3+3+3 = 9 — wins for its path

/users/me/settings resolves to UserSettings (score 9) regardless of declaration order — flipping these two lines changes nothing. /users/* (score 4) still wins for paths like /users/42.

Components and requests can live at the file root or nested inside a view.

  • Global (file root): matched against every view.
  • View-scoped (inside a view): matched only when that view is active.
  • They are additive. A view defines its own components on top of any globals; both apply.
components:
- name: Navigation # global — matched on every view
selector: 'nav[data-component="Navigation"]'
views:
- name: Dashboard
route: /dashboard
components:
- name: DashboardLayout # scoped — only on /dashboard
selector: '[data-component="DashboardLayout"]'

source names the view’s primary file. A view often touches more code than that — a feature-specific hook, a Zustand store, a CSS module. Listing those in dependencies lets reverse-lookup tooling tie file edits back to the view without forcing every supplementary file into its own entry:

views:
- name: ListView
route: /lists/:id
source: src/views/ListView.tsx
dependencies:
- src/hooks/useChecklist.ts
- src/stores/listStore.ts
- src/styles/list-card.css

dependencies is purely curation-time — strings are minimatch globs, project-root-anchored, with !-prefix negation. The runtime never reads source files. See the schema reference for the full rules (what belongs, what doesn’t, normative diagnostics).

Components covers selector strings, arrays, and nesting rules. Requests covers method filters and route normalization.