Skip to content

Commit f8cfbb4

Browse files
os-zhuangclaude
andauthored
feat(spec)!: 退役主题引擎发出但零消费方的 9 组 token (#5021) (#5289)
`ThemeSchema` 声明了一整套设计 token 词表——字号/字重/行高/字距四条排版 标尺、动效标尺、z-index 标尺,以及 `fontFamily.heading`/`.mono`。objectui 的 主题引擎多年来忠实地把它们全部降成 CSS 自定义属性。缺的从来不是"发出", 而是"读取":2026-08-04 对 objectui `main` 复测,`--font-size-*` / `--font-weight-*` / `--line-height-*` / `--letter-spacing-*` / `--duration-*` / `--timing-*` / `--z-*` / `--font-heading` / `--font-mono` 在 `packages/**` 里 消费方为 **0**,而同一轮里 `--font-sans` / `--radius*` / `--shadow*` / `--primary` 作为阳性对照全部命中。所以作者写下的排版标尺是真实的 CSS, 却什么也没有渲染。 这正是 #3494 那轮够不着它们的原因:上一轮的判据是"引擎从不发出", 而这批**引擎确实发出**。够得着它们的是 ADR-0049 的判据——发出了,但没人读。 按 spec-property-retirement 套件退役,墓碑处方统一指向 `customVars`: 引擎把 `customVars` 每一项原样发成 `--<key>: <value>`,因此任何真在读 `--z-modal` 的租户样式表都能**逐字节复现**同名变量,能力无损。 - **schema**:8 个键改为 `retiredKey()` 墓碑——写它既是 `tsc` 错误(输入 类型为 `never`),也是携带处方的 parse 错误。`AnimationSchema` / `ZIndexSchema` 及其类型整体删除:各自唯一的消费方就是刚被墓碑化的那个键, 而一个没有消费方的导出 schema 会被后来者读成一项能力(#3950)。 ⚠️ 走墓碑而非 `guidance` 是被门测出来的,不是风格选择:`guidance` 依赖 `unrecognized_keys`,要求键从 shape 里消失,而那会从 `authorable-surface.json` 删掉一条 LIVE 基线,`gen:schema` 的 #4650 删除检查会直接拒绝。 - **别名**:指向退役键的 5 个(`animations`/`motion`/`transitions` → `animation`,`layers`/`stacking` → `zIndex`)与指向退役排版标尺的 7 个 **随目标一起删除**,不做改指——留着会先答"你是不是想写 `zIndex`?" 再拒绝 `zIndex`,正是台账 finding 7 的形状。 - **迁移**:`theme-inert-token-scales-removed`(ADR-0087 D2),挂进 protocol-17 链步骤并 `retiredFromLoadPath`。它**删键 + 每键一条 notice**, 不自动改写进 `customVars`:改写会塞回二十多个仍然没人读的变量,把一个 死的语义槽位变成一个死的字面槽位,退役反而隐形。 - **存量实测**:`examples/**` 与 `apps/**` 里 authored 这些块的实例数为 **0** (showcase 两个主题只写 `colors`),所以没有需要 D2 转写的在仓配置。 `colors` / `borderRadius` / `shadows` / `typography.fontFamily.base` 有活 消费方,**不动**。 Fixes #5021 Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7cf1531 commit f8cfbb4

13 files changed

Lines changed: 753 additions & 398 deletions
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
**BREAKING (theme):** retire the nine theme token groups that were emitted and read by nobody (#5021, ADR-0049 enforce-or-remove).
6+
7+
`ThemeSchema` declared a full design-token vocabulary — a type scale, a weight
8+
scale, line-height and letter-spacing scales, a motion scale and a z-index scale.
9+
objectui's theme engine turned every one of them into CSS custom properties,
10+
faithfully and for years. What never existed was a **reader**: measured against
11+
objectui `main` on 2026-08-04, `--font-size-*`, `--font-weight-*`,
12+
`--line-height-*`, `--letter-spacing-*`, `--duration-*`, `--timing-*`, `--z-*`,
13+
`--font-heading` and `--font-mono` have **zero** consumers across objectui's
14+
components and stylesheets, while `--font-sans`, `--radius*`, `--shadow*` and the
15+
colour variables come back live in the same run. So a declared type scale was
16+
real CSS that styled nothing, and an overlay you "lifted" with `zIndex` still
17+
stacked by document order.
18+
19+
This is why the earlier theme sweep (#3494) left them standing: its criterion was
20+
*"the engine never emits it"*, and these are emitted. ADR-0049's criterion —
21+
emitted, but consumed by nobody — is what reaches them.
22+
23+
FROM → TO:
24+
25+
| Removed | Replace with |
26+
|---|---|
27+
| `theme.typography.fontSize` | `theme.customVars: { "font-size-lg": "1.125rem" }` |
28+
| `theme.typography.fontWeight` | `theme.customVars: { "font-weight-semibold": "600" }` |
29+
| `theme.typography.lineHeight` | `theme.customVars: { "line-height-relaxed": "1.75" }` |
30+
| `theme.typography.letterSpacing` | `theme.customVars: { "letter-spacing-wide": "0.025em" }` |
31+
| `theme.typography.fontFamily.heading` | `theme.customVars: { "font-heading": "Georgia, serif" }` |
32+
| `theme.typography.fontFamily.mono` | `theme.customVars: { "font-mono": "ui-monospace, monospace" }` |
33+
| `theme.animation` | `theme.customVars: { "duration-fast": "150ms", "timing-ease": "ease" }` |
34+
| `theme.zIndex` | `theme.customVars: { "z-modal": "1050" }` |
35+
36+
The one-line fix: **delete the key; re-declare under `customVars` only the
37+
variables your own stylesheets actually read.** `customVars` emits each entry
38+
verbatim as `--<key>: <value>`, so every retired variable is reproducible byte
39+
for byte — no capability is lost. Run `os migrate meta --from 16` to strip the
40+
keys automatically; it emits one notice per key so you can see what you were
41+
declaring before deciding what to keep.
42+
43+
`colors`, `borderRadius`, `shadows` and `typography.fontFamily.base` have live
44+
consumers and are **unchanged**.
45+
46+
The retirement kit:
47+
48+
- **Schema** — each key is a `retiredKey()` tombstone, so authoring one is both
49+
a `tsc` error (the input type is `never`) and a parse error carrying the
50+
prescription above. `AnimationSchema` and `ZIndexSchema` were deleted outright
51+
along with the `Animation` / `ZIndex` types: each had exactly one consumer —
52+
the key now tombstoned — and an exported schema with no consumer reads as a
53+
capability to whoever finds it (#3950).
54+
- **Aliases** — the five that pointed at `animation`/`zIndex` and the seven that
55+
pointed into the retired typography scales were deleted with their targets
56+
rather than re-pointed. Keeping them would answer an author with *"did you mean
57+
`zIndex`?"* and then reject `zIndex` — a rename into a second rejection.
58+
- **Migration**`theme-inert-token-scales-removed` (ADR-0087 D2), wired into
59+
the protocol-17 chain step and retired from the load path, so a live parse
60+
rejects loudly and only `os migrate meta` rewrites sources. It **deletes** the
61+
keys rather than auto-populating `customVars`: a rewrite would hand back two
62+
dozen variables that still nothing reads, turning a dead semantic slot into a
63+
dead literal one.
64+
- **Baselines**`authorable-surface.json` gains eight `[RETIRED]` markers and
65+
loses the ten `ui/Animation:*` / `ui/ZIndex:*` lines under the #4650 deletion
66+
check's whole-def proof; `json-schema.manifest.json` drops the two defs.

content/docs/references/ui/theme.mdx

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,13 @@ Defines brand colors and their variants.
1616
## TypeScript Usage
1717

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

2222
// Validate data
23-
const result = AnimationSchema.parse(data);
23+
const result = BorderRadiusSchema.parse(data);
2424
```
2525

26-
---
27-
28-
## Animation
29-
30-
### Properties
31-
32-
| Property | Type | Required | Description |
33-
| :--- | :--- | :--- | :--- |
34-
| **duration** | `{ fast?: string; base?: string; slow?: string }` | optional | |
35-
| **timing** | `{ linear?: string; ease?: string; ease_in?: string; ease_out?: string; … }` | optional | |
36-
37-
3826
---
3927

4028
## BorderRadius
@@ -111,11 +99,11 @@ const result = AnimationSchema.parse(data);
11199
| **description** | `string` | optional | Theme description |
112100
| **mode** | `Enum<'light' \| 'dark' \| 'auto'>` || Theme mode (light, dark, or auto) |
113101
| **colors** | `{ primary: string; secondary?: string; accent?: string; success?: string; … }` || Color palette configuration |
114-
| **typography** | `{ fontFamily?: object; fontSize?: object; fontWeight?: object; lineHeight?: object; … }` | optional | Typography settings |
102+
| **typography** | `{ fontFamily?: object; fontSize?: any; fontWeight?: any; lineHeight?: any; … }` | optional | Typography settings |
115103
| **borderRadius** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Border radius scale |
116104
| **shadows** | `{ none?: string; sm?: string; base?: string; md?: string; … }` | optional | Box shadow effects |
117-
| **animation** | `{ duration?: object; timing?: object }` | optional | Animation settings |
118-
| **zIndex** | `{ base?: number; dropdown?: number; sticky?: number; fixed?: number; … }` | optional | Z-index scale for layering |
105+
| **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. |
106+
| **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. |
119107
| **customVars** | `Record<string, string>` | optional | Custom CSS variables (key-value pairs) |
120108
| **extends** | `string` | optional | Base theme to extend from |
121109

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

140128
| Property | Type | Required | Description |
141129
| :--- | :--- | :--- | :--- |
142-
| **fontFamily** | `{ base?: string; heading?: string; mono?: string }` | optional | |
143-
| **fontSize** | `{ xs?: string; sm?: string; base?: string; lg?: string; … }` | optional | |
144-
| **fontWeight** | `{ light?: number; normal?: number; medium?: number; semibold?: number; … }` | optional | |
145-
| **lineHeight** | `{ tight?: string; normal?: string; relaxed?: string; loose?: string }` | optional | |
146-
| **letterSpacing** | `{ tighter?: string; tight?: string; normal?: string; wide?: string; … }` | optional | |
147-
148-
149-
---
150-
151-
## ZIndex
152-
153-
### Properties
154-
155-
| Property | Type | Required | Description |
156-
| :--- | :--- | :--- | :--- |
157-
| **base** | `number` | optional | Base z-index (e.g., 0) |
158-
| **dropdown** | `number` | optional | Dropdown z-index (e.g., 1000) |
159-
| **sticky** | `number` | optional | Sticky z-index (e.g., 1020) |
160-
| **fixed** | `number` | optional | Fixed z-index (e.g., 1030) |
161-
| **modalBackdrop** | `number` | optional | Modal backdrop z-index (e.g., 1040) |
162-
| **modal** | `number` | optional | Modal z-index (e.g., 1050) |
163-
| **popover** | `number` | optional | Popover z-index (e.g., 1060) |
164-
| **tooltip** | `number` | optional | Tooltip z-index (e.g., 1070) |
130+
| **fontFamily** | `{ base?: string; heading?: any; mono?: any }` | optional | |
131+
| **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. |
132+
| **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. |
133+
| **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. |
134+
| **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. |
165135

166136

167137
---

docs/audits/2026-07-unknown-key-strictness-ledger.counts.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ regenerate.
2121
| Measure | Value |
2222
|---|---|
2323
| Triaged directories | 5 |
24-
| Object sites in them | 484 |
24+
| Object sites in them | 476 |
2525
| Still-open (strip) sites | 221 |
2626
| Files carrying at least one | 36 |
2727

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

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

5353
## File-level triage — site counts
5454

@@ -77,11 +77,11 @@ classify and is not listed (it becomes reportable the day it grows its first sit
7777
| `report.zod.ts` | 3 |
7878
| `responsive.zod.ts` | 4 |
7979
| `sharing.zod.ts` | 2 |
80-
| `theme.zod.ts` | 14 |
80+
| `theme.zod.ts` | 6 |
8181
| `touch.zod.ts` | 7 |
8282
| `view.zod.ts` | 51 |
8383
| `widget.zod.ts` | 9 |
84-
| **total** | **200** |
84+
| **total** | **192** |
8585

8686
### `data/` — sites
8787

@@ -161,7 +161,7 @@ over it is here.
161161

162162
### `ui/` — open
163163

164-
**75 strip of 200**, in 13 file(s).
164+
**75 strip of 192**, in 13 file(s).
165165

166166
| File | Strip | Sites |
167167
|---|---|---|
@@ -178,7 +178,7 @@ over it is here.
178178
| `touch.zod.ts` | 7 | 7 |
179179
| `view.zod.ts` | 5 | 51 |
180180
| `widget.zod.ts` | 9 | 9 |
181-
| **total** | **75** | **200** |
181+
| **total** | **75** | **192** |
182182

183183
| Bucket | Sites |
184184
|---|---|

0 commit comments

Comments
 (0)