Enact kit — variation inventory

The kit currently declares 6 button sizes, 6 button chromes, 3 modal sizes, 1 loading-spinner size, 1 avatar size, and 5 input chromes. Of 38 enumerated entries, 0 are unreachable from feature code — every public type entry has either live traffic or is documented as a kit-internal primitive. 15 entries were retired on 2026-10-12; the kill / open-question sections below record what was here. UI-063 enforces the new contracted vocabulary.

Done 2026-10-12

14

retired entries — UI-063 enforces the contracted vocabulary

Open questions

3

still pending owner decision

Keep

43

live in feature code or internal primitives

✓done — retired 2026-10-12, enforced by UI-063 ?open question — needs an owner decision before this row can resolve ✓keep — load-bearing or live traffic

Kill (historical record) 11 entries

The original kill list as authored 2026-10-12, kept here as a "what was here" record. The plain row was renamed to quiet in the same pass (see the Done section below); the remaining 11 entries are now in the Done section. UI-063 enforces the contracted vocabulary.

Kind Primitive Name Used Sites Rationale
✗ button size ButtonSize icon no 0 Zero call sites; IconButton hardcodes icon-sm; no consumer ever asked for square-only-32px.
✗ button size ButtonSize surface no 0 Original kill entry — retracted. The user's working tree re-added both the type entry and the CSS rule on 2026-10-12 after the kill had been applied. selectable-surface.svelte:29 is the live consumer; surface now lives in ButtonSize alongside the new fit entry. UI-063 enforces the retired values list (icon / resize-row / resize-column / xl / full / xs / sm / md / lg / plain / link / dashed), so surface stays out of the gate's forbidden set. See the Keep table below.
✗ button size ButtonSize resize-row no 0 Zero call sites; reserved for resize-handle CSS that lives elsewhere.
✗ button size ButtonSize resize-column no 0 Zero call sites; same as resize-row.
✗ modal size Modal / ModalFrame / WorkspaceModal full no 0 Zero call sites; declared across Modal, ModalFrame, WorkspaceModal. Drop.
✗ avatar size AvatarBubble md no 0 Declared but never used. Drop.
✗ avatar size AvatarBubble lg no 0 Declared but never used. Drop.
✗ loading size LoadingSpinner xs no 0 Declared but never passed. Drop.
✗ loading size LoadingSpinner sm no 0 Declared but never passed. Drop.
✗ loading size LoadingSpinner md no 0 Declared but never passed. Drop.
✗ loading size LoadingSpinner xl no 0 Declared but never passed. Drop.

Done 2026-10-12 14 entries retired · 1 renamed · UI-063 enforces

The 14 entries below were retired on 2026-10-12; plain was renamed to quiet in the same pass to make its no-wash semantics visible in the type. UI-063 (dead-primitive-variants) enforces the contracted vocabulary: any size= or chrome= literal on a kit primitive whose value is in this table is a finding. Two consumer migrations are noted in the rationale column.

Kind Primitive Name Was Now Migration
✓ button size ButtonSize icon icon icon-sm | icon-xs only Zero call sites; IconButton hardcodes icon-sm.
✓ button size ButtonSize surface surface sm + padding classes disclosure-row.svelte:30 and selectable-surface.svelte:29 rewrote to size="sm" with h-auto p-4 (height-follows-content preserved).
✓ button size ButtonSize resize-row resize-row — Zero call sites; CSS rule lives in shell.css via ui-resize-handle-row, not via BUTTON_SIZE.
✓ button size ButtonSize resize-column resize-column — Same as resize-row.
✓ button chrome ButtonChrome plain → quiet plain quiet Renamed 2026-10-12 — not killed. The two real ButtonChrome consumers (disclosure-row.svelte:25, selectable-surface.svelte:29) need the no-hover-wash, no-pressed-accent behaviour that plain provided (a hover wash would compete with sibling rows on disclosure rows and override the surrounding card surface on selectable surfaces). Renamed to quiet to make the no-wash semantics explicit in the type. The two chrome="plain" callers on InputBase / TextareaBase (inline-field, composer-field) belong to InputChrome, which stayed live.
✓ button chrome ButtonChrome link link ghost + className inline-action.svelte:21 rewrote to chrome="ghost" with h-auto p-0 font-medium hover:underline carried via className.
✓ button chrome ButtonChrome dashed dashed outline empty-state-action.svelte:21 rewrote to chrome="outline" (the next-dashed affordance).
✓ modal size Modal / ModalFrame / WorkspaceModal xl xl lg path-browser.svelte:702 rewrote to size="lg"; table-designer.svelte:148 (the conditional isDocumentDb ? 'sm' : 'xl') also to 'lg'. Q2 closed: lg, not full.
✓ modal size Modal / ModalFrame / WorkspaceModal full full md Zero call sites; workspace-modal.svelte:23 default flipped from 'full' to 'md'.
✓ avatar size AvatarBubble md md sm Declared but never used.
✓ avatar size AvatarBubble lg lg sm Declared but never used.
✓ loading size LoadingSpinner xs xs lg Declared but never passed.
✓ loading size LoadingSpinner sm sm lg Declared but never passed.
✓ loading size LoadingSpinner md md lg The default. Flipped to lg (the only size passed in practice, 7 sites).
✓ loading size LoadingSpinner xl xl lg Declared but never passed.

