opsinjs
HandbookRegistry & distribution

Self-hosting a fork

Running your own registry from a fork of opsinjs. What to change, what to keep, and how to stay able to take updates.

Overview

Forking is a first-class outcome here, not a failure of the project. A hospital trust, an insurer or a company with its own clinical governance will have rules opsinjs cannot encode, and the honest answer is to run your own registry with your own components and your own review process.

This page is about doing that without ending up with a snapshot nobody can update. The distinction that matters is between forking the registry and forking the system. Forking the registry gives you your own items, your own namespace and your own governance. Forking the system takes on the tokens, the two axes and the doctrine. The first is cheap and expected. The second is expensive and should be a decision somebody signs.

Decide what you are forking

You wantFork thisCost
Your own components alongside opsinjsNothing. Run a second registryLow. Two namespaces in components.json
Your own themeNothing. Publish a presetLow
Your own copies of opsinjs components, editedThe registryMedium. You own the diffs
Different category ramps or a seventh categoryThe token sourceMedium. Regenerate and re-measure
A different status vocabularyThe systemHigh. See below
Different clinical doctrineThe system, and get clinical sign-offHighest

Most people who think they need the last two rows need the third.

Run your own registry

You do not need a fork of this repository to do this. A registry is JSON at a URL.

Build a catalog

registry.json with your name, homepage and items. Field by field in registry.json. Keep it flat and keep file contents out of it.

Serve one file per item

/<name>.json, matching the {name} pattern consumers will configure. The shadcn CLI has a build command that generates these from a local registry.json, so this is usually not code you write.

Pick a namespace nobody else will want

Your organisation, not a generic word. @acme-health, not @health.

Declare dependencies across registries by URL

An item of yours that depends on an opsinjs item must reference it by full URL, or by a namespace you can be certain the consumer has configured. A bare name resolves against whichever registry the CLI is currently looking at, and the failure is silent.

Authenticate if it is private

The long form of a registry entry takes headers, with values substituted from the environment. Never commit a token into components.json.

Fork the repository

If you need the token source, the generators and the documentation site, fork the whole thing. Three things to do immediately, before anyone else clones it.

Change the identity. Registry name, homepage, the namespace, the preset prefix and the site URL. A fork that still says opsinjs.pensievelabs.org will confuse your own developers and misattribute your decisions to this project. Official resources covers this from the other direction, and the same reasoning applies to you.

Keep the generators. build-tokens, check-contrast and assert-ia are the parts that make the system self-checking. A fork that keeps the tokens and drops the checks has kept the values and lost the guarantee, and it will drift within one release.

Record the divergence. One page in your own docs listing everything you changed and why. This is the single highest-value artefact of a fork and it is almost always the one that does not get written.

Staying able to take updates

Keep a clean upstream remote

Track this repository as a remote you never commit to. Merging is easier than archaeology.

Keep your changes separable

Your own tokens in your own file; your own components under their own directory; your own documentation pages in their own group. A fork whose changes are scattered through generated files cannot be merged, only re-done.

Never edit generated output

app/tokens.generated.css, lib/generated/**, registry/__index__.ts and the generated reference pages are all rewritten by pnpm run generate and diffed by pnpm run check:generated. Edit the source.

Re-measure after every merge

pnpm run generate && pnpm run contrast && pnpm run check

A merge that changes a token and not its measurement is the failure mode this whole pipeline exists to catch.

Licensing

The code and the documentation are licensed separately. See Licence and attribution before publishing a fork, particularly if you are republishing the prose. Third-party guidance this project cites, such as national health service manuals, is cited and never copied, and that constraint travels with the fork: content under Crown copyright or a similar licence cannot be redistributed just because it is adjacent to something that can.

Verify it worked

A consumer can install from you with no configuration

Test the full-URL form from a clean project, using an item that composes nothing. An item that names its siblings by namespace needs that namespace configured whichever form the consumer types, and many opsinjs items are in exactly that position. Because of that, either keep your leaf items self-contained, or make the namespace part of your published contract and say so on your own install page.

Your catalog validates

Against shadcn's schema. A registry that only works with your own tooling has given up the main benefit of using somebody else's spec.

The checks still pass

pnpm run check in the fork. If check:ia fails, the fork's page tree and its catalogue have already diverged.

Troubleshooting

Consumers get 404s for some items. The catalog lists an item with no served file. Run the build step that generates per-item files.

A cross-registry dependency installs the wrong thing. A bare name. Use a URL.

Merging upstream produces conflicts in generated files. Take upstream wholesale and regenerate; never hand-merge generated output.

Our fork's contrast numbers do not match the published ones. Correct, and expected. Publish your own; the published figures describe the opsinjs presets only.

Next

On this page