Versioning

  • Upgrades and support
  • 6 min
  • 1 snippet

What 1.x promises, how the packages of the family are tied together by versions, and what the library does not promise yet.

Reviewed 2026-10-16

On this page

An adopter’s question is not “what is the current version” but “how long will this version live”. The honest answer today: no support window is declared. Below is what exists instead, which of it is checkable, and what to do while there is no promise.

Since 1.0

The library numbers its releases by Semantic Versioning, and since 1.0 the public surface — the components, their subpaths, the tokens and the registries — breaks only in a major. On 0.x it was different: a minor release could break compatibility, and did. Version 0.41.0 moved GrCodeBlock out of the core into @feugene/granularity-code, and the @feugene/granularity/components/GrCodeBlock subpath disappeared:

diff
- import { GrCodeBlock } from '@feugene/granularity'
+ import { GrCodeBlock } from '@feugene/granularity-code/components/GrCodeBlock'

The reason is named in the changelog and it is about 1.0: taking something out of a frozen core costs a major, so moves of that kind were made before the freeze rather than after it.

On major 1, ^ and ~ differ. ^1.0.0 expands to >=1.0.0 <2.0.0 and accepts new minors; ~1.0.0 expands to >=1.0.0 <1.1.0. On a zero major — @feugene/granularity-datasource 0.1.x and the @feugene/fint-i18n peer — ^ still means the same as ~.

Every package is versioned on its own

The family has no shared version, and that is not an oversight. The core and the companions run on different cycles: the core ships patch after patch, a companion may sit on its version for weeks, and @feugene/granularity-datasource is still on 0.x. An extra companion release for the sake of a core release would mean an update with not a single change behind it.

Three things follow, and all three are visible in the repository:

  1. Every package has its own changelog. There is deliberately no shared changelog at the root — the changelogs on this portal are assembled from them.
  2. Every package has its own tag. The core is released with a vX.Y.Z tag, a companion with <directory-name>-vX.Y.Z, for instance granularity-charts-v1.0.4.
  3. Publishing happens from the tag. Pushing a tag triggers publication to npm with --provenance: the package on npm has traceable provenance, and your scanner verifies it rather than us. The GitHub Packages mirror is published under the @efureev scope and without provenance.

The compatibility matrix

A companion declares which version of the core and of the build pipeline it works with — not in words but as a range in peerDependencies. Your package manager reads it by itself and speaks up about an incompatible pair: npm with a warning, pnpm and Yarn Berry with strict settings by refusing to install. Here is the same range in readable form:

PackageCore @feugene/granularityPipeline @feugene/granumVue
@feugene/granularity-charts>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-chrono>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-code>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-dashboard>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-datasource——^3.5.0
@feugene/granularity-devtools>=1.0.0 <2.0.0—^3.5.0
@feugene/granularity-editor>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-forms-schema>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-media>=1.0.0 <2.0.0>=2.0.0 <3.0.0^3.5.0
@feugene/granularity-test-kit—>=2.0.0 <3.0.0^3.5.0
@feugene/unplugin-granularity>=1.0.0 <2.0.0——

The table covers the core, the pipeline and Vue. The other peer dependencies — for instance the optional @feugene/fint-i18n >=0.9.0 <1.0.0 and @feugene/unplugin-granularity, @feugene/granularity-chrono for forms-schema, the editor and schema libraries of their companions — are in the package’s package.json. For test-kit both the pipeline and Vue are optional.

A dash means the package does not declare that dependency at all: granularity-datasource knows about neither the core nor the pipeline — it is about data rather than markup — test-kit works with any core, because it checks CSS and the DOM rather than importing components, and unplugin-granularity resolves components without generating a single rule.

The family reached 1.0 together, and the ranges say so: the lower bound is >=1.0.0, the upper <2.0.0. Before that the bound was <1.0.0 and meant “beyond that there are no promises” — 1.0 has redefined the contract, and the ranges were rewritten with it, as promised.

The pipeline column moves on its own major: since the 1.1.0 release the companions are built with granum 2 and declare >=2.0.0 <3.0.0 — granum 1 does not carry out the theming recipes and the ban on root imports that the documentation relies on.

The table is typed by hand but verified against the manifests in the portal’s CI: a core or Vue range that has diverged from a companion’s package.json fails the run. The pipeline column is not verified by the gate yet. And the check runs against the tag the library submodule is pinned to — not against what sits in npm this very second. The source of truth is the peerDependencies of the installed package.

Environment requirements

WhatVersionWhere from
Node>=22engines in every package of the family
Vue^3.5.0a peer dependency
Module formatESM only"type": "module", there is no CommonJS build

Those three lines are part of compatibility too, and they change by the same rules: raising the lower bound of Node is a breaking change, and since 1.0 it arrives only in a major.

Documentation for one version

The documentation describes one version — the one the portal is pinned to, currently the 1.x line. There is no /v0/ archive and no version switcher: the 0.x pages are not kept on the portal, and moving off them is covered in migration.

The version in the header is read from the installed core’s manifest at build time and is never typed by hand.

What the library does not promise yet

This section exists because silence here reads as a promise. Not one of the following exists today:

  • the lifetime of a version. How long the 1.x line will receive fixes after 2.0, and how long a minor lives after the next one, is not declared;
  • a deprecation period. A subpath may disappear in the same release in which its replacement appears: that is what happened with GrCodeBlock;
  • backports of security fixes into previous minors;
  • since and deprecated metadata on components. A component page does not say which version a prop appeared in: the library does not carry that data yet, and the portal will not invent it.

What to do instead:

  1. Pin the minor if you need predictability: ~1.0.0 rather than ^1.0.0 — see the callout above — and check that package.json holds a range rather than latest.
  2. Read the changelog of the package you are raising. Not a shared one: every package has its own, and a breaking change is described there with a diff.
  3. Raise them one at a time. A companion and the core are tied by a range rather than by a shared version: updating the core does not require updating the companions as long as the range still holds.

Where to see the changes

The changelogs on this portal are assembled from the changelogs of the packages in the library repository — one per package, with diffs and explanations. It is the same text as in the repository: the portal does not retell it.