# Frontend / SPA Constitution (v1)

Derived from first principles. Paste into `.specify/memory/constitution.md` via
`/speckit.constitution`. Preserve the exact phrasing of each principle; do not
paraphrase into generalities.

Source: https://daita.io/blog/spec_kit_constitution_first_principles

## Principles

### 1. Optimize for deletion, not extension

A component must be small enough that one engineer can delete and rewrite it
in a day. Reject speculative abstractions (HOCs, render-prop towers, generic
`<Wrapper>` layers). Inline until it hurts, then extract. Duplication below
three occurrences is cheaper than the wrong abstraction.

### 2. Make dependencies explicit

No hidden coupling via module-level state, no implicit context providers that
components silently rely on. Every prop a component reads is in its signature.
Every hook's dependencies are declared. No import-time side effects.

### 3. The network is the boundary

Every fetch is a contract. Requests and responses have schemas with versions.
Validation happens at the boundary, not scattered through components. Treat
the server as an untrusted producer: never render raw server strings, always
validate before use.

### 4. Render what you can prove

No loading spinner without a timeout. No error state without a retry path. No
empty state without copy. A component that can render "nothing" for any reason
must render a deliberate "nothing", not a blank frame.

### 5. Accessibility is a correctness property

Keyboard navigation, focus order, ARIA roles, and color contrast are not
polish. They are either correct or broken. Every interactive element must be
reachable by keyboard and announced by a screen reader. No `<div onClick>` in
new code.

### 6. Measure what users feel

Core Web Vitals (LCP, INP, CLS) are the latency budget, not nice-to-haves.
Regressions in these are treated like test failures. Bundle size is tracked
per route; new dependencies that add more than the route's remaining budget
require a written justification.

### 7. State lives at the edge it is needed

URL state is URL state. Server state is server state (cache, not store).
Form state is local. Global client state is the last resort. A new entry in a
global store requires a written justification of why URL, server, or local
state could not hold it.

### 8. Commands are discoverable; local dev matches CI

Every repeatable action (dev, build, test, lint, typecheck, e2e) is a single
named command listed in one place. The command a developer runs locally is
the same command CI runs. If a new contributor cannot list every command in
30 seconds, the interface is broken.

### 9. Value is realized at the user, not at merge

A PR is not done until the change is in the hands of users, observable (error
tracking, web-vitals reporting), and revertible via a flag or a redeploy.
"Shipped" means deployed and monitored, not merged.
