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
| Bump | From 1.0.0 onward | Before 1.0 (historical) |
|---|---|---|
| major | may remove or change covered API; every removal was deprecated first | n/a |
| minor | adds API; never removes or changes covered API | may include breaking changes |
| patch | fixes only | fixes 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:
- Exported values and types from every
1.xpackage, on both the@cascivo/reactprebuilt path and the copy-paste path. - Prop contracts — a prop's name, its type, and whether it is required. Widening a type is a minor; narrowing it is a major.
- Design tokens — the
--cascivo-*custom properties, at all three levels (primitive, semantic, component). - Style hooks — the
data-cascivo-*attributes documented inSTYLING-INTERNALS.mdand listed in each component's manifest. - The canonical
@layernames and their order (cascivo.reset→base→tokens→component→theme→blocks→override). - The accessibility contract — each component's ARIA role, its accessible-name
requirement, and its documented keyboard map, as published in the manifest and enforced by
apg:check. - The CLI's command surface —
cascivo init | add | list | update | audit | doctorand their documented flags. registry.json's shape, which the CLI, the MCP server and agent tooling all read.
What the promise does not cover
These may change in any release, including a patch:
- Exact DOM nesting and element choice, beyond the roles and style hooks above.
Components ship CSS Modules with hashed class names —
_navWrapper_1r5fv_83is not a selector you can target, and structural selectors likediv:has(> div > nav[aria-label='Main'])will silently stop matching. Use a style hook; that is what they exist for. - Rendered visual output — spacing, colour and motion may be tuned. Tokens are the stable surface, not the pixels they produce.
- Anything not exported from a package's entry points, including
@cascivo/coreinternals. - Packages still on
0.x— see the table below.
Supported versions
- The current major receives fixes on its latest minor.
- The previous major receives security fixes for six months after the next major
ships. Report security issues per
SECURITY.md. - Older majors are unsupported.
cascivo doctor --driftcompares your installed versions againstbreaking-changes.jsonand tells you where you stand. - Minors are batched (at most one every two weeks) and a major ships only after a four-week
release candidate — the full release policy is in
GOVERNANCE.md.
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.
| Line | Packages |
|---|---|
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
- Per-package
CHANGELOG.md— changesets-generated, the source of truth (e.g.packages/react/CHANGELOG.md). - Root
CHANGELOG.md— a generated index: every published package, its current version, latest feature release, and a link to its changelog. breaking-changes.json— the machine-readable surface, below.
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.
- Both names work. The old name stays as an alias for at least one minor. Your code keeps compiling and behaving identically.
- The old name carries
@deprecatedin its TSDoc, naming the replacement. Your editor strikes it through;tscstays silent. - The deprecation names when it expires. Every deprecation records
since— the version that introduced the replacement — andremoveIn, the major that removes the old name. Both appear in the manifest, and therefore inregistry.json, thellms/*.mdfiles and the docs site, so the expiry is discoverable before you adopt the old name rather than after it disappears. - A changeset records it, so it appears in the CHANGELOG and in
breaking-changes.json— the filecascivo doctor --driftreads. - 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:
- unchanged — your file already matches; nothing written.
- clean — upstream changes applied around your edits automatically.
- conflict — you and upstream touched the same lines; the file gets standard conflict markers to resolve by hand:
<<<<<<< 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:
-
Upgrading
0.x→1.0.0— eleven deprecated surfaces are removed. Every one has a replacement that takes the identical argument, so each fix is a rename, and every removal is a compile error rather than a silent behaviour change.Removed Replace with Affects onChange(value-carrying)onValueChangeCombobox,DatePicker,Filter,NumberInput,Search,Swap,TimePicker,ToggleText,TextPropsChartText,ChartTextProps@cascivo/chartsBarChartxTicks/yTicksvalueAxisTicks/categoryAxisTicks@cascivo/chartsDropdownitemseparator: truea separate { kind: 'separator' }entry@cascivo/reactTwo notes on the shape of these:
- Three of the eight —
Toggle,NumberInput,TimePicker— extend an HTML element's attributes, and they keepOmit<…, 'onChange'>deliberately. Without it the nativeChangeEventHandlerwould take the name back, and a value-carrying handler would compile and then be called with a DOM event — a silent break instead of a loud one. The other five are plain interfaces with no HTML base, soonChangeis simply not a prop. Either way, passing it is a compile error that names the replacement. BarChart's removed pair followed screen position, so its meaning swapped withorientation:yTicks={1}did nothing on a horizontal chart whilexTicks={1}worked. The replacements are named by role and mean the same thing either way.ScatterChartkeepsxTicks/yTicks— both its axes are value axes, so screen naming is correct there.
OverflowMenuis not removed. It is deprecated in favour ofMenuand keeps working for the whole1.xline; its manifest carriesremoveIn: '2.0.0'. - Three of the eight —
-
<Badge>with novariantis no longer the brand colour (@cascivo/reactminor) —neutral(and its aliasdefault, which is what you get when you pass nothing) now renders the subtle look instead of--cascivo-color-accent.Badge was the only one of the four tone-taking components that did this:
Tag,StatusandNotificationall renderneutralquietly, so a single domain enum driving all four produced a primary-blue pill in one place and a grey chip in the others.pnpm tone:checknow enforces that a tone reads from its own token family in every component.Migration: if you were relying on the accent look, ask for it explicitly.
// before — accent, by accident <Badge>Production</Badge> // after — accent, on purpose <Badge variant="primary">Production</Badge>A neutral
<Tag>also gains a 1px border, so it reads as a chip rather than as loose text on dark themes. No API change. -
The theme runtime moved to
@cascivo/core(@cascivo/coreminor,@cascivo/reactunchanged) —ThemeProvider,useTheme,setTheme,themeSignal,applyThemeandthemePreloadScriptare now exported from@cascivo/core, so the copy-paste path can reach them without installing the whole prebuilt component distribution.No migration needed.
@cascivo/reactre-exports all of them under the same names.persistedSignaland the synchronous drivers moved the same way and are still re-exported from@cascivo/storage; the persisted value format is unchanged, so stored themes survive the upgrade. -
useTheme()returns a string, not a signal (@cascivo/reactminor) — the first tuple element ofuseTheme()is now the theme name (a plainstring) instead of aSignal<string>. Migration: drop.value.// before const [theme, setTheme] = useTheme() theme.value === 'dark' // after const [theme, setTheme] = useTheme() theme === 'dark'TypeScript flags every site (
.valueon astringis an error). If you passed the signal itself intocomputed()/effect()or a Preact component, get it from the newthemeSignal()export instead. The setter is unchanged. -
@cascivo/themesnow installs with@cascivo/react(@cascivo/reactminor) — it moved from a peer/optional install to a real dependency, sopnpm add @cascivo/reactbrings it along. You still import a theme CSS file once (or import the now-self-contained@cascivo/react/styles.css, which bundles tokens + light + dark); skipping the import renders components grayscale, andThemeProvidernow warns about exactly that in dev. No action needed unless you were installing@cascivo/themesseparately — you can drop it from yourpackage.json. -
cascivo.blockslayer slot (@cascivo/tokensminor) — the canonical@layerorder gained a declaredcascivo.blocksslot betweencascivo.themeandcascivo.override, and the@functionhelpers moved from an undeclaredcascivo.functionslayer intocascivo.tokens. Shipped blocks and functions now sit belowcascivo.overrideinstead of silently above it. If you relied on a block's CSS beating your@layer cascivo.override { … }rules, that was a bug — your override now wins as documented. See CSS-LAYERS-PITFALL.md. -
v37 — Consumer Upgrade Guide — migration hardening: the
cascade.*→cascivo.*@layerrename (the one breaking change),styles.cssexport fix, app-shell adoption, token-name stability.
See also
- GETTING-STARTED.md — the two install paths.
- TROUBLESHOOTING.md — "a component looks different than the docs" is usually version drift.
- RELEASING.md — how releases are cut (maintainers).
As Markdown: /docs/upgrading.md