---
title: "Upgrading"
description: "Taking updates on code you own. Diffs, dry runs, three-way merges, and how to know which version you started from."
url: "https://opsinjs.pensievelabs.org/start/installation/upgrading"
source: "https://opsinjs.pensievelabs.org/start/installation/upgrading.md"
section: "Start here"
kind: "guide"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["upgrading opsinjs", "taking updates", "diff copied source"]
---

> 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="guide" />

## Overview [#overview]

Copied source is a trade. You get to change any line, you are never broken by an
update you did not ask for, and no dependency can alter your interface between two
deploys. In exchange, improvements do not arrive on their own, and taking one means
merging into code you may have edited.

That trade is a good one for a health interface, where an unreviewed change to how
a clinical status renders is a genuine hazard. But it only holds if upgrading is
survivable, and this page is how opsinjs intends to make it so.

<Callout title="There is code, but no released version to upgrade from">
  Every catalogue row is implemented and installable, and nothing has been
  released. So there is no earlier version of anything to move off,
  no changelog entry to read, and no diff to run. The mechanics below are the
  intended contract rather than a path anybody has walked.
  [Troubleshooting](../troubleshooting.mdx#nothing-is-published-yet).
</Callout>

## See what changed before you change anything [#see-what-changed-before-you-change-anything]

<Steps>
  ### Read the changelog first [#read-the-changelog-first]

  Changes are described in terms of behaviour, not commits, and anything that alters
  what a component *asserts* about a person's health is called out separately from
  anything cosmetic. [Changelog](../../project/changelog/index.mdx).

  ### Diff your copy against the current source [#diff-your-copy-against-the-current-source]

  <CodeBlockTabs defaultValue="npm" groupId="package-manager">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="npm">
        npm
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="pnpm">
        pnpm
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="yarn">
        yarn
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="bun">
        bun
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="npm">
      ```bash
      npx shadcn@latest diff @opsinjs/result-card
      ```
    </CodeBlockTab>

    <CodeBlockTab value="pnpm">
      ```bash
      pnpm dlx shadcn@latest diff @opsinjs/result-card
      ```
    </CodeBlockTab>

    <CodeBlockTab value="yarn">
      ```bash
      yarn dlx shadcn@latest diff @opsinjs/result-card
      ```
    </CodeBlockTab>

    <CodeBlockTab value="bun">
      ```bash
      bun x shadcn@latest diff @opsinjs/result-card
      ```
    </CodeBlockTab>
  </CodeBlockTabs>

  This shows the difference between the file in your repository and the current
  registry version. That includes your own edits, which is usually the more
  interesting half.

  ### Dry-run the write [#dry-run-the-write]

  <CodeBlockTabs defaultValue="npm" groupId="package-manager">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="npm">
        npm
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="pnpm">
        pnpm
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="yarn">
        yarn
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="bun">
        bun
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="npm">
      ```bash
      npx shadcn@latest add @opsinjs/result-card --dry-run
      ```
    </CodeBlockTab>

    <CodeBlockTab value="pnpm">
      ```bash
      pnpm dlx shadcn@latest add @opsinjs/result-card --dry-run
      ```
    </CodeBlockTab>

    <CodeBlockTab value="yarn">
      ```bash
      yarn dlx shadcn@latest add @opsinjs/result-card --dry-run
      ```
    </CodeBlockTab>

    <CodeBlockTab value="bun">
      ```bash
      bun x shadcn@latest add @opsinjs/result-card --dry-run
      ```
    </CodeBlockTab>
  </CodeBlockTabs>

  Confirms which files would be touched before any of them is.
</Steps>

## Merge deliberately [#merge-deliberately]

Three approaches, in the order you should prefer them.

**You have not edited the file.** Overwrite it. Check the diff, take the new
version, run your tests. This is the common case and it takes a minute.

**You have edited it lightly.** Take the new version and re-apply your edits on
top, rather than hand-picking hunks out of the update. Re-applying a small, known
change to new code is more reliable than reasoning about a merge. It also makes
you re-justify the edit, which is often useful on its own.

**You have edited it heavily.** Treat it as a three-way merge between the version
you started from, your current file, and the new one. Nothing in the file tells
you the first of those three, so that base comes from your own version control:
commit the files `shadcn add` writes, in their own commit, so a later `--diff`
has something to compare against.

If you find yourself doing a heavy merge repeatedly on the same component, the
divergence is telling you something. Either your change belongs upstream, or
your component has become a different component. In the first case, propose it.
In the second, the component should stop pretending to be an opsinjs one.

## Treat generated files as generated [#treat-generated-files-as-generated]

Two things in your project are not yours to edit, and editing them is the main
avoidable source of upgrade pain:

* **The token layer** (`opsinjs.css`). Replace it wholesale on every upgrade. Your
  extensions belong in your own `@theme` block after it. See
  [Adding your own tokens](../../theming/adding-your-own-tokens.mdx).
* **Anything carrying a generated-file header.** If a file says it is generated, an
  upgrade will overwrite it, and the polite warning in the header is the only notice
  you get.

## Verify it worked [#verify-it-worked]

1. **Your tests pass**, especially any that assert on rendered clinical status.
2. **Nothing in your own code was silently rewritten.** Read the diff of the whole
   commit, not just the component file.
3. **The token layer still resolves.** An upgraded component may read a token that
   an older token layer does not define. Upgrade both together.
4. **The greyscale check still passes.** A visual change that quietly makes status
   colour-only is exactly the kind of regression an upgrade can introduce and a
   test suite will not catch.
5. **You have recorded which version you took**, in the commit message or the
   pull request. Nothing in the file records it for you.

## Troubleshooting [#troubleshooting]

**A component references a token that does not exist.** The component and the token
layer are from different versions. Upgrade the token layer.

**The diff is enormous and you did not edit the file.** This is formatting
drift. A different Prettier configuration reformatted it on the way in.
Normalise before comparing, or the real change will be invisible inside the
noise.

**Your edits are gone.** The upgrade overwrote them. This is why the dry run and
the diff come first; recover from version control, then re-apply on top.

**You cannot tell which version you have.** There is no stamp in the file to look
at. Emitting one is specified and not built. Fall back to version control: find
the commit that added or last updated the file and read the date, then diff
against the registry to see how far apart you are. If that is hopeless, take the
current version cleanly and record it this time.

## Next [#next]

* [Versioning policy](../../project/versioning-policy.mdx) says what semver
  covers here, which is more than you expect.
* [Upgrades and diffs](../../registry/upgrades-and-diffs.mdx) covers the
  registry-side mechanics.
