opsinjs
HandbookCorrectness and cost

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-axes

Severities

3 classes, and they are a statement about consequence rather than about how noisy the warning is.

SeverityCodesWhat it means
safety8A defect that can mislead a reader about their own health. Treat as a bug, not as a lint warning.
correctness9The component will render something, but not what the author meant.
hygiene4Works 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.

CodeSeverityWhat went wrongValues in the messagePrevented by
OPSIN-0001safetyBoth a category and a status were given to one surfacecomponent, category, statushealth/two-colour-axes
OPSIN-0002safetyA status was rendered without a wordcomponent, statushealth/clinical-status-semantics
OPSIN-0003safetyA value was rendered without a unitvalue, nullhealth/unit-systems
OPSIN-0004safetyA reference range was rendered without a sourcecomponenthealth/reference-ranges
OPSIN-0005safetyMore than one urgent surface on a screencounthealth/alarm-fatigue
OPSIN-0006safetyA banned word appeared in a component's copytext, word, replacementcontent/plain-english-a-z
OPSIN-0007safetyA health value was animatedcomponent, tokenhealth/motion-in-health-ui
OPSIN-0008correctnessA raw colour value was passed where a token is requiredcomponent, prop, valuefoundations/token-architecture
OPSIN-0009correctnessA primitive token was referenced from a componenttokenfoundations/token-architecture
OPSIN-0010correctnessAn unknown category was requestedcategory, knowntheming/category-palettes
OPSIN-0011correctnessunknown was used as a status levelnonehealth/uncertainty-and-staleness
OPSIN-0012correctnessA trend was drawn from too few pointscount, minimumhealth/trends-and-change
OPSIN-0013correctnessA chart's y-axis was truncatedcomponent, minfoundations/data-visualisation/chart-anatomy
OPSIN-0014correctnessCategory colours were used as chart series colourscountfoundations/data-visualisation/chart-colour
OPSIN-0015correctnessA touch target is below the floorcomponent, width, heightaccessibility/target-size-and-motor
OPSIN-0016correctnessA stale reading was rendered as currentcomponent, agehealth/uncertainty-and-staleness
OPSIN-0017hygieneMore than three translucent surfaces are compositedcountfoundations/materials/performance-budget
OPSIN-0018hygieneA deprecated token was referencedtoken, version, replacement, removalproject/deprecations
OPSIN-0019hygieneThe token stylesheet was not loadednonetheming/tailwind-v4
OPSIN-0020hygieneTwo theme providers are mountednonehandbook/dark-mode
OPSIN-0021safetyA status outside the four levels was passedcomponent, statushealth/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 safety code 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-0007 is next to OPSIN-0008 because 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_ENV guard 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. Where process is 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-0021 is allocated, so the next one is OPSIN-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.
  • 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.

On this page