Upgrading cascivo

cascivo has two consumption paths, so it has two upgrade stories: npm packages (@cascivo/react, @cascivo/core, …) upgrade with a version bump, and copied components (installed via cascivo add) upgrade with a merge. This page covers both, plus where changes are recorded.


The stability contract

cascivo's public API is covered by semver from 1.0.0 onward. Below 1.0 it was not: a minor could break you. This section is the contract — what is promised, what is not, and for how long.

What a version bump means

BumpFrom 1.0.0 onwardBefore 1.0 (historical)
majormay remove or change covered API; every removal was deprecated firstn/a
minoradds API; never removes or changes covered APImay include breaking changes
patchfixes onlyfixes and improvements, safe to take

From 1.0.0, "^1.2.0" is safe to widen across the whole 1.x line. Below 1.0, npm's ^ range does not cross a minor while the major is 0 — "^0.18.0" means >=0.18.0 <0.19.0 — so the default save prefix already protected you from breaking bumps.

What the promise covers

These are public API. Changing or removing one requires a major:

What the promise does not cover

These may change in any release, including a patch:

Supported versions

Which packages are covered

The 1.x line covers the packages an application depends on directly or transitively at runtime. Tooling packages stay on 0.x until their own surfaces settle, and say so on npm.

LinePackages
1.x@cascivo/react, @cascivo/core, @cascivo/charts, @cascivo/editor, @cascivo/flow, @cascivo/i18n, @cascivo/storage, @cascivo/ai, @cascivo/text, @cascivo/render (the lockstep family below), plus @cascivo/tokens, @cascivo/themes, @cascivo/icons and the cascivo CLI
0.x@cascivo/mcp, @cascivo/registry, @cascivo/docs, @cascivo/docspack, @cascivo/eslint-config, @cascivo/eslint-plugin, @cascivo/vite-plugin, @cascivo/platform, @cascivo/email

@cascivo/platform in particular is an early experiment in platform-idiomatic geometry and motion; treat its API as unsettled. @cascivo/email-preview is a development-only preview server on its own version line (currently 2.x); it is not part of the application runtime.

Where changes are recorded

breaking-changes.json — for machines

Every major and minor release per package, with notes — patch noise is deliberately excluded. Agents (and scripts) compare it against installed versions to detect API drift:

// https://cascivo.com/breaking-changes.json
{
  "generatedAt": "2026-07-02",
  "packages": [
    {
      "name": "@cascivo/charts",
      "version": "0.3.4",
      "releases": [
        { "version": "0.3.0", "level": "minor", "notes": ["PieChart: donut …"] }
      ]
    }
  ]
}

So "what changed between my @cascivo/[email protected] and current?" is: every release in that package's releases array newer than 0.2.1.


The @cascivo/core family versions in lockstep

Ten packages release together at one version: @cascivo/core, @cascivo/react, @cascivo/charts, @cascivo/editor, @cascivo/flow, @cascivo/i18n, @cascivo/storage, @cascivo/ai, @cascivo/text, @cascivo/render.

What this means for you: pin them all to the same version. If @cascivo/react is 1.3.1, so is @cascivo/charts. A mismatched pair is not a supported combination, and cascivo doctor will tell you if your install ended up with one.

Why it is enforced rather than advised. Independently-versioned 0.x packages could resolve two non-overlapping @cascivo/core ranges, and the package manager then nests a second copy. cascivo's reactivity is a module-level signal registry, so two copies means two registries: a signal written through one is invisible to components subscribed through the other. Nothing errors — handlers fire and the UI silently stops updating, which is the hardest failure in this system to diagnose from symptoms.

@cascivo/icons, @cascivo/themes, @cascivo/tokens, @cascivo/mcp, @cascivo/registry, @cascivo/vite-plugin, @cascivo/eslint-config, @cascivo/docs and the cascivo CLI keep their own version lines — none of them link against the signal registry.

scripts/checks/version-lockstep.test.ts fails the build if a package starts depending on @cascivo/core without joining the group, so the guarantee cannot erode as packages are added.

How cascivo renames things (the deprecation contract)

A rename is the most expensive change a component library can make, so the catalog uses one mechanism for all of them. If you see any of these, this is what is happening and how long you have.

  1. Both names work. The old name stays as an alias for at least one minor. Your code keeps compiling and behaving identically.
  2. The old name carries @deprecated in its TSDoc, naming the replacement. Your editor strikes it through; tsc stays silent.
  3. The deprecation names when it expires. Every deprecation records since — the version that introduced the replacement — and removeIn, the major that removes the old name. Both appear in the manifest, and therefore in registry.json, the llms/*.md files and the docs site, so the expiry is discoverable before you adopt the old name rather than after it disappears.
  4. A changeset records it, so it appears in the CHANGELOG and in breaking-changes.json — the file cascivo doctor --drift reads.
  5. A guard keeps the pair honest. The alias and its tracking entry are removed together; a check fails if one outlives the other, so an alias cannot quietly become permanent and a tracking note cannot go stale. The same guard fails the build if a deprecation is still shipping in the major named by its own removeIn — an expiry that can slip is not a policy.

Removals only happen in a major. A deprecation introduced during 1.x is removed in 2.0.0 at the earliest, so the alias is guaranteed to outlive the whole 1.x line.

Aliases that only add a name (ariaLabel alongside aria-label, value alongside id) are not deprecations — both spellings are supported indefinitely, and neither is struck through. They exist because guessing the wrong one cost adopters a compile round-trip.

Where a name is required for accessibility, an alias is expressed as an XOR union ({ label: string; ariaLabel?: never } | { ariaLabel: string; label?: never }) rather than making both optional — the compile-time guarantee that a control has an accessible name is worth more than the simpler type.

Upgrading copied components: cascivo update

Copied source is yours to edit — so upgrading it is a merge, not an overwrite. The machinery is cascivo.lock, written by cascivo add: per component it records the registry it came from, the installed version, and a sha256-… hash of every copied file. Commit it — it is what lets the CLI tell your edits apart from upstream changes.

Check what changed upstream

npx cascivo update --check

Compares the registry's current per-file content hashes against the hashes in cascivo.lock — accurate to the individual source edit, not just version labels. Lists what changed and exits 1 if anything is outdated (CI-friendly); exits 0 with "All components up to date." otherwise.

Apply upstream changes

npx cascivo update button

Runs a three-way merge: the CLI fetches the base version recorded in the lockfile (from the registry's versioned snapshots, r/<name>@<version>.json), your local copy, and the current upstream, then merges upstream changes around your local edits. Three outcomes per file:

<<<<<<< local
your edit
=======
upstream change
>>>>>>> upstream

You are shown the per-file summary and asked to confirm before anything is written; on apply, the lock entry is bumped to the new version (and flagged conflicted until you resolve). With --yes, files that merged cleanly are written and conflicted ones are skipped — nothing lands half-merged in CI.

Two fallbacks: if the base snapshot can't be fetched, or the component predates the lockfile, update degrades to a two-way diff (shows upstream vs. local, overwrite on confirm — your edits are not preserved automatically, so review the printed diff).


Version notes

Release-specific upgrade guides, newest first:

See also

As Markdown: /docs/upgrading.md

← All guides