`@asheeui/core` used to be the framework's engine: class dictionaries, the class merge, per-component
fallbacks, the compatibility matrix and the contracts all lived there, and both renderers reached
in. That made core depend on pixels it could not compile, and it made a class string a shared
contract.

Core is the **contract** now. It holds the tokens, the rules, the types and the configuration
pipeline; the renderers own their class strings and the values only they compile. This is the
consumer-facing migration for that move. `CORE-MIGRATION-REPORT.md` is the record; this page is what
changes in your imports.

## The rule

A class string is a decision about pixels, and the two platforms' pixels are not the same one. So:

- the renderer that compiles a class string publishes it;
- core publishes the tokens, the rules and the types both platforms name;
- the 21 strings both renderers compile are the documented exception: each renderer states them in
  its own `portable.ts`, and `packages/core/src/shared/style-parity.test.ts` fails when a pair stops
  matching.

A contributor's one-line test for the next component: *would both platforms render this exact
string?* If no, it belongs to a renderer. If yes, it belongs to core only if a script or both
renderers read it.

## Where the names went

| Name (example) | Before | Now |
| --- | --- | --- |
| `BUTTON_SIZE_CLASS`, `ALERT_ICON_CLASS`, every web `*_CLASS` | `@asheeui/core` | `@asheeui/web` — `packages/web/src/styles/<component>.ts` |
| `NATIVE_BUTTON_SIZE_CLASS`, every native `*_CLASS` | `@asheeui/core` | `@asheeui/native` — `packages/native/src/styles/<component>.ts` |
| `SPACE_PADDING_Y_CLASS` and the other paired strings | `@asheeui/core` | each renderer's `portable.ts`, asserted equal |
| `cn` | `@asheeui/core` | `@asheeui/web` (Tailwind merge) and `@asheeui/native` (the platform's merge) |
| `FALLBACK_BUTTON_CONFIG`, `defaultButtonConfig`, every per-component fallback/default | `@asheeui/core` | the renderer that states the values; native keeps the shared *names* with the platform's values |
| `COMPONENT_SUPPORT`, `findComponentSupport`, `PlatformSupport`, `PLATFORM_SUPPORT_LABEL` | `@asheeui/core` | `@asheeui/core/dev` |
| `FieldConfig`, `Size`, `Radius`, `Space`, `Variant`, `Color`, `ThemeName`, `ExternalConfig`, the typography role table, the cascade, `selectTheme` | `@asheeui/core` | unchanged |

The class constant keeps its name on the side it moved to. A web component imports
`BUTTON_SIZE_CLASS` from `@asheeui/web`; a native one imports `NATIVE_BUTTON_SIZE_CLASS` from
`@asheeui/native`. Neither package imports the other's.

## The ten deletions

These had no reader in the framework, its tests, the docs or any package. They are gone, not
aliased:

| Name | Why |
| --- | --- |
| `TextContract`, `ButtonContract`, `CardContract`, `StackContract`, `BadgeContract` | described props the components already declare; a second statement can disagree with the shipped component |
| `_ColorConfigGuards` | a type-level helper left behind |
| `FontSize`, `FontWeight`, `Elevation` | aliases; the `*_STEP` maps and `ELEVATION_LEVEL` are the values |
| `NATIVE_STEPPER_LABEL_CLASS` | a native class constant with no native reader |

`contracts.ts` itself was retired in the same work. To be precise: the five `*Contract` types above
were deleted when C4 found no reader, and B19 then retired the module. `Substitution` and
`LinkSubstitution` — the two types the renderers still read — live in
`packages/core/src/shared/substitution.ts` and are exported from `@asheeui/core` (and `asheeui`),
while `ThemedContract` and `InputContract` were deleted with the module. The module's prose now
documents `PORTABLE_API`.

## The matrix moved to a subpath

```ts
// before
import { COMPONENT_SUPPORT } from "@asheeui/core";

// now — data only tooling reads
import { COMPONENT_SUPPORT } from "@asheeui/core/dev";
```

The root entry no longer reaches the matrix, which is what stops an application bundle carrying the
inventory. The docs generator, the parity guard and the CLI read `@asheeui/core/dev`.

## What did **not** move

- The token scales: `SPACING_STEP`, `RADIUS_STEP`, `FONT_SIZE_STEP`, `FONT_WEIGHT_STEP`,
  `COLOR_ROLE`, `VARIANT`, `SIZE`, `MIN_TOUCH_TARGET`, `ELEVATION_LEVEL`.
- The vocabulary: `DeepPartial`, `ComponentsConfig`, `ConfigIssue`, `validateConfig`,
  `PaletteRole`, `AnnouncementRole`, `MessageVariant`, `ColumnSortDirection`, `RowCountLabelValues`.
- The rules both platforms answer the same way: `isFieldInvalid`, `resolveFieldStatusColor`,
  `UnderlineRadius`, `selectTheme`, the cascade resolvers (`mergeObject`, `resolveCascade`,
  `resolveClassKey`, `resolveConfigCascade`, `resolveRadiusKey`, `resolveGridColumns`).
- The shared types and the component configuration sections.

`@asheeui/core` still declares **no dependencies** and publishes **no class string**.
`pnpm check:core-charter` is the guard: no class string, no dependency, no renderer import, and no
export without a reader.

## Finding a moved name

```bash
node scripts/audit/core-usage.mjs             # the per-file usage class of every core export
node scripts/audit/core-usage.mjs --json      # per-export rows, for a codemod
```

Then import it from the renderer the row names. A name that does not appear in either renderer's
entry was one of the ten deletions, or an internal that was never public.

## Checking the migration

```bash
pnpm build
pnpm check:core-charter      # 4/4, no class string and no unread export
pnpm check:umbrella          # the portable surface still sees the names it needs
pnpm verify:distribution     # the published core has no dependencies and resolves
```

The shrink is a **major** release of `@asheeui/core`. The changelog groups the moves; this guide is
the index.
