You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(spec): declare record:details section headerColor as a closed six-token enum (#12616)
* feat(spec): declare record:details section headerColor as a closed six-token enum
The #11661 refusal's recorded reason (template-literal Tailwind read
generating no CSS) was repaired by objectui#6294's literal-class lookup;
maintainer ruling A (2026-08-26) declares the key as z.enum over
exactly the six tokens that lookup ships: muted, muted/50, accent,
primary/10, secondary/10, destructive/10. Declared = enforced — values
outside the enum (including bg-* pass-through spellings) are refused at
authoring time instead of shipping a header that silently does not paint.
Optional, no schema default; the title withhold stands and its pin flips
consciously, as its own comment anticipated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xGvxcwPRTJfA7RfjXEYA4
* docs(spec): regenerate ui/component reference for the headerColor enum
gen:docs wrote this page; gen:schema produced no tracked-file changes
(authorable-surface.base.json lags by design — baseRev allowance).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xGvxcwPRTJfA7RfjXEYA4
---------
Co-authored-by: Claude <noreply@anthropic.com>
Declare `headerColor` on the strict `record:details` section schema as a closed enum (#12126, maintainer ruling A 2026-08-26). The key was deliberately refused by #11661 because the renderer's only read was a template-literal Tailwind class that generated no CSS; objectui#6294 (merged 2026-08-25) replaced that read with a lookup of complete class literals, so the refusal outlived its recorded reason. The vocabulary is exactly the six tokens that lookup ships — `muted` | `muted/50` | `accent` | `primary/10` | `secondary/10` | `destructive/10` — tints only (`CardHeader` sets no foreground, so solids would need a paired `text-*-foreground`). Declared = enforced: `objectstack validate` refuses anything outside the enum at authoring time — including the renderer's `bg-*` pass-through spellings, which render only when the host app's Tailwind build happens to generate the class — instead of shipping a header that silently does not paint. Optional with NO schema default; the `title` withhold from #11661 stands unchanged.
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -753,7 +753,7 @@ Sort field and direction pair
753
753
| :--- | :--- | :--- | :--- |
754
754
|**columns**|`Enum<'1' \| '2' \| '3' \| '4'>`| optional (default: `"2"`) | Number of columns for field layout (1-4) |
755
755
|**layout**|`never`| optional |[REMOVED]`record:details` property `layout` was removed in @objectstack/spec 17.0.0 (#6946, ADR-0087 D2) — its declared `auto`\|`custom` semantics were never implemented: the renderer tests `layout` only against `inline`\|`compact`, two values the schema never permitted, so both legal values took the same branch and the key selected nothing. Delete the key — the body is already chosen by what you author: `sections` renders the explicit groups (the old `custom`), and omitting it falls back to the object's `highlightFields` (the old `auto`). Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
756
-
|**sections**|`{ name?: string; label?: string \| Record<string, string>; columns?: integer; fields: string[]; … }[]`| optional | Field groups rendered as the detail body, in order. Object form: `{ name?, label?, columns?, fields, hideEmpty?, collapsible?, showBorder?, defaultCollapsed?, icon?, description? }`. |
756
+
|**sections**|`{ name?: string; label?: string \| Record<string, string>; columns?: integer; fields: string[]; … }[]`| optional | Field groups rendered as the detail body, in order. Object form: `{ name?, label?, columns?, fields, hideEmpty?, collapsible?, showBorder?, defaultCollapsed?, icon?, description?, headerColor? }`. |
757
757
|**fields**|`string[]`| optional | Explicit field list to display (optional, overrides highlightFields) |
758
758
|**hideFields**|`string[]`| optional | Field names to omit from the body — applied to `fields` and to every section's `fields` (used to dedupe fields already shown in `record:highlights` or as the page title) |
759
759
|**inlineEdit**|`boolean`| optional | Allow inline field editing in the detail body (renderer default: on, where the object itself is editable — set `false` to force it off). |
@@ -774,6 +774,7 @@ Sort field and direction pair
774
774
|**defaultCollapsed**|`boolean`| optional | Start a `collapsible: true` section collapsed (renderer default: expanded). Consulted only when `collapsible` is on — a non-collapsible section never reads its collapse state. |
775
775
|**icon**|`string`| optional | Heading icon, as a lucide icon name (kebab-case, e.g. `building-2`). A value that is not an ASCII identifier (emoji, CJK text) renders as literal text beside the heading instead. Shown where the section heading renders: a titled section, or any collapsible section. |
776
776
|**description**|`string`| optional | Sub-heading text rendered under the section heading (plain string — the renderer applies no translation to it, unlike `label`). Renders on a titled or collapsible section; a collapsible section hides it while collapsed. |
777
+
|**headerColor**|`Enum<'muted' \| 'muted/50' \| 'accent' \| 'primary/10' \| 'secondary/10' \| 'destructive/10'>`| optional | Section-header background tint, from the closed six-token vocabulary rendered by objectui's `record:details` header (`muted`\|`muted/50`\|`accent`\|`primary/10`\|`secondary/10`\|`destructive/10`). A value outside the enum is refused at authoring time rather than silently not painting. Omit for an untinted header. |
* Two keys the same measurement found are deliberately NOT declared here
906
-
* (#11661 holds their forks):
905
+
* One key the same measurement found is deliberately NOT declared here
906
+
* (#11661 holds its fork):
907
907
* - `title` — the renderer's `s.title ?? s.label` limb is a second
908
908
* spelling of the heading slot `label` already declares (identical
909
909
* localization handling, zero producers). Same shape as the `page:card`
910
910
* `body`-vs-`children` pair, which #5775 CONVERGED rather than declared
911
911
* — one heading slot, not two de-facto contracts (Prime Directive #12).
912
912
* Held for the maintainer's declare-vs-converge ruling.
913
-
* - `headerColor` — the renderer's only read is `bg-${headerColor}`, a
914
-
* template-literal Tailwind class: Tailwind v4 scans source text with
915
-
* no safelist, so this call site generates NO CSS and an authored value
916
-
* works only when some other source file happens to use the same class
917
-
* literally. Dead-in-practice at the pin (zero producers); declaring it
918
-
* would advertise a capability the renderer does not deliver.
913
+
*
914
+
* `headerColor` used to be withheld alongside it (the renderer's only
915
+
* read was `bg-${headerColor}`, a template-literal Tailwind class that
916
+
* generates no CSS under the v4 source scan — declaring it would have
917
+
* advertised a capability the renderer did not deliver). objectui#6294
918
+
* (merged 2026-08-25) replaced the interpolation with a lookup of
919
+
* complete class literals in `plugin-detail/src/headerColor.ts`, so the
920
+
* renderer now delivers the key because the module declares it; the
921
+
* refusal outlived its recorded reason and #12126 (maintainer ruling A,
922
+
* 2026-08-26) declares the key below as a closed enum.
919
923
*/
920
924
defaultCollapsed: z.boolean().optional().describe('Start a `collapsible: true` section collapsed (renderer default: expanded). Consulted only when `collapsible` is on — a non-collapsible section never reads its collapse state.'),
921
925
icon: z.string().optional().describe('Heading icon, as a lucide icon name (kebab-case, e.g. `building-2`). A value that is not an ASCII identifier (emoji, CJK text) renders as literal text beside the heading instead. Shown where the section heading renders: a titled section, or any collapsible section.'),
922
926
description: z.string().optional().describe('Sub-heading text rendered under the section heading (plain string — the renderer applies no translation to it, unlike `label`). Renders on a titled or collapsible section; a collapsible section hides it while collapsed.'),
923
-
})).optional().describe('Field groups rendered as the detail body, in order. Object form: `{ name?, label?, columns?, fields, hideEmpty?, collapsible?, showBorder?, defaultCollapsed?, icon?, description? }`.'),
927
+
/**
928
+
* Section-header background tint (#12126, maintainer ruling A 2026-08-26:
929
+
* declare as a CLOSED enum — declared = enforced). The vocabulary is
930
+
* exactly the six complete class literals objectui's
931
+
* `plugin-detail/src/headerColor.ts` lookup ships (objectui#6294): those
932
+
* literals live in a file every consuming app's Tailwind scan covers, so
933
+
* each enum value is guaranteed to be in the compiled stylesheet. Tints
934
+
* only, by the renderer module's own reasoning: `CardHeader` sets no
935
+
* foreground colour, so a solid `bg-primary` would leave the title
936
+
* unreadable without a paired `text-*-foreground`.
937
+
*
938
+
* Anything outside the enum — including the renderer's `bg-*`
939
+
* pass-through spellings, which render only if the HOST app's Tailwind
940
+
* build happens to generate the class — is refused at authoring time
941
+
* rather than shipping a header that silently does not paint (the
942
+
* objectui#6178 failure mode this key's old refusal existed to prevent).
943
+
* Optional with NO schema default: an omitted key means "no tint", the
944
+
* renderer's own fallback.
945
+
*/
946
+
headerColor: z.enum(['muted','muted/50','accent','primary/10','secondary/10','destructive/10']).optional().describe('Section-header background tint, from the closed six-token vocabulary rendered by objectui\'s `record:details` header (`muted` | `muted/50` | `accent` | `primary/10` | `secondary/10` | `destructive/10`). A value outside the enum is refused at authoring time rather than silently not painting. Omit for an untinted header.'),
947
+
})).optional().describe('Field groups rendered as the detail body, in order. Object form: `{ name?, label?, columns?, fields, hideEmpty?, collapsible?, showBorder?, defaultCollapsed?, icon?, description?, headerColor? }`.'),
924
948
fields: z.array(z.string()).optional().describe('Explicit field list to display (optional, overrides highlightFields)'),
925
949
/**
926
950
* Field names to omit from the body, applied to both `fields` and every
0 commit comments