---
title: "Decisions"
description: "Architecture decision records say why the system is shaped the way it is, including the choices that constrain everything built after them."
url: "https://opsinjs.pensievelabs.org/project/decisions"
source: "https://opsinjs.pensievelabs.org/project/decisions.md"
section: "Project"
kind: "project"
reviewed: "2026-09-02"
reviewer: "engineering"
aliases: ["ADR", "architecture decisions", "why is it like this"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="project" />

## What these are [#what-these-are]

An architecture decision record is one page describing one decision: what forced
it, what was chosen, what was rejected, and what the project accepted as a
consequence. They are numbered, they are never rewritten to look better in
hindsight, and they are public.

They are public because of who is expected to depend on this system. A team
adopting a component library for a patient-facing product may have to justify
that choice to a clinical safety officer, an accessibility auditor or a
procurement reviewer, none of whom were in the room. "The system does it this
way" is not an answer to any of them. A dated record naming the alternative and
the trade-off is.

## The format [#the-format]

Every record has the same six headings, and the two that matter most are the two
usually missing.

**Consequences** is where the cost is written down. A decision with only benefits
listed has not been thought about; it has been advertised. Consequences include
the things that got harder, the work the decision created, and the risks accepted.

**Alternatives considered** names the option that was rejected and why. This is
what makes a record useful in two years, when somebody proposes the rejected
option again. They usually have good reasons, because the reason it was rejected
may have expired.

## Status values [#status-values]

| Status                            | Meaning                                                    |
| --------------------------------- | ---------------------------------------------------------- |
| **Proposed**                      | Written, not yet agreed                                    |
| **Accepted**                      | In force. The system is built this way                     |
| **Accepted, not yet implemented** | Agreed and binding, but the code is not written            |
| **Superseded**                    | Replaced by a later record, which is named. The page stays |

A record is normally kept even after it is reversed, because the reasoning that
produced a decision we later undid is the most useful thing in this folder. The
exception is a record whose subject no longer exists in the system at all. Two
records described the `considered` release phase and the roster built from it.
That phase has been removed from the vocabulary, so those records described a
mechanism that no page, no script and no catalogue row can refer to any more,
and a page kept on the site keeps teaching its vocabulary to every reader and
to every agent fetching the `.md` twins. A reader cannot tell a withdrawn record
from a live one by reading it, which is why the page goes rather than gaining a
banner. They were withdrawn and deleted, and
[0023](./0023-three-release-phases.mdx) names what went. Three conditions hold
whenever that happens: the record that removes the mechanism names what went,
the number is never reused, and git history is the archive.

## When to write one [#when-to-write-one]

Write a record when a choice is expensive to reverse, constrains work that comes
after it, or will look arbitrary to somebody who arrives later. Choosing a
primitive library, a distribution model or a colour architecture qualifies.
Choosing a variable name does not.

Write it **before** implementing, not after. A record written afterwards
describes what happened; a record written first is a decision that can still be
argued with while arguing is cheap.

## The records [#the-records]

| #                                                      | Decision                                                        | Status                               |
| ------------------------------------------------------ | --------------------------------------------------------------- | ------------------------------------ |
| [0001](./0001-base-ui-not-radix.mdx)                   | Base UI, not Radix, as the primitive layer                      | Accepted                             |
| [0002](./0002-shadcn-registry-distribution.mdx)        | Distribute as a shadcn registry; no CLI first                   | Accepted                             |
| [0003](./0003-two-colour-axes.mdx)                     | Two independent colour axes that never mix                      | Accepted                             |
| [0004](./0004-lyra-docs-chrome.mdx)                    | lyra themes the documentation chrome only                       | Accepted                             |
| [0005](./0005-no-lang-segment-yet.mdx)                 | No `[lang]` segment; bound the retrofit instead                 | Accepted                             |
| [0006](./0006-generated-not-authored.mdx)              | Source, tables and measured numbers are generated               | Accepted                             |
| [0007](./0007-two-root-layouts.mdx)                    | Two sibling root layouts, and no `app/layout.tsx`               | Accepted                             |
| [0009](./0009-default-export-is-a-demo.mdx)            | The default export of a registry file is a zero-prop demo       | Accepted                             |
| [0010](./0010-shared-code-ships-with-every-item.mdx)   | Shared code ships inside every registry item                    | Accepted                             |
| [0011](./0011-anatomy-parts-are-data-slots.mdx)        | Anatomy parts are internal structure, identified by `data-slot` | Accepted                             |
| [0012](./0012-synthetic-example-data.mdx)              | Example data is synthetic and explicitly unsourced              | Accepted                             |
| [0013](./0013-previews-are-iframes.mdx)                | A component preview is an iframe, not an inline render          | Accepted                             |
| [0014](./0014-material-rung-names.mdx)                 | The material rung names are the token names                     | Accepted                             |
| [0015](./0015-error-codes-are-flat.mdx)                | One flat error-code scheme; `errors.json` is the source         | Accepted                             |
| [0016](./0016-the-unit-table-ships-no-factors.mdx)     | The unit table ships spoken forms, not conversion factors       | Accepted                             |
| [0017](./0017-precision-means-decimal-places.mdx)      | `precision` means decimal places, everywhere                    | Accepted                             |
| [0021](./0021-modal-surfaces-own-their-open-state.mdx) | A modal surface owns its own open state                         | Accepted                             |
| [0023](./0023-three-release-phases.mdx)                | Three release phases, and only a component page carries one     | Accepted, outline superseded by 0024 |
| [0024](./0024-the-component-page-is-lean.mdx)          | The component page is lean                                      | Accepted                             |
| [0025](./0025-the-audit-is-author-run.mdx)             | The WCAG 2.2 AA audit is author-run, not an independent review  | Accepted                             |

The numbers are not contiguous. There is no 0008, no 0018 to 0020 and no 0022.
0008 and 0022 were withdrawn and deleted; 0018 to 0020 were never written. The
gaps are in the numbering rather than in this table, and they are left as they
are because renumbering a record would break every link and every citation that
already names it.

<LastUpdated />

<Reviewed />
