opsinjs
HandbookCorrectness and costTooling

Tooling

Two lint plugins that turn the system's most important rules from advice into build failures. A rule enforced only by review is a rule enforced only sometimes.

The short version

NOT IMPLEMENTED. This component does not exist in any released version of opsinjs. There is no package to install, no module to import and no props interface to generate code against. Everything on this page is a specification of intended behaviour and may change without notice. Do not write code against it.

This component is not built yet

There is nothing to render because there is nothing to install. What you can read on this page is the specification the implementation will have to satisfy.

PlannedRoadmapWhat “planned” means

Two packages are specified:

  • eslint-plugin-opsinjs covers JSX and TypeScript. Catches a raw colour, a mixed axis, a banned word, and a component used against its documented contract.
  • stylelint-plugin-opsinjs covers CSS. Catches a hardcoded colour, a token used outside its tier, and a status token applied to a category surface.

The reasoning is simple arithmetic. The never-mix-the-axes rule has one page of doctrine, one section on every component page, a paragraph in the agent skill and a mention in the contributing checklist. That is five places asking a human to remember. A lint rule asks nobody.

How it works

The rules divide by what they can see.

A lint rule can check a syntactic invariant. Is this a hex colour in a className? Are status and category both being passed to a component whose documentation says they must not both change the surface? Is this CSS custom property from the status namespace being assigned to a category role? These are decidable from the source and are exactly what a plugin should own.

A lint rule cannot check a semantic one. Whether this particular reading deserves urgent is a clinical judgement, and no static analysis will ever answer it. The plugins deliberately do not try; they enforce the shape of the system, and the meaning stays with the people who own the thresholds.

The third layer is runtime: development-mode warnings for the mistakes that only become visible when data flows through them. See Error codes. Each layer catches what the one before it cannot, and the error codes and lint rule names are deliberately aligned so that one search finds both.

Do this

  • Install both plugins and turn them on in CI. A rule that runs only locally is a rule that runs only for the people who remember.
  • Start with the recommended config. It contains the rules that are almost never a false positive; add the stricter ones once the codebase is clean.
  • Fix rather than disable. If you must disable, disable the specific rule on the specific line with a comment saying why. A bare file-level disable removes protection nobody will notice is gone.
  • Report a false positive. A noisy rule gets disabled wholesale, and then the real violations get through too.

Not this

  • Do not treat the plugins as sufficient. They enforce shape. Every component has been audited against WCAG 2.2 AA, but that audit was run by the authors, not an independent reviewer; see ADR 0025 for the record. The independent accessibility review, the clinical review and the content review are still the things that catch the failures that matter most.
  • Do not add project rules to the opsinjs/ namespace. Use your own; the namespace is versioned.
  • Do not run the plugins on generated output. .source/, lib/generated/ and public/r/ are excluded in this repository for exactly that reason.

Gotchas

  • The plugins do not exist yet. Both pages are specifications. They carry proposed rule names so that the error codes, the documentation and the future implementation can be written against one vocabulary rather than three.
  • A lint rule cannot follow a variable. A colour assembled at runtime, or a status read from a variable, is invisible to static analysis. That is what the runtime warnings are for.
  • Tailwind arbitrary values are the escape hatch that defeats most rules. bg-[--my-var] and bg-[#e11d48] look similar and only one is a violation; the rule has to parse the arbitrary value rather than the class name.

On this page