Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .changeset/theme-inert-token-scales-removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
"@objectstack/spec": major
---

**BREAKING (theme):** retire the nine theme token groups that were emitted and read by nobody (#5021, ADR-0049 enforce-or-remove).

`ThemeSchema` declared a full design-token vocabulary — a type scale, a weight
scale, line-height and letter-spacing scales, a motion scale and a z-index scale.
objectui's theme engine turned every one of them into CSS custom properties,
faithfully and for years. What never existed was a **reader**: measured against
objectui `main` on 2026-08-04, `--font-size-*`, `--font-weight-*`,
`--line-height-*`, `--letter-spacing-*`, `--duration-*`, `--timing-*`, `--z-*`,
`--font-heading` and `--font-mono` have **zero** consumers across objectui's
components and stylesheets, while `--font-sans`, `--radius*`, `--shadow*` and the
colour variables come back live in the same run. So a declared type scale was
real CSS that styled nothing, and an overlay you "lifted" with `zIndex` still
stacked by document order.

This is why the earlier theme sweep (#3494) left them standing: its criterion was
*"the engine never emits it"*, and these are emitted. ADR-0049's criterion —
emitted, but consumed by nobody — is what reaches them.

FROM → TO:

| Removed | Replace with |
|---|---|
| `theme.typography.fontSize` | `theme.customVars: { "font-size-lg": "1.125rem" }` |
| `theme.typography.fontWeight` | `theme.customVars: { "font-weight-semibold": "600" }` |
| `theme.typography.lineHeight` | `theme.customVars: { "line-height-relaxed": "1.75" }` |
| `theme.typography.letterSpacing` | `theme.customVars: { "letter-spacing-wide": "0.025em" }` |
| `theme.typography.fontFamily.heading` | `theme.customVars: { "font-heading": "Georgia, serif" }` |
| `theme.typography.fontFamily.mono` | `theme.customVars: { "font-mono": "ui-monospace, monospace" }` |
| `theme.animation` | `theme.customVars: { "duration-fast": "150ms", "timing-ease": "ease" }` |
| `theme.zIndex` | `theme.customVars: { "z-modal": "1050" }` |

The one-line fix: **delete the key; re-declare under `customVars` only the
variables your own stylesheets actually read.** `customVars` emits each entry
verbatim as `--<key>: <value>`, so every retired variable is reproducible byte
for byte — no capability is lost. Run `os migrate meta --from 16` to strip the
keys automatically; it emits one notice per key so you can see what you were
declaring before deciding what to keep.

`colors`, `borderRadius`, `shadows` and `typography.fontFamily.base` have live
consumers and are **unchanged**.

The retirement kit:

- **Schema** — each key is a `retiredKey()` tombstone, so authoring one is both
a `tsc` error (the input type is `never`) and a parse error carrying the
prescription above. `AnimationSchema` and `ZIndexSchema` were deleted outright
along with the `Animation` / `ZIndex` types: each had exactly one consumer —
the key now tombstoned — and an exported schema with no consumer reads as a
capability to whoever finds it (#3950).
- **Aliases** — the five that pointed at `animation`/`zIndex` and the seven that
pointed into the retired typography scales were deleted with their targets
rather than re-pointed. Keeping them would answer an author with *"did you mean
`zIndex`?"* and then reject `zIndex` — a rename into a second rejection.
- **Migration** — `theme-inert-token-scales-removed` (ADR-0087 D2), wired into
the protocol-17 chain step and retired from the load path, so a live parse
rejects loudly and only `os migrate meta` rewrites sources. It **deletes** the
keys rather than auto-populating `customVars`: a rewrite would hand back two
dozen variables that still nothing reads, turning a dead semantic slot into a
dead literal one.
- **Baselines** — `authorable-surface.json` gains eight `[RETIRED]` markers and
loses the ten `ui/Animation:*` / `ui/ZIndex:*` lines under the #4650 deletion
check's whole-def proof; `json-schema.manifest.json` drops the two defs.
52 changes: 11 additions & 41 deletions content/docs/references/ui/theme.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,13 @@ Defines brand colors and their variants.
## TypeScript Usage

```typescript
import { AnimationSchema, BorderRadiusSchema, ColorPaletteSchema, ShadowSchema, ThemeSchema, ThemeModeSchema, TypographySchema, ZIndexSchema } from '@objectstack/spec/ui';
import type { Animation, BorderRadius, ColorPalette, Shadow, Theme, ThemeMode, Typography, ZIndex } from '@objectstack/spec/ui';
import { BorderRadiusSchema, ColorPaletteSchema, ShadowSchema, ThemeSchema, ThemeModeSchema, TypographySchema } from '@objectstack/spec/ui';
import type { BorderRadius, ColorPalette, Shadow, Theme, ThemeMode, Typography } from '@objectstack/spec/ui';

// Validate data
const result = AnimationSchema.parse(data);
const result = BorderRadiusSchema.parse(data);
```

---

## Animation

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **duration** | `{ fast?: string; base?: string; slow?: string }` | optional | |
| **timing** | `{ linear?: string; ease?: string; ease_in?: string; ease_out?: string; … }` | optional | |


---

## BorderRadius
Expand Down Expand Up @@ -111,11 +99,11 @@ const result = AnimationSchema.parse(data);
| **description** | `string` | optional | Theme description |
| **mode** | `Enum<'light' \| 'dark' \| 'auto'>` | ✅ | Theme mode (light, dark, or auto) |
| **colors** | `{ primary: string; secondary?: string; accent?: string; success?: string; … }` | ✅ | Color palette configuration |
| **typography** | `{ fontFamily?: object; fontSize?: object; fontWeight?: object; lineHeight?: object; … }` | optional | Typography settings |
| **typography** | `{ fontFamily?: object; fontSize?: any; fontWeight?: any; lineHeight?: any; … }` | optional | Typography settings |
| **borderRadius** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Border radius scale |
| **shadows** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Box shadow effects |
| **animation** | `{ duration?: object; timing?: object }` | optional | Animation settings |
| **zIndex** | `{ base?: number; dropdown?: number; sticky?: number; fixed?: number; … }` | optional | Z-index scale for layering |
| **animation** | `any` | optional | [REMOVED] `theme.animation` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — unlike the #3494 props above, the engine DID emit `--duration-*` and `--timing-*`, faithfully and for years; what never existed was a reader. No first-party component or stylesheet has ever consumed one, so every transition ran at the renderer default whatever you declared. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "duration-fast": "150ms", "timing-ease_in": "cubic-bezier(0.4, 0, 1, 1)" }` emits exactly the same properties). Run `os migrate meta --from 16` to rewrite it automatically. |
| **zIndex** | `any` | optional | [REMOVED] `theme.zIndex` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — the engine emitted `--z-base` … `--z-tooltip` and nothing read one, so an overlay you "lifted" still stacked by document order. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "z-modal": "1050" }` emits exactly the same `--z-modal`). Run `os migrate meta --from 16` to rewrite it automatically. |
| **customVars** | `Record<string, string>` | optional | Custom CSS variables (key-value pairs) |
| **extends** | `string` | optional | Base theme to extend from |

Expand All @@ -139,29 +127,11 @@ const result = AnimationSchema.parse(data);

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **fontFamily** | `{ base?: string; heading?: string; mono?: string }` | optional | |
| **fontSize** | `{ xs?: string; sm?: string; base?: string; lg?: string; … }` | optional | |
| **fontWeight** | `{ light?: number; normal?: number; medium?: number; semibold?: number; … }` | optional | |
| **lineHeight** | `{ tight?: string; normal?: string; relaxed?: string; loose?: string }` | optional | |
| **letterSpacing** | `{ tighter?: string; tight?: string; normal?: string; wide?: string; … }` | optional | |


---

## ZIndex

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **base** | `number` | optional | Base z-index (e.g., 0) |
| **dropdown** | `number` | optional | Dropdown z-index (e.g., 1000) |
| **sticky** | `number` | optional | Sticky z-index (e.g., 1020) |
| **fixed** | `number` | optional | Fixed z-index (e.g., 1030) |
| **modalBackdrop** | `number` | optional | Modal backdrop z-index (e.g., 1040) |
| **modal** | `number` | optional | Modal z-index (e.g., 1050) |
| **popover** | `number` | optional | Popover z-index (e.g., 1060) |
| **tooltip** | `number` | optional | Tooltip z-index (e.g., 1070) |
| **fontFamily** | `{ base?: string; heading?: any; mono?: any }` | optional | |
| **fontSize** | `any` | optional | [REMOVED] `theme.typography.fontSize` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — the engine emitted `--font-size-xs` … `--font-size-4xl` faithfully and NO first-party component or stylesheet has ever read one, so a declared type scale was real CSS that styled nothing. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "font-size-lg": "1.125rem" }` emits exactly the same `--font-size-lg`). Run `os migrate meta --from 16` to rewrite it automatically. |
| **fontWeight** | `any` | optional | [REMOVED] `theme.typography.fontWeight` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — the engine emitted `--font-weight-*` and nothing read it, so text rendered at the inherited weight whatever you declared. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "font-weight-semibold": "600" }` emits exactly the same `--font-weight-semibold`). Run `os migrate meta --from 16` to rewrite it automatically. |
| **lineHeight** | `any` | optional | [REMOVED] `theme.typography.lineHeight` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — the engine emitted `--line-height-*` and nothing read it, so every block kept its inherited leading. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "line-height-relaxed": "1.75" }` emits exactly the same `--line-height-relaxed`). Run `os migrate meta --from 16` to rewrite it automatically. |
| **letterSpacing** | `any` | optional | [REMOVED] `theme.typography.letterSpacing` was removed in @objectstack/spec 17.0.0 (#5021, ADR-0049 D2) — the engine emitted `--letter-spacing-*` and nothing read it, so tracking never moved. Delete the key; if your own CSS reads those variables, declare them under `customVars` (`{ "letter-spacing-wide": "0.025em" }` emits exactly the same `--letter-spacing-wide`). Run `os migrate meta --from 16` to rewrite it automatically. |


---
Expand Down
14 changes: 7 additions & 7 deletions docs/audits/2026-07-unknown-key-strictness-ledger.counts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ regenerate.
| Measure | Value |
|---|---|
| Triaged directories | 5 |
| Object sites in them | 484 |
| Object sites in them | 476 |
| Still-open (strip) sites | 221 |
| Files carrying at least one | 36 |

Expand All @@ -43,12 +43,12 @@ The `strict` column is the one the campaign schedules against; it counts both th

| Dir | Sites | strict | passthrough | catchall | strip |
|---|---|---|---|---|---|
| `ui/` | 200 | 120 | 5 | 0 | 75 |
| `ui/` | 192 | 112 | 5 | 0 | 75 |
| `data/` | 162 | 54 | 1 | 0 | 107 |
| `automation/` | 75 | 49 | 0 | 0 | 26 |
| `security/` | 20 | 7 | 0 | 0 | 13 |
| `studio/` | 27 | 27 | 0 | 0 | 0 |
| **total** | **484** | **257** | **6** | **0** | **221** |
| **total** | **476** | **249** | **6** | **0** | **221** |

## File-level triage — site counts

Expand Down Expand Up @@ -77,11 +77,11 @@ classify and is not listed (it becomes reportable the day it grows its first sit
| `report.zod.ts` | 3 |
| `responsive.zod.ts` | 4 |
| `sharing.zod.ts` | 2 |
| `theme.zod.ts` | 14 |
| `theme.zod.ts` | 6 |
| `touch.zod.ts` | 7 |
| `view.zod.ts` | 51 |
| `widget.zod.ts` | 9 |
| **total** | **200** |
| **total** | **192** |

### `data/` — sites

Expand Down Expand Up @@ -161,7 +161,7 @@ over it is here.

### `ui/` — open

**75 strip of 200**, in 13 file(s).
**75 strip of 192**, in 13 file(s).

| File | Strip | Sites |
|---|---|---|
Expand All @@ -178,7 +178,7 @@ over it is here.
| `touch.zod.ts` | 7 | 7 |
| `view.zod.ts` | 5 | 51 |
| `widget.zod.ts` | 9 | 9 |
| **total** | **75** | **200** |
| **total** | **75** | **192** |

| Bucket | Sites |
|---|---|
Expand Down
Loading
Loading