---
name: graduation
description: Consolidate duplicated UI markup into a verified-fidelity reusable primitive. Use when two or more screens repeat the same markup tree and you want one component without moving a pixel.
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
---

> **Specialization:** generic by design — bind every `<…>` and "per `CLAUDE.md`" reference from the project `CLAUDE.md` (the component naming conventions, design-system prefix, test runner, and primitive home directory). See `SPECIALIZE.md`. If a needed value is missing, STOP-and-ask.

## What graduation is — and when to reach for it

**Graduation** is the act of lifting a hand-authored markup tree that several screens duplicate into a single parameterized component, then having each screen compose that component instead. The result is one canonical source of truth for the markup; the screens become callers, not copyists.

Reach for it when:
- Two or more screens emit **structurally identical markup** for the same UI element.
- The duplication is in the **markup/template layer** — the style names are already consistent.
- The goal is **zero visual change** — not a redesign, not a style rename, just collapsing the repeated tree into one place.

Do NOT reach for graduation when a screen's markup merely resembles another — resemblance is not duplication. Verify byte-level structural identity before starting.

---

## The easy vs. hard case — know which you are in

This distinction determines whether fidelity is machine-provable or requires a dedicated spike.

**Easy case — shared-style duplication**

The duplicated screens already share one style namespace (the same class names, the same attribute patterns). A new component that emits the identical class names produces identical output *by construction*. Machine-verifiable fidelity; low risk; graduate this first.

**Hard case — forked-style duplication**

The duplicated screens use diverged style prefixes for the same logical structure (e.g., two intake forms with different naming conventions that evolved independently). A single component must first *reconcile* those divergent styles before producing consistent output. Reconciliation is exactly where 1:1 fidelity silently breaks — a class renamed or an attribute shifted during reconciliation will visually look fine but is a real divergence. Treat as a separate, higher-risk project; run a dedicated reconciliation spike before committing to it.

---

## Proof-first scoping — the one rule that earns its keep

Graduate **one primitive family end-to-end on one consumer first**. Verify fidelity with both harness layers (below). Then **stop and evaluate** (go/no-go). Fan-out to the remaining consumers only after the go decision.

Why: the first graduation always surfaces structural non-uniformities (an optional trailing slot you missed, an attribute that renders unexpectedly, a test-harness version surprise). If you fan out before encountering these, you will propagate the wrong primitive to every consumer and face a coordinated rollback. Proof-first makes each surprise local, cheap, and instructive.

The go/no-go evaluation after the proof is the human checkpoint — this honors capped-autonomy (human on the loop, not in it).

---

## Golden-by-construction fidelity

The governing idea is simple:

1. **Capture** the pre-refactor rendered output of the target region — before any markup change.
2. **Consolidate** the duplicated markup into the new primitive; update the consumer to compose it.
3. **Assert** that the post-refactor rendered output of the same region matches the captured golden exactly.

Two rules are load-bearing:

- **Capture before refactoring.** The golden must be the actual pre-refactor truth, not reconstructed from memory or the spec. Once you start editing the consumer, the golden opportunity is gone.
- **Use the same harness for capture and for assertion.** If both sides go through the same normalization step, any surface-syntax difference (whitespace, attribute order) is normalized out and the comparison is purely structural and semantic. If you capture with one tool and assert with another, you are not comparing like to like.

---

## Two-layer fidelity proof

Layer 1 and Layer 2 answer different questions; you need both.

### Layer 1 — primitive emission (unit)

Render each new primitive **in isolation**, pass it a fixed set of parameters, and assert it emits the exact expected markup skeleton.

- For a primitive with **no opaque child content** (a leaf element): use an exact-markup assertion — the emitted bytes must match a small inline golden.
- For a primitive with **a child slot whose internals you do not want to hardcode** (a container): use structural assertions — confirm the outer class is present and correctly nested, confirm optional slots appear only when the relevant parameter is supplied, confirm required text content matches. Verify the child slot's bytes in Layer 2.

This layer catches: missing attributes, wrong class names, extra or missing wrapper nodes, optional-slot logic inverted, and naming typos in the primitive.

### Layer 2 — region golden (integration)

Render the **whole refactored consumer screen** with the same in-process component-render harness. Select the consolidated region of the output. Assert it equals the committed pre-refactor golden.

Use a **semantic markup diff** — one that normalizes whitespace and attribute order but flags any structural, class, or attribute difference. This is the correct fidelity contract: surface-syntax equivalence is noise; structural equivalence is the signal.