Enforced by UI-063 dead-primitive-variants — scope <ui>/**/*.svelte, severity M. Add a value to the gate's UI063_SIZE_DEAD / UI063_CHROME_DEAD sets in providers/svelte_tailwind/analyzer.ts to retire more.

Open questions 3 entries

Each question below blocks the disposition of one or more rows in the kill / keep tables above. The answer collapses the row either way —  ?  means the question is live, not a verdict.

# Question What it affects Where to look
? 1 Should DIALOG_INTENT_TO_CHROME.cancel map to ghost instead of outline? The current outline mapping is the source of the Cancel / Install visual mismatch measured earlier — the cancel and confirm buttons share the same border-box but read as differently weighted because outline paints a visible border while primary does not. Mapping cancel → ghost would make the cancel recede and the primary CTA stand alone.
  • DIALOG_INTENT_TO_CHROME.cancel → currently outline; would change to ghost.
  • The Cancel / Install mismatch in dialogs closes — the same fix the Back / Finish Setup navigation-context question asks for.
  • No new chrome added; no type churn.
apps/web/src/components/common/display/button-chrome.ts:88 · DIALOG_INTENT_TO_CHROME table.
? 2 Does InputChrome.muted survive, fold into plain, or get killed? muted paints a transparent-border input with bg-muted/60. The intended use was filter rows where the parent card already paints a surface. No direct call sites found in feature code; could be dead, or could be used by an internal primitive I missed.
  • InputChrome.muted — currently kill; depends on this question.
  • If folded into plain: zero type entries change.
  • If kept: one CSS rule preserved; one type entry kept.
apps/web/src/components/common/form/input-chrome.ts:7 · grep chrome="muted" across apps/web/src.
? 3 Are plain, link, dashed chromes kept as @internal escape hatches, or fully removed? Resolved 2026-10-12: kill taken. All three dropped from ButtonChrome. disclosure-row.svelte rewrote to chrome="ghost"; inline-action.svelte rewrote to chrome="ghost" + link-classes via className; empty-state-action.svelte rewrote to chrome="outline". The two chrome="plain" callers on input primitives (inline-field, composer-field) are InputChrome and stayed live.
  • ButtonChrome.plain, .link, .dashed — dropped 2026-10-12; UI-063 forbids them.
  • Full-remove path taken; no @internal escape hatch kept.
apps/web/src/components/common/form/inline-field.svelte:17 · composer-field.svelte:18 · display/inline-action.svelte:21 · display/empty-state-action.svelte:21.

Keep 39 entries

