eugenelim/agent-ready-repo/packs/frontend-engineering/.apm/skills/component-contract/SKILL.md
component-contract
Design a UI component's public interface — props/slots/events, controlled vs. uncontrolled ownership, composition patterns, lifecycle contract, and usage documentation — before writing any implementation.
- Source repository stars
- 15
- Declared platforms
- 0
- Static risk flags
- 0
- Last source update
- 2026-08-05
- Source checked
- 2026-08-05
Decision brief
What it does—and where it fits
Load this skill when designing a new shared component — one that will be used by multiple callers in the codebase. Do not load it for one-off components local to a single page; the additional design overhead is not warranted. Load component-contract when:
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/eugenelim/agent-ready-repo --skill "packs/frontend-engineering/.apm/skills/component-contract"Inspect the Agent Skill "component-contract" from https://github.com/eugenelim/agent-ready-repo/blob/9563bc93aa5b0750b327be2fd95676ff2a5ec63b/packs/frontend-engineering/.apm/skills/component-contract/SKILL.md at commit 9563bc93aa5b0750b327be2fd95676ff2a5ec63b. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
Usage documentation template
Every shared component must ship a usage doc. Minimum viable format:
Every shared component must ship a usage doc. Minimum viable format: - 02
Output rendering
Table — When presenting several items that share the same fields, render a Markdown table. Cap at 5 columns; beyond that, switch to a per-item detail list. Right-align numeric columns. Key–value / one record — For a single record's fields, use an aligned key: value list, not a t…
Table — When presenting several items that share the same fields, render a Markdown table. Cap at 5 columns; beyond that, switch to a per-item detail list. Right-align numeric columns. Key–value / one record — For a sin… - 03
The API-first principle
A component's public interface is its most durable artifact. The implementation changes; the interface is what callers depend on. Once multiple callers depend on a component's props, events, and slots, changing the interface is a breaking change. An interface designed carelessly…
The interface is the deliverable of this design pass — not the implementation.The interface should be the minimum that satisfies the callers' needsA component's public interface is its most durable artifact. The implementation changes; the interface is what callers depend on. Once multiple callers depend on a component's props, events, and slots, changing the inte… - 04
Controlled vs. uncontrolled ownership
Uncontrolled component: the component manages its own state internally. The caller does not control the value; it just receives change notifications.
Uncontrolled component: the component manages its own state internally. The caller does not control the value; it just receives change notifications.Controlled component: the caller owns the state. The component is a pure rendering function — it displays what it's given and reports changes.The rule for when to support both: provide uncontrolled by default (simpler for most callers), with a controlled override for callers that need to manage state themselves (e.g., when the value must be derived from exter… - 05
Props design rules
Review the “Props design rules” section in the pinned source before continuing.
Review and apply the “Props design rules” source section.
Permission review
Static risk signals and limitations
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 15 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- eugenelim/agent-ready-repo
- Skill path
- packs/frontend-engineering/.apm/skills/component-contract/SKILL.md
- Commit
- 9563bc93aa5b0750b327be2fd95676ff2a5ec63b
- License
- Apache-2.0
- Collected
- 2026-08-05
- Default branch
- main
View the original SKILL.md
Skill: component-contract
Load this skill when designing a new shared component — one that will be
used by multiple callers in the codebase. Do not load it for one-off
components local to a single page; the additional design overhead is not
warranted. Load component-contract when:
- Building a new component that will go into a shared UI library or component directory
- Refactoring a component that has grown multiple callers and its interface is inconsistent
- Reviewing a component's public interface before it is published or exported
The contract must be written before any implementation code. An interface designed after the implementation reflects the implementation's needs, not the caller's needs — it is the wrong order.
Output rendering
Table — When presenting several items that share the same fields, render a Markdown table. Cap at ~5 columns; beyond that, switch to a per-item detail list. Right-align numeric columns. Key–value / one record — For a single record's fields, use an aligned key: value list, not a two-row table. Rationale / narrative — Use short ## headings and 2–3 sentence paragraphs. Don't force narrative into a table.
The API-first principle
A component's public interface is its most durable artifact. The implementation changes; the interface is what callers depend on. Once multiple callers depend on a component's props, events, and slots, changing the interface is a breaking change. An interface designed carelessly at the start becomes technical debt proportional to its adoption.
The principle has two implications:
- The interface is the deliverable of this design pass — not the implementation.
- The interface should be the minimum that satisfies the callers' needs today, with room to extend without breaking.
Controlled vs. uncontrolled ownership
Uncontrolled component: the component manages its own state internally. The caller does not control the value; it just receives change notifications.
// Uncontrolled: caller provides an initial value; component manages the rest
<InputField defaultValue="Initial text" onChange={handleChange} />
Controlled component: the caller owns the state. The component is a pure rendering function — it displays what it's given and reports changes.
// Controlled: caller owns the value; component is a display layer
<InputField value={controlledValue} onChange={setValue} />
The rule for when to support both: provide uncontrolled by default (simpler for most callers), with a controlled override for callers that need to manage state themselves (e.g., when the value must be derived from external state, validated before setting, or synchronized with another control).
The most common pattern: accept both value (controlled) and defaultValue
(uncontrolled). When value is provided, operate in controlled mode; when
only defaultValue is provided, operate in uncontrolled mode.
Props design rules
| Rule | Wrong | Right | Reason |
|---|---|---|---|
| Name props for what they ARE | showModal={true} | isOpen={true} | show is a verb (what to do); isOpen is a state (what it is) |
| Boolean props name the positive state | disabledState | isDisabled | Double negatives (isNotDisabled) are harder to reason about |
| Avoid encoding implementation | useFlexLayout | layout="horizontal" | The implementation detail leaks into the API; the caller shouldn't care how layout is achieved |
| Prefer composition over configuration | <Button showIcon={true} iconName="check"> | <Button><CheckIcon />Submit</Button> | Multiple boolean props that add/configure content make the component harder to extend without changing the API |
| Consistent naming convention | Mixed on_click, onClick, handleClick | Always on + PascalCase event name | Consistency reduces cognitive load for callers |
Slots and composition patterns
Default slot (children): the most flexible composition pattern. The caller provides any content they need; the component provides the container and behavior.
Use the default slot when: the component's job is behavior/wrapper (a modal, a tooltip, a card), not content generation.
Named slots: when the component has multiple content regions with distinct
semantic roles (a card with header, body, and footer; a dialog with
title and content).
In JSX:
<Card>
<Card.Header>Card title</Card.Header>
<Card.Body>Card content goes here.</Card.Body>
<Card.Footer><Button>OK</Button></Card.Footer>
</Card>
In Vue / Web Components (explicit slot names):
<card-component>
<template slot="header">Card title</template>
<template slot="body">Card content.</template>
</card-component>
Render props: a function-as-children pattern that gives the caller control over rendering while the component provides data. Appropriate for components that manage complex state and expose it to the caller for rendering.
Use render props when: the component manages state the caller needs to render (e.g., a dropdown that tracks open/selected but lets the caller render the options).
Events contract
| Rule | Detail |
|---|---|
Naming: on + PascalCase past-tense event | onChange, onSubmit, onDismiss — not handle_click, not clicked |
| Payload shape | Define the shape of the event payload in the contract. If it carries data (the new value, the selected item), document it explicitly. |
| What the component does NOT do after emitting | The component emits the event and stops. It does not optimistically update its own controlled state. The caller owns the state update. |
| Avoid emitting raw browser events | Wrap browser events in component-level events with meaningful names. onChange on a text field is a component event; it should not expose the browser's InputEvent object unless the caller genuinely needs browser-level access. |
Lifecycle contract
Document what the component expects on mount, during its lifetime, and on unmount:
- On mount: any async operations it initiates (data fetching, subscriptions, DOM measurements). What state is it in before those operations complete?
- During lifetime: any external dependencies it listens to (context, global stores, DOM resize observers). What happens when those change?
- On unmount: what it tears down (subscriptions, timers, event listeners). A component that does not clean up its subscriptions on unmount is a memory leak.
- Strict mode double-invocation: React 18+ strict mode double-invokes
useEffect(and the equivalent in other frameworks) in development. Does the component survive a mount/unmount/mount cycle without observable side effects? If not, the lifecycle contract has a bug.
Usage documentation template
Every shared component must ship a usage doc. Minimum viable format:
## ComponentName
**Purpose:** One sentence describing what this component does and why it exists.
**When to use:** [conditions], not [counter-conditions].
### Props
| Name | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `string` | — | Controlled value. Provide with `onChange` for controlled mode. |
| `defaultValue` | `string` | `''` | Initial value for uncontrolled mode. |
| `isDisabled` | `boolean` | `false` | Disables the component; renders `aria-disabled`. |
| `onChange` | `(value: string) => void` | — | Called on every value change. |
### Events
| Name | Payload | When it fires |
|------|---------|--------------|
| `onChange` | `string` | Every time the user changes the value |
| `onBlur` | `FocusEvent` | When the component loses focus |
### Slots
| Name | Required | Description |
|------|----------|-------------|
| default | Yes | Content rendered inside the component |
| `label` | No | If provided, replaces the built-in label element |
### Accessibility contract
- Manages `aria-disabled` when `isDisabled` is true.
- Accepts focus via Tab; announces its label to screen readers via `<label>` association.
- Does not trap focus.
### Example
[Minimal complete usage example — the smallest correct usage, not a feature tour]
Anti-patterns
| Anti-pattern | Problem | Fix |
|---|---|---|
| Prop drilling beyond 2 levels | Component requires callers to pass the same prop through 3+ levels of nesting | Extract a context (React Context, Vue provide/inject) to share the value without threading it through props |
| God component (more than one primary job) | A component renders a form AND handles data fetching AND manages a modal | Split into two components: one for UI (receives data via props), one for data management (renders the UI component) |
| Implicit global state mutation | Component writes to a global store directly rather than emitting an event | The component emits; the caller (or a coordinating layer) decides whether and how to update global state |
| Spreading all props on the root element | <div {...props}> passes unknown props to the DOM | Explicitly whitelist which props are valid for the root element; spread the rest only if a forwarded-refs pattern is intentional |
Alternatives
Compare before choosing
K-Dense-AI/scientific-agent-skills
esm
Use when working directly with the `esm` Python SDK, ESM3 or ESMC model IDs, Forge/Biohub inference clients, or ESMFold2 folding workflows.
mgiovani/cc-arsenal
team-review
Multi-agent review team: architecture, security, performance, testing, style, docs/UX, plus an adversary that cross-examines the other 6, for security-sensitive, architectural, or large PRs (15+ files) where a single-agent pass risks missing cross-cutting issues. Use for auth/payments/PII changes, schema/pattern changes, compliance sign-off, or when asked to 'get the review team on this' / 'multi-agent review' / 'thorough review before merge'. For a standard PR or a quick pre-merge check, use /r
eugenelim/agent-ready-repo
work-loop
Use when implementing or resuming a non-trivial repository change: a feature, behavior-changing fix, refactor, migration, framework or dependency upgrade, schema or API change, performance work, infrastructure or build-system change, reversion, or an existing build spec under `docs/specs/`. Also use for bare continuation commands ('resume', 'continue', 'keep going', 'pick up where I left off', 'let's get going') when conversation or workspace context identifies active build work. Do not use for
wshobson/agents
architecture-decision-records
Write and maintain Architecture Decision Records (ADRs) following best practices for technical decision documentation. Use when documenting significant technical decisions, reviewing past architectural choices, or establishing decision processes.