RFC 001: Component Dictionary#87
Conversation
…onary Establishes rfcs/ as a sibling to adr/ for strategic, multi-decision design proposals (broader than ADRs but not yet — or no longer — discrete decisions). RFCs carry the why; ADRs carry the what. RFC 0001: Component Dictionary Proposes a deterministic emitter registry inside specs-cli that fans each validated component contract into ~16 purpose-built files so each consumer (engineers, agentic coding tools, design-system reviewers, report scripts) gets the shape closest to its workflow. Pipeline is pure: zero LLM calls, zero opinions, byte-reproducible from the spec; the contract stays canonical and derivative files are projections; inference is deferred to a separate downstream stage. The bet: the right shape for design-system data in the agentic-coding era is a blend of structured (YAML/JSON) and prose (MD) outputs deterministically projected from a single schema-validated contract. Includes companion documents (script-inference-boundaries, dictionary-composition, cli-reference, smoothing-backlog, templates-appendix) and 14 illustrative sketches grounded in button.yaml from the GitHub Primer demo set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| | Comprehensive scripted reference for docs / review | `button.docs.md` | YAML | | ||
| | Any precision question — exact token, dimension, padding, effect, per-variant value | **YAML** | — | | ||
|
|
||
| **Rationale.** The contract (YAML in v1, ~3k tokens for Button) is canonical and complete but verbose and shaped for the schema, not the consumer. Each edge artifact reshapes the contract's data into a form closer to the consumer's workflow — React types, iOS enums, CSS rules, token graphs, structural index. Starting at the edge means less translation work for the consumer; falling back to the contract for precision means no answer is ever approximated. Edge artifacts should not restate values the contract carries authoritatively when a precision question has a single correct answer. |
There was a problem hiding this comment.
Each edge artifact reshapes the contract's data into a form closer to the consumer's workflow
The only thing I'm having a hard time grappling with is how we might be producing an artifact that is closer to the consumer workflow, but it isn't precise enough to get the right outcome during inference and very likely acts as "corrupted" context.
To me it poses a question of "is it better to have false artifacts or no artifacts for LLM inference". If the cli allows for customizing the output via templates or configuration options I think this concern goes away.
|
|
||
| This RFC scopes `specs generate` and its emitter registry. Everything above generate is existing CLI behavior; the agent smoothing pass downstream is a separate workstream and the scripts/inference boundary is a hard rule (see Principles). | ||
|
|
||
| Inside `specs generate`, each emitter is a Style-Dictionary-style `Selector → Transform → Format` pipeline: a pure function `(spec, options) => { filename, content }` with no I/O. The registry lives in **`specs-cli`**; custom emitters are deferred (see Decisions). |
There was a problem hiding this comment.
This pure function approach would make supporting templates at config/invokation time really simple. That would allow consumers to produce whatever artifact that they would find useful.
Baking in robust defaults as like in your "proposed outputs" table still makes sense in a world where the CLI supports templating as I'm suggesting.
There was a problem hiding this comment.
I see you have a "Template-driven emitters" document which describes an approach to this.
There was a problem hiding this comment.
Comments aren't a contract, they're just documentation.
I'd advocate for stronger typing. In fact that's something most React component devs miss, which leaves users guessing what props are usable together or not. This can also lead to runtime errors when an edge case isn't officially supported but ended up being shipped in the wild.
I gave it a go (I tested it, it compiles!):
// Pure types + slot signatures. No framework imports.
// Defaults and slot visibility rules are encoded as `as const` data so that
// tooling, codegen, and runtime validators can read them — comments would rot.
// ---------------------------------------------------------------------------
// Public unions
// ---------------------------------------------------------------------------
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'invisible';
export type ButtonSize = 'small' | 'medium' | 'large';
export type ButtonState = 'rest' | 'focus' | 'hover' | 'pressed' | 'disabled' | 'inactive';
export type ButtonAlign = 'center' | 'start';
// ---------------------------------------------------------------------------
// Props shape
// ---------------------------------------------------------------------------
export interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
state?: ButtonState;
alignContent?: ButtonAlign;
counter?: boolean;
dropdown?: boolean;
leadingVisual?: string | null;
trailingVisual?: string | null;
}
// ---------------------------------------------------------------------------
// Defaults
//
// `as const` — preserves literal types so consumers see
// `ButtonDefaults.variant` as 'secondary', not string.
// `satisfies Required<…>` — every prop must have a default and every default
// must match its declared union. A missing or drifted
// default fails at compile time.
// ---------------------------------------------------------------------------
export const ButtonDefaults = {
variant: 'secondary',
size: 'medium',
state: 'rest',
alignContent: 'center',
counter: false,
dropdown: false,
leadingVisual: null,
trailingVisual: null,
} as const satisfies Required<ButtonProps>;
// ---------------------------------------------------------------------------
// Slots shape
// ---------------------------------------------------------------------------
export interface ButtonSlots {
search?: unknown;
button: string;
counterLabel?: unknown;
trailingVisual?: unknown;
dropdown?: unknown;
}
// ---------------------------------------------------------------------------
// Slot visibility rules
//
// Replaces the `/* visible when ... */` prose. The `prop` field is narrowed
// to props whose declared type matches the predicate, so e.g. `whenTrue`
// cannot reference a string prop and a typo in the prop name is rejected.
// ---------------------------------------------------------------------------
type PropKeysOfType<T> = {
[K in keyof ButtonProps]-?: NonNullable<ButtonProps[K]> extends T ? K : never;
}[keyof ButtonProps];
type BooleanPropKey = PropKeysOfType<boolean>;
type NullablePropKey = {
[K in keyof ButtonProps]-?: null extends ButtonProps[K] ? K : never;
}[keyof ButtonProps];
export type SlotVisibility =
| { kind: 'always' }
| { kind: 'whenTrue'; prop: BooleanPropKey }
| { kind: 'whenNotNull'; prop: NullablePropKey };
export const ButtonSlotRules = {
search: { kind: 'whenNotNull', prop: 'leadingVisual' },
button: { kind: 'always' },
counterLabel: { kind: 'whenTrue', prop: 'counter' },
trailingVisual: { kind: 'whenNotNull', prop: 'trailingVisual' },
dropdown: { kind: 'whenTrue', prop: 'dropdown' },
} as const satisfies Record<keyof ButtonSlots, SlotVisibility>;|
The bet makes sense to me – and I think it stands fine as a v1. Consider tackling these (non of them are blockers):
|
How to review
rfc/001-component-dictionary/README.md(opens the rendered RFC in a new tab; close it to return here)That's the proposal. The companion documents and sketches in this PR are supporting material — useful if you want to dig into a specific architectural detail, but they are not asking for review at this stage. If you only have time for one document, read the README; the rest is optional context.
What the RFC proposes
A deterministic emitter registry inside
specs-clithat fans each validated component contract into ~16 purpose-built files so each consumer (engineers, agentic coding tools, design-system reviewers, report scripts) gets the shape closest to its workflow.The bet: the right shape for design-system data in the agentic-coding era is a blend of structured (YAML/JSON) and prose (MD) outputs deterministically projected from a single schema-validated contract.
What I'm asking for
Substantive engagement with the RFC's argument, posted as a Conversation comment:
I am not asking for line-edits on the companion docs or sketches. Sketches are exploratory; companions are reference material. If you spot a narrow line-level note on the README itself worth leaving inline, the Files Changed tab supports it — but the comment box on this page is the right venue for substantive feedback on the proposal.
Status
Proposed. The RFC itemizes alternatives considered, drawbacks, decisions ratified, immediate unresolved questions, and future work — engage with each section as it suits you.
🤖 Generated with Claude Code