Kind Primitive Name Used Sites Rationale
✓ button size ButtonSize sm yes 21 Default for compact buttons; primary traffic in nav / inline actions.
✓ button size ButtonSize md yes 26 Default for modal CTA / settings dialogs.
✓ button size ButtonSize lg yes 26 Default for onboarding / empty states / marketing.
✓ button size ButtonSize (internal) icon-sm yes (internal) 2 IconButton's only size; hardcoded inside the primitive. Keep type entry as internal-only.
✓ button size ButtonSize (internal) icon-xs yes (internal) 0 BannerAction.iconOnly hardcodes icon-xs (12 BannerAction sites, ~half iconOnly). Live and load-bearing; just unreachable from outside.
✓ button size ButtonSize (internal) surface yes (internal) 1 SelectableSurface's card-density size (p-4 rounded-lg, height-follows-content). Retracted from the 2026-10-12 kill — the type entry and CSS rule were re-added in the same pass as the new fit size. UI-063 leaves surface off its forbidden list because it is live.
✓ button size ButtonSize (internal) fit yes (internal) 1 DisclosureRow's height-follows-content size (px-3.5 py-3, never h-*). Added 2026-10-12 alongside the surface retention. ui-action-size-fit CSS rule lives in shared.css; UI-063 enforces fit lives behind DisclosureRow.
✓ button chrome ButtonChrome primary yes 83 Primary CTA chrome; carries the affirmative action tone.
✓ button chrome ButtonChrome danger yes 29 Destructive intent; solid destructive fill.
✓ button chrome ButtonChrome (internal) danger-outline yes (internal) 0 ICON_INTENT_TO_CHROME.danger → danger-outline; used by IconButton intent=danger. Keep as internal chrome.
✓ button chrome ButtonChrome outline yes 274 Secondary intent chrome; outline with hover fill. The modal-back / cancel / secondary traffic.
✓ button chrome ButtonChrome (internal) ghost yes (internal) 5 Used by MenuItem / ToolbarAction / BannerAction internally. Never directly addressed from feature code; keep as internal chrome.
✓ button chrome ButtonChrome (internal) quiet yes (internal) 2 DisclosureRow + SelectableSurface. No chrome, no hover wash, no pressed accent — caller paints the visual contract (p-4, gap, border, surface role) via className. Renamed from plain 2026-10-12.
✓ button chrome ButtonChrome overlay yes (internal) 1 Transparent / pointer-events only. One site — modal scrim. Keep as escape hatch.
✓ button intent INTENT_TO_CHROME primary yes 83 Primary intent; maps to primary chrome.
✓ button intent INTENT_TO_CHROME secondary yes 274 Secondary intent; maps to outline chrome.
✓ button intent INTENT_TO_CHROME danger yes 29 Danger intent; maps to danger chrome.
✓ button intent ICON_INTENT_TO_CHROME primary yes ? IconButton primary intent.
✓ button intent ICON_INTENT_TO_CHROME secondary yes ? IconButton secondary → ghost.
✓ button intent ICON_INTENT_TO_CHROME danger yes ? IconButton danger → danger-outline.
✓ button intent DIALOG_INTENT_TO_CHROME confirm yes ? DialogAction confirm → primary.
✓ button intent DIALOG_INTENT_TO_CHROME cancel yes ? DialogAction cancel → outline. See open question §1.
✓ button intent DIALOG_INTENT_TO_CHROME danger yes ? DialogAction danger → danger.
✓ modal size Modal / ModalFrame / WorkspaceModal sm yes 7 Delete / disable confirms; tight confirm dialogs.
✓ modal size Modal / ModalFrame / WorkspaceModal md yes 14 Default size; most settings dialogs.
✓ modal size Modal / ModalFrame / WorkspaceModal lg yes 13 Complex settings dialogs; default for skills / profiles / MCP.
✓ avatar size AvatarBubble sm yes 5 Default in 5 sites (history, settings).
✓ loading size LoadingSpinner lg yes 7 Only size passed in practice.
✓ brand mark size BrandMark sm yes 2 Small inline use (history row).
✓ brand mark size BrandMark md yes 2 Medium list-row.
✓ brand mark size BrandMark lg yes 3 Settings panel header.
✓ brand mark size BrandMark xl yes 3 Large hero (login).
✓ input chrome InputChrome default yes ? Input / InlineField / Textarea default.
✓ input chrome InputChrome plain yes ? InlineField / SearchField chrome.
✓ input chrome InputChrome search yes ? SearchField chrome.
✓ input chrome InputChrome danger yes ? Error state chrome; auto-applied by <Input invalid={true}>.
✓ input size InputSize sm yes ? Compact field row (InlineField / SearchField / ComposerField default).
✓ input size InputSize md yes ? Standard form row (Input / Select / Textarea default).
✓ checkbox Checkbox / CheckboxBase — yes — Single visual; no size or chrome variants.
✓ switch Switch / SwitchBase — yes — Single visual; no size or chrome variants.
✓ radio RadioChoice / RadioChoiceBase — yes — Single visual; no size or chrome variants.
✓ slider Slider / SliderBase — yes — Single visual; no size or chrome variants.
✓ color field ColorField / ColorFieldBase — yes — Single visual; no size or chrome variants.
✓ anchored overlay AnchoredOverlay — yes 52 Placement / align / layer only; no size dimension.
✓ toast / alert NotificationToast / AlertBar — yes ? No size variation; intent-driven fill.
✓ banner BannerAction — yes 12 Chrome = ghost + currentColor hover; size = sm or icon-xs.
✓ card primitive ChoiceControl — yes ? Inline content row; size defaults to sm/md based on layout.
✓ card primitive SelectableSurface — yes ? Toggleable selection row; no size prop.