Run the harness **in-process** (no live server, no browser launch). In-process render is fast, deterministic, and CI-safe.

**Exclude non-deterministic content** from the compared region: timestamps, random IDs, session-specific data. Tighten the selection to the structural region you actually changed.

---

## Avoid the false-green trap

A fidelity test that can pass *without actually exercising the comparison* is worse than no test — it reports green while proving nothing.

Common failure modes:

- A browser-based test that early-returns when no server URL is configured (the default in most CI environments). The test exits 0, the comparison never runs.
- A golden file that is committed but never asserted against.
- An assertion that compares the wrong region (e.g., the whole page instead of the consolidated section, or vice versa).

Before trusting your harness, verify that it **fails when the markup differs**. Introduce a deliberate mutation to the consumer, run the test, confirm it goes red, revert. If your only available harness can vacuously pass, fix or replace it before proceeding.

---

## Expect these fidelity surprises — proof-first earns its keep here

These are the categories of surprise that reliably appear when you graduate a real primitive for the first time. Knowing them in advance means you handle them quickly and correctly.

**Non-uniform consumer structure.** Screens you assumed were byte-identical turn out to differ in one place — an optional trailing element, an extra wrapper, an element that one consumer renders conditionally. The primitive must model the optional structure as an optional slot parameter. Omit the slot node entirely when the parameter is not supplied; do not render an empty container.

**A pre-existing style smell you are not allowed to fix.** The original markup may contain a known anti-pattern (an inline style attribute, a layout shortcut that violates the design-system rules). Bake it verbatim into the primitive to preserve 1:1. Record it as a deliberate, separate cleanup task — do not fix it silently during the fidelity move. A silent "improvement" diverges from the golden and breaks the proof.

**An attribute that renders unexpectedly.** A boolean attribute bound to a boolean parameter may render differently than the string form you expected (e.g., minimized to `attr=""` when truthy, omitted when falsy, rather than `attr="true"`/`attr="false"`). If the original markup also exhibits this behavior, match it 1:1. Do not "correct" the rendering during the graduation — file it separately as a deliberate change after the proof.

**Test-harness API or version surprises.** The render test framework may have a different API than you assumed (a renamed method, a changed base class, a named-slot syntax difference). The first task in every graduation should be a one-render spike that fails loudly if the harness cannot render the primitive at all — surface harness surprises before investing in the full test suite.

---

## The 1:1-outranks-cleanup rule

If graduation reveals a pre-existing defect — an accessibility attribute wrong-valued, a style anti-pattern, a misleading class name — **do not fix it** during the fidelity move.

Fixing it mid-graduation diverges the new component from the original and breaks the golden. That means the golden no longer represents pre-refactor truth; it represents a mix of refactoring and improvement, and the fidelity proof is invalid.

The correct sequence:
1. Graduate first — reproduce the defect 1:1 in the primitive; let the golden pass.
2. Commit the graduation as a pure fidelity move (separate commit, reviewable in isolation).
3. Fix the defect in a subsequent deliberate change: update the primitive, rebaseline the golden, document the improvement.

This keeps commit history clean and every change reviewable in isolation.

---

## Go/no-go evidence report

After both harness layers are green, produce this report. No fan-out begins without it.

**Lines eliminated.** Count the net markup lines removed from the proof consumer. Project the savings across the remaining consumers (rough, but enough to judge whether fan-out is worth it).

**Per-consumer verification cost.** Estimate the authoring effort for Layer 1 tests (typically low — transcription-level for known-clean primitives) and Layer 2 (one golden capture + one compare assertion per consumer). The harness is built once and reused.

**Fidelity surprises encountered.** List each unexpected finding and how it was resolved. This is the evidence that the proof actually ran and tested the method against the real codebase, not a toy.

**Staged recommendation.** Should fan-out proceed? If yes, in what order, and are there any consumers that should be treated as a separate risk class (forked-style, page-specific sub-family, etc.)? Name them explicitly.

---

## Done when

- The primitive(s) exist in the project's shared component home, emitting verbatim DOM.
- Layer 1 emission tests pass for every primitive (all parameter combinations including optional slots).
- The proof consumer composes the primitive(s); no other consumer is changed.
- Layer 2 region golden of the proof consumer's consolidated region is byte-identical post-refactor.
- The false-green check was performed (harness confirmed to fail on deliberate mutation).
- The go/no-go evidence report is written and a decision is recorded.
