Skip to content
Browse docs

Screens and states

Describe each step once, then give every meaningful condition a reachable frame.

On this page

A screen is a step with evidence#

A screen describes one product surface: its stable identity, route pattern, design purpose, source files, shared components, relevant API routes, and states. Prefer a page.mockspace.mjs beside the page it describes so the metadata moves and is deleted with the page.

export const meta = {
  id: "inventory",
  name: "Inventory",
  route: "/trips/[id]/inventory",
  purpose: "Compare available options before choosing one.",
  sources: ["src/app/trips/[id]/inventory/page.tsx"],
  components: ["ResultCard"],
  api: ["GET /api/trips/:id/inventory"],
};

export const states = [
  { id: "default", url: "/trips/trip-searched/inventory" },
  {
    id: "no-results",
    url: "/trips/trip-searched-none/inventory",
    purpose: "A completed empty search must not look like a failure.",
  },
];

The default state supplies the screen URL and always appears first. Source paths are relative to the application repository. mockspace check fails when a declared source path does not exist.

A state is a condition worth comparing#

States stack vertically beneath their step. Derive them from behavior in the actual page: lifecycle enums, independent statuses, decision records, error branches, optional fields, and list sizes that change the layout.

A useful state has a reason to exist. “No results” checks the difference between an empty success and a failed search. “Dialog open” checks an overlay against the screen behind it. A second label for the same pixels is not coverage.

A frame is reachable behavior#

Every frame ultimately has a URL in the application. Prefer a real identifier in the path or another routing mechanism the app already uses. Mockspace opens that URL; it does not understand how the app selects its data.

Some UI state is not URL-addressable. For a dialog, menu, loading moment, or expanded panel, a state can declare an act sequence after loading its URL.

{
  id: "edit-search-dialog",
  url: "/trips/trip-searched/inventory",
  act: [{ click: 'button:has-text("Edit search")' }],
  expect: "[role=dialog]",
  purpose: "Check the dialog against a full result list.",
}

Each action step uses exactly one of click, hover, fill, press, or wait. A fill also needs a string value. Prefer selectors based on accessible names. Use expect when opening an overlay so a click that did nothing cannot silently capture the unopened page.

A static frame contains no application JavaScript. If a captured state is normally reached by a control, reachedBy can identify that control. The capture marks it so selecting the control moves the canvas to the corresponding frame instead of navigating the inert snapshot.

States captured with a click action already identify their trigger. Use reachedBy for a URL-addressable state reached by a tab, mode switch, or list row.

Validate what differs#

After capture, compare the visible result rather than only checking for HTTP 200. Client-rendered applications can return a loading shell for every state. Two states with identical rendered text or pixels usually mean the fixture selector is ignored, a fallback erased the difference, or the state should not exist.

Read Prepare states for the app-owned fixture workflow.