# Versioning

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

- Page: https://granularity.tech/docs/versioning
- Last reviewed: 2026-10-27

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](https://semver.org/),
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. The first major under that rule is 2.0: themes and
modes changed the build config and the switching API. The second is 3.0: component
token values moved into themes, and `themes` in the granum config became required.
Both moves are covered in [migration](/docs/migration).

:::callout{type="info"}
From major 1 on, `^` and `~` differ. `^3.0.0` expands to `>=3.0.0 <4.0.0` and accepts
new minors; `~3.0.0` expands to `>=3.0.0 <3.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, `@feugene/granularity-datasource` is still
on 0.x, and the token package `@feugene/granularity-tokens` started its own
numbering at 1.0 together with core 2.0 and moved to 2.0 together with core 3.0. 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:

:::steps
1. **Every package has its own changelog.** There is deliberately no shared
   changelog at the root — [the changelogs on this portal](/changelog) 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-v2.0.0`.
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:

| Package | Core `@feugene/granularity` | Pipeline `@feugene/granum` | Vue |
|---|---|---|---|
| `@feugene/granularity-charts` | `>=3.1.0 <4.0.0` | `>=3.11.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-chrono` | `>=3.5.0 <4.0.0` | `>=3.10.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-code` | `>=3.5.0 <4.0.0` | `>=3.10.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-dashboard` | `>=3.1.0 <4.0.0` | `>=3.11.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-datasource` | — | — | `^3.5.0` |
| `@feugene/granularity-devtools` | `>=2.4.0 <4.0.0` | `>=3.8.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-editor` | `>=3.5.0 <4.0.0` | `>=3.10.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-forms-schema` | `>=3.1.0 <4.0.0` | `>=3.10.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-media` | `>=3.1.0 <4.0.0` | `>=3.10.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-test-kit` | — | `>=3.0.0 <4.0.0` | `^3.5.0` |
| `@feugene/granularity-tokens` | — | `>=3.10.0 <4.0.0` | — |
| `@feugene/unplugin-granularity` | `>=1.0.0 <4.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 `granularity-tokens` is the
token dictionary and the themes — the core depends on it, not the other way round.

The companions with components keep their values in the theme — the colour, the rules
and the face are decided by the theme rather than by the package code. That is why core
2.x does not suit them: the core brings the theme catalogue from 3.1 on, through the token
package — `charts`, `dashboard`, `forms-schema` and `media` have the core range
`>=3.1.0 <4.0.0`. `chrono`, `code` and `editor` draw the field with the shared frame
sheet of the core and stand on `>=3.5.0 <4.0.0`. Core 2.x is left only to `devtools`
(`>=2.4.0`), which reads the state of components rather than drawing them, and to
`unplugin-granularity`: it resolves components without generating a single rule —
`>=1.0.0 <4.0.0`.

The pipeline column moves on its own major: every package is built with granum 3,
and the lower bound is the mechanism the package stands on: component values in the
theme need granum 3.10, a theme section for a group of components — 3.11. They cannot
be mixed with a companion built for core 1: the manifests of granum 2 and 3 are
incompatible, and the config with the `themes` list the documentation relies on is
understood by granum 3.8 and later only.

:::callout{type="warning"}
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

| What | Version | Where from |
|---|---|---|
| Node | `>=22` | `engines` in every package of the family |
| Vue | `^3.5.0` | a peer dependency |
| Module format | ESM 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 3.x line. There is no archive of earlier versions and no switcher:
the 0.x, 1.x and 2.x pages are not kept on the portal, and moving off them is covered
in [migration](/docs/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.** Whether the 2.x line receives fixes after 3.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:

:::steps
1. **Pin the minor if you need predictability:** `~3.2.0` rather than `^3.2.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](/changelog) 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.
