# ADR-0001: AspectRatio is a typed enum, not a free-form string

**Status:** Accepted (Phase 2)

## Context

Project frame ratios appear all over the system: as a property on a Project,
as an optional override on a Take, as a CSS class on a media frame, as a
parameter to model dispatch, and persisted in workspace state. Pre-Console-v2
these were tracked as free strings (`"9:16"`, `"vertical"`, `"portrait"`),
which led to drift across producers and consumers and silently broke
aspect-aware CSS when a typo passed through.

## Decision

Define `AspectRatio` as a closed enum with exactly four canonical values:
`"9_16" | "16_9" | "1_1" | "4_3"`. Validate at the system boundary via
`AspectRatioSchema` (zod) and brand TypeScript usage with `isAspectRatio()`.
The enum lives in `@recoil/contracts/aspect.ts`; the CSS class lookup table
lives in `@recoil/design-system/aspect.ts` (single home per Law 5).
`Project.aspect` is REQUIRED (no fallback default); `Take.aspect` is an
optional override.

## Consequences

The compiler refuses unknown aspect strings at every consumer; CSS class
generation is a one-line lookup; new aspects (e.g., 21:9) require an explicit
contract change followed by a coordinated tokens.css update. The grep gate
in `verify-laws` enforces that `aspect-ratio:` only appears in `tokens.css`
(four times — one per enum value). Anti-Pattern 4 (hand-typed shadow types)
becomes harder to commit because every aspect string flows through one
parser.
