agentleFS
Sign inSign up

frontend-page

tadoEng/web-starter/.claude/skills/frontend-page/SKILL.md

Add or change a page, screen, form, table or component in this product's React frontend, with the five async states and the accessibility floor. Use when asked to build a page, add a route, create a form, show a list or table, or change the UI.

Skill2 starsChanged 50 days ago
---
name: frontend-page
description: >
  Add or change a page, screen, form, table or component in this product's React frontend, with
  the five async states and the accessibility floor. Use when asked to build a page, add a
  route, create a form, show a list or table, or change the UI.
---

# Frontend page or component

Read `AGENTS.md` first. The frontend owns presentation and client state. It does not own
business rules, authorization, or data shapes.

## Steps

1. **Route** in `frontend/src/routes/`, registered with React Router.
2. **Data** via a TanStack Query hook in `frontend/src/api/`, built on the **generated** client.
   If the endpoint doesn't exist yet, use the `api-endpoint` skill first — do not work around a
   missing endpoint from the client side.
3. **UI** from shadcn/ui primitives in `frontend/src/components/ui/`, composed in
   `frontend/src/components/`. Prefer an unmodified primitive over a bespoke one.
4. **Forms**: React Hook Form + the **generated** Zod schema from
   `src/generated/api-client/zod.gen.ts`. Zod is generated precisely so the shape is never
   written twice — never hand-write one that mirrors the request type. For presentation-only
   constraints (confirm-password, a client-side max), `.extend()` or `.refine()` the generated
   schema rather than copying its fields.
5. **Tests**: Vitest + React Testing Library. Query by role and label.
6. `./scripts/verify.sh`, then verify in the browser and attach a screenshot.

## The five states — all of them, every async surface

| State | Requirement |
|---|---|
| Loading | Skeleton sized like the real content, so layout doesn't jump |
| Empty | Says what would appear here and how to create the first one |
| Error | The message from the error contract, plus retry where retry helps |
| Success | The content; for mutations, a confirmation the user can notice |
| Disabled | Buttons disable while a mutation is in flight — no double submits |

A screen that renders only the happy path is not finished. Optimistic updates need a rollback
path or they are a bug.

## Forms

- Field errors map from **422 responses**, not just client validation. A form that silently
  fails a server rejection is worse than a crash.
- Every input has a real `<label>`. A placeholder is not a label.

## Accessibility floor

Cheap to get right, expensive to retrofit:

- Every interactive element keyboard-reachable and operable, in sensible order.
- Visible focus ring — never `outline: none` without a replacement.
- `aria-label` on icon-only buttons.
- Contrast 4.5:1 body, 3:1 large text and UI borders.
- Dialogs trap focus, close on Escape, return focus to the trigger.
- Never convey state by colour alone.

Testing by role and label keeps most of this honest automatically.

## Responsive — a hard gate, not a finish

Every screen must work at **375px** and at desktop width.

- The **page body never scrolls horizontally.** Wide content — tables, charts, code, long
  unbroken strings — scrolls inside its own `overflow-x: auto` container.
- Decide desktop → tablet → mobile per surface. Navigation, tables, forms, toolbars, dialogs
  and charts each need an actual decision, not whatever flexbox does by default.
- A six-column table becomes **compact record rows** on mobile, or scrolls in its own container.
  Both are legitimate; a horizontally crushed table is not.
- Use relative units and `max-width: 100%` on media. Test with a long title and a null optional
  field, not just the comfortable middle.

`frontend/e2e/smoke.spec.ts` enforces the no-overflow rule at 375px mechanically. **Do not
weaken that test to make a layout pass** — that inverts the gate into a rubber stamp.

Ship visual work with a desktop screenshot **and** a 375px screenshot. For layout-led work, use
the `frontend-craft` skill instead of this one.

## Stop

Hiding a control because of a role is **UX only** — the backend must reject the action
independently and have a test proving it. If you find yourself writing a business rule in a
component, it belongs on the backend.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.