Error codes
Every development-mode warning opsinjs will emit, its stable code, what causes it, and the page that prevents it happening again.
The short version
Every development-mode warning opsinjs emits carries a stable code, a sentence that says what to do instead, and a link:
[opsinjs] OPSIN-NNNN (severity): what was passed, why it is wrong, and the one
thing to do instead.
→ https://opsinjs.pensievelabs.org/docs/<the page that prevents it>Three properties make this worth building rather than emitting a sentence:
- The code is stable across versions, so a search finds the answer regardless of which release produced it.
- Every code links to the page that explains the rule, not to a stack trace.
- Warnings are development-only. Nothing in this list ships to production, and none of them throws.
The complete table is generated from tokens/errors.json into the section
below. It is not maintained by hand here, and no row of it is written twice.
How it works
The codes are flat. OPSIN-0001 upwards, allocated in sequence, with no
grouped ranges and no meaning attached to the leading digits. A code is
permanent: never reused, never renumbered, never removed. Retiring one leaves
its row in place, because the code will outlive this release in somebody's log
aggregator. The reason the scheme is flat rather than banded is written down in
ADR 0015; the short
version is that a band is a promise about a code's meaning that the number
cannot keep, and that the first mis-filed code makes every other one unsearchable.
One code names exactly one page. The page is the code's permanent home and it is where the rule is argued rather than asserted. That is also why the severity column below is not a priority queue: it says what happens if you leave the defect in, not how soon somebody will shout at you.
A warning fires once per code and message per session, not once per render. The message includes the values the component passed, so two components making the same mistake produce two warnings while one component making it in sixty consecutive frames produces one. React's Strict Mode double-invokes a render on purpose; the second invocation computes the same message, finds it already reported, and prints nothing.
The runtime and this page read the same source. tokens/errors.json is the
only place a code is authored. scripts/build-tokens.mts emits it into three
places: lib/opsinjs.ts, lib/generated/tokens.ts for the documentation site,
and the table below. lib/opsinjs.ts is the file shadcn add copies into your
project, so the messages travel with the components. Editing any of the three is
undone by the next pnpm run generate.
A warning is one console.warn call, and it looks like this. The braces are
filled from what the component passed; this is the template as authored, so
what you see below is what the code emits and not a paraphrase of it.
[opsinjs] OPSIN-0001 (safety): <{component}> received both `category="{category}"` and `status="{status}"`. A surface carries one axis. Set the category on the surface and render the status as a StatusPill inside it.
→ https://opsinjs.pensievelabs.org/health/two-colour-axesSeverities
3 classes, and they are a statement about consequence rather than about how noisy the warning is.
| Severity | Codes | What it means |
|---|---|---|
safety | 8 | A defect that can mislead a reader about their own health. Treat as a bug, not as a lint warning. |
correctness | 9 | The component will render something, but not what the author meant. |
hygiene | 4 | Works today, will not survive an upgrade. |
Every code
21 codes, in allocation order. "Values in the message" names the
{braced} spans a component has to supply; a missing one is reported in the
warning itself rather than swallowed.
| Code | Severity | What went wrong | Values in the message | Prevented by |
|---|---|---|---|---|
OPSIN-0001 | safety | Both a category and a status were given to one surface | component, category, status | health/two-colour-axes |
OPSIN-0002 | safety | A status was rendered without a word | component, status | health/clinical-status-semantics |
OPSIN-0003 | safety | A value was rendered without a unit | value, null | health/unit-systems |
OPSIN-0004 | safety | A reference range was rendered without a source | component | health/reference-ranges |
OPSIN-0005 | safety | More than one urgent surface on a screen | count | health/alarm-fatigue |
OPSIN-0006 | safety | A banned word appeared in a component's copy | text, word, replacement | content/plain-english-a-z |
OPSIN-0007 | safety | A health value was animated | component, token | health/motion-in-health-ui |
OPSIN-0008 | correctness | A raw colour value was passed where a token is required | component, prop, value | foundations/token-architecture |
OPSIN-0009 | correctness | A primitive token was referenced from a component | token | foundations/token-architecture |
OPSIN-0010 | correctness | An unknown category was requested | category, known | theming/category-palettes |
OPSIN-0011 | correctness | unknown was used as a status level | none | health/uncertainty-and-staleness |
OPSIN-0012 | correctness | A trend was drawn from too few points | count, minimum | health/trends-and-change |
OPSIN-0013 | correctness | A chart's y-axis was truncated | component, min | foundations/data-visualisation/chart-anatomy |
OPSIN-0014 | correctness | Category colours were used as chart series colours | count | foundations/data-visualisation/chart-colour |
OPSIN-0015 | correctness | A touch target is below the floor | component, width, height | accessibility/target-size-and-motor |
OPSIN-0016 | correctness | A stale reading was rendered as current | component, age | health/uncertainty-and-staleness |
OPSIN-0017 | hygiene | More than three translucent surfaces are composited | count | foundations/materials/performance-budget |
OPSIN-0018 | hygiene | A deprecated token was referenced | token, version, replacement, removal | project/deprecations |
OPSIN-0019 | hygiene | The token stylesheet was not loaded | none | theming/tailwind-v4 |
OPSIN-0020 | hygiene | Two theme providers are mounted | none | handbook/dark-mode |
OPSIN-0021 | safety | A status outside the four levels was passed | component, status | health/clinical-status-semantics |
Do this
- Fix the cause, not the symptom. Every code links to a page that explains why the thing is a rule; the fix is usually one line and the reasoning is usually worth two minutes.
- Treat a
safetycode as an error even though it warns. Those are the ones where the visible result looks fine and the meaning is wrong, which is exactly the class of bug that survives review. The reader who is misled by it is being misled about their own health. - Search by code, not by message text. Messages get clearer between versions; codes do not change.
- Turn warnings into failures in CI if you want them enforced. They are warnings so that they never break a reader's session, not because they are optional.
- Report a warning that fires when it should not. A false positive here trains people to ignore the channel, which is the same alarm-fatigue failure this system documents elsewhere.
Not this
- Do not suppress the console to silence these. You will silence React's warnings too, and those are the ones that catch hydration bugs.
- Do not ship a workaround for a warning you have not read. Several of these are about a claim being made to a reader about their health; the workaround usually preserves the claim and hides the notice.
- Do not rely on them in production. They are stripped. Anything you need at runtime must be validated by your own code.
- Do not parse the message string. Parse the code. The prose is not a contract.
- Do not read a code's digits as a category.
OPSIN-0007is next toOPSIN-0008because it was written first, not because they are related.
Gotchas
- A warning fires once per cause, not once per instance. Fixing one of five identical mistakes makes the message vanish and the other four persist.
- A message with a
{brace}still in it is a second bug. The component raised a warning without supplying one of the values the message asks for. The warning still prints, and names what was missing, because losing a safety complaint to a typo in the complaint is the worse of the two outcomes. - Development-only means bundler-dependent. In an unusual build setup the
NODE_ENVguard may not be applied, and you may see codes in a production bundle. That is a bundler configuration issue rather than a change in behaviour. Whereprocessis not defined at all the channel stays silent rather than guessing. - Server-rendered warnings appear in the server log, not in the browser console. That is why a warning can seem to fire "sometimes".
- New codes are not a breaking change.
OPSIN-0021is allocated, so the next one isOPSIN-0022, whatever it turns out to be about. Renumbering an existing one would be. - A warning about a copied file being out of date is not a bug in your code. You own the source; see Upgrades and diffs.
Related
- The two colour axes is the rule behind
OPSIN-0001, and the one most likely to look right and mean the wrong thing. - ADR 0015 explains why the scheme is flat rather than banded.
- ESLint plugin catches the same mistakes before they run.
- Troubleshooting covers failures that are not warnings, with their exact error text.
Naming conventions
The published naming contract for component ids, prop names, token names, data attributes, files and CSS custom properties, and where the two spellings diverge.
Performance and bundle size
What each component will cost, the composited-surface budget that matters more than kilobytes, and how to measure both rather than argue about them.