Migration
Moving between majors: what the moves to 1.0, 2.0 and 3.0 broke and how to make them.
The library has had three major transitions. From 2.x to 3.0 component values moved into themes: token names are a contract of the core, and the theme supplies the values. From 1.x to 2.0 came themes and modes: the theme became data of the token package, light and dark became its modes, and the build moved to granum 3. From 0.x to 1.0 the pipeline the application builds its CSS with changed. All three moves are breaking even though not a single component import changes in any of them.
From 2.x to 3.0
Components, their props and the look of Slate do not change: the values components see in every mode and island match 2.x. What changes is where these values live and who answers for their completeness.
| In 2.x | In 3.0 |
|---|---|
peer @feugene/granum >=3.8.0 <4.0.0 | >=3.10.0 <4.0.0; the token package @feugene/granularity-tokens is major 2 |
a granum config without themes built the catalog’s default theme | themes is required: the application chooses the main theme, and without it granum fails with the list of catalog themes |
GR_DEFAULT_THEME_ID | GR_BASE_THEME_ID — the package’s base theme, not the main theme of someone’s build |
| component token values lived with the components | the values live in the theme, in its components/@feugene/granularity/<GrX>/ section |
| a component read a theme token with a fallback value | without one: a missing value is named by granum doctor (theme-incomplete) |
--gr-button-neutral-bg was a hook the theme set | a theme token; a hook is the application’s handle, and in theme data it is a theme-token-foreign finding |
theme.json with modes as an array and componentDefaults at the root | modes as an object, component defaults in extensions.granularity.componentDefaults, a mode file holds flat values |
The application sets its own values on top of a theme as before — with
modes.tokenOverrides of the theme entry or its own CSS; a hook stays the application’s
handle on top of the theme token.
One thing breaks silently — a theme of your own without extends that relied on the
components’ fallback values. There is no build error: the component simply does not get
the token, and granum doctor names it. The values go into the theme’s components
section, or the theme inherits a catalog theme; starting from a copy is easiest with
gr-theme-copy slate <id>. A theme with extends: 'slate' works as before and now also
gets the component values from Slate.
The charts and media companions moved to core 3 with 3.0 majors: their values live in
the theme too, and their core range is >=3.1.0 <4.0.0. chrono, code, dashboard,
editor and forms-schema keep no values in the theme and work with both core majors.
The full map of the move is in the
library documentation.
From 1.x to 2.0
The components, their props and the look of Slate do not change: the token values match 1.x except for two fixed defects of the dark island, described in the changelog. What changes is how the theme reaches the build and how the mode is switched.
| In 1.x | In 2.0 |
|---|---|
peer @feugene/granum of major 1 or 2 | >=3.0.0 <4.0.0, releases with build themes need >=3.8.0 <4.0.0; the @feugene/granum-engine-wind engine is major 3 too |
themes: { names: ['light', 'dark'] } | themes: [{ theme: 'slate', modes: ['light', 'dark'] }] — the list of build themes; an object themes fails with InvalidConfigError |
themes.define, themes.tokenOverrides | modes.define, modes.tokenOverrides in the theme’s entry |
data-theme="dark" on the root | the pair data-theme="slate-dark"; there are no .light and .dark classes |
[data-theme='dark'] .x in your own CSS | [data-theme$='-dark'] .x or the full pair; dark: in utilities works unchanged |
useTheme().theme — the mode name | theme — the current build theme; the mode is mode |
setTheme('dark'), toggleTheme() | setMode('dark'), toggleMode(); setTheme(id) switches the build theme |
the ThemeName type | GrModeName, GrThemeId |
<GrConfigProvider theme="dark"> | <GrConfigProvider mode="dark">; theme is an island of another build theme |
@feugene/granularity/theme: extendTheme, createTheme | @feugene/granularity-tokens/theme: extendMode, createMode, defineTheme |
@feugene/granularity/theme/apply | @feugene/granularity-tokens/theme/apply |
grThemeTokens from @feugene/granularity/tokens | grRoleTokens from @feugene/granularity-tokens |
styles/tokens.css, styles/themes/light.css and dark.css | themes/slate.css — both modes of one theme |
The mode manifest is handed over once, where the state is created:
initThemeEarly({ manifest }) or app.use(granularityThemePlugin, { manifest })
with manifest from virtual:granum/themes. The storage key is unchanged —
gr-theme — and for the default theme it still holds the mode name: the user’s
choice survives the upgrade. The token package needs no separate install — it arrives as a dependency
of the core.
One thing breaks silently — an attribute or a selector with a mode name
instead of a pair. data-theme="dark" set by hand and your own CSS under
[data-theme='dark'] give neither an error nor a warning: the components simply
stay in the default mode. theme="dark" on GrConfigProvider at least gets a
warning in a dev build.
The first 2.x releases wrote the theme and the modes as separate keys — theme: 'slate'
and modes: { names: [...] }. Since granum 3.8 that is an entry of the themes list,
and previews and css.previews became additional themes with sheets of their own.
granum rejects the old keys with a config error that says where the value moved.
The companions moved to core 2 with their own 2.x majors on the same day: core 2 cannot be mixed with a companion built for core 1 — the manifests of granum 2 and 3 are incompatible. The full map of the move is in the library documentation, the granum config keys are in its guide (ru, en).
From 0.x to 1.0
| In 0.x | In 1.0 |
|---|---|
peer @feugene/unocss-preset-granular | peer @feugene/granum >=1.0.0 <2.0.0 and the @feugene/granum-engine-wind engine |
uno.config.ts | granum.config.ts; the engine is passed as an instance — engine: windEngine() |
| CSS concatenated in import order | CSS arrives as the virtual:granum.css module in five granum.* layers |
| the browser reset as plain CSS | the reset in a layer: @import '…' layer(reset);, or it overrides the library |
defineGranularComponent, defineGranularProvider | defineGranumComponent, defineGranumProvider from @feugene/granum/contract — for provider authors |
The companions that depend on the core moved to 1.0 on the same day: their core
range is now >=1.0.0 <2.0.0, and it does not match a 0.x core. That is why the
core and the companions are upgraded together. The step-by-step guide is in the
granum documentation
(ru,
en).
Breaking changes within the 0.x line are in the changelog.
What will appear here by the next major
- A map of API changes — what was renamed, what was removed, what changed its default. As a table rather than as prose: prose cannot be searched by a prop name.
- A codemod. Renames are performed by a script rather than by hand: it lives in the repository next to the migration and runs with one command.
- What breaks silently. A separate section for changes that give neither a build error nor a warning — a shifted default, a different resolution order, another ARIA contract. This is the most expensive part of a move and the shortest one in other people’s guides. One such case is already known from 1.0 — a reset outside the layer, see troubleshooting.
- The order of operations for an application that does not upgrade all at once: what can stand side by side, what conflicts, where the compatibility line runs.
- An effort estimate by application size — so that “move now or next quarter” is decided by a number rather than by mood.
Documentation versions
There is one set of documentation, and it describes the 3.x line; the portal keeps no 0.x, 1.x or 2.x archive. The version in the header is read from the installed core’s manifest at build time and is never typed by hand — it matches what the portal is built with.
The details of the version policy are on the versioning page.
Moving from another library is not migration in this sense. The component maps for it are being prepared on a separate page — Migrating from….