# `migration-guide-authoring.md`, authoring the MIGRATION GUIDE on a breaking cut

> Load whenever the cut is **MINOR** (an API-surface break) or on an explicit
> "author the migration guide" ask. This is the **producer** side of migrations:
> the release ships the breaking change, so the release authors the guide section
> consumers follow. The shared producer/consumer format contract is
> [`../../../references/contracts/migration-guide-format.md`](../../../references/contracts/migration-guide-format.md)
>, the factory's `app-migration` skill consumes exactly that shape.
>
> Scope split: **producer (this skill)** authors the guide section + migrates the
> framework's own in-repo surfaces. **Consumer** (a downstream app sweep) is the
> factory plugin's `app-migration`, decline and redirect. Designing the breaking
> change itself is a contract decision upstream of both.

The guide lives at **`.claude/docs/MIGRATION GUIDE.md`** (the space in the filename is intentional; it's a first-class consumer artifact). One section per breaking release, newest at top; a consumer jumping versions reads the merged span. Every cut, breaking or not, gets a version-scope bullet in the top index (see the format contract).

## §When a section is owed

Author (or extend) a section when the cut removes or renames a **public API symbol**: a prop/attribute (`variant="danger"` → `color="danger"`), a slot or slot-semantics flip, an event name, a token (`--n-*` → `--a-*`), a tag, a Boolean → enum migration, or a default-value change wide enough that consumers must act. Same line as the PATCH-vs-MINOR rule: cutting MINOR almost certainly owes a section. Additive cuts need none, note "additive; no consumer sweep" in the version-coverage table and move on.

## §The authoring workflow

**1. Enumerate the breaking surface** from the cut's diff + CHANGELOG `### Removed`/`### Changed` entries. Per item capture: the symbol · before → after · the kind (pure rename / semantic flip / Boolean→enum / removal) · the audit grep · the sweep (or "manual review").

**2. Write the section**, one `###` subsection per item:

```markdown
## Migrating to @adia-ai/web-components@X.Y.Z (YYYY-MM-DD)

<one-line scope: N breaking items, the headline.>

### <item, bold headline> (`old` → `new`)

<one sentence: what changed and why.>

**Audit:**
git grep -nE '<pattern that surfaces call sites>'

**Sweep:**
git grep -lz '<pattern>' | while IFS= read -r -d '' f; do
  perl -i -pe 's/<old>/<new>/g' "$f"
done
```

Shape rules:

- **Audit grep first, sweep second**, the consumer always lists call sites before sweeping; author both.
- **Mechanical vs manual, explicitly labeled.** A pure rename ships a `perl -i -pe`; a semantic flip ships a "manual review, here's why" note (sed can't tell author intent).
- **One component per sweep regex**, alternation captures (`<(toast|alert)-ui`) don't preserve the matched alternative cleanly.
- **HTML-attribute regexes only match HTML/JSX**, author a separate JS-side regex when the symbol has a programmatic form (`el.variant = 'danger'`).
- **`git grep -lz | while read -d '' f; do perl -i … "$f"; done`, never `| xargs perl -i`.**
  The `xargs` form hangs on zero matches (GNU xargs still runs perl once with no file argument,
  and `perl -i -pe` then blocks reading stdin instead of no-op'ing) and, if `<old>`/`<new>` are
  passed as shell variables rather than literals, an `@`-bearing replacement (`@adia-ai/...`)
  parses as perl array interpolation when inlined into `-pe` and silently substitutes empty
  (gh#1233), pass such strings through the environment instead of the perl source, per
  `.claude/docs/MIGRATION GUIDE.md`'s `§0.8.37` `sweep()` helper.

**3. Migrate the in-repo surfaces FIRST.** Before the cut ships, run the audit + sweep against `apps/`, `playgrounds/`, `catalog/`, `packages/web-components/components/*/*.html`, the release must not ship broken examples of the thing it changed, and dogfooding the recipe here is what proves it works for consumers.

**4. Verify.** Run the cut's normal gate roster; the structural/demo gates catch missed in-repo surfaces. Then the **sweep-verification grep** across ALL extensions: the trap is a vocabulary migration that touched markup but not the CSS selectors or JS comments referencing it:

```bash
LEGACY_PATTERNS=( '<old-tag' 'old-attr=' '--old-token' )
for pat in "${LEGACY_PATTERNS[@]}"; do
  echo "=== $pat ==="
  grep -rln -E "$pat" apps playgrounds catalog \
    --include='*.css' --include='*.html' --include='*.js' --include='*.yaml'
done   # 0 hits = sweep verified clean
```

**5. Cross-reference.** The breaking package's CHANGELOG entry names the symbol AND points at the guide section; the release notes carry a `⚠️` heads-up linking it. Anchors are stable, never retitle an existing section (release notes and consumer tooling link them).

## §Manual-review classes (never auto-sweep)

- **Semantic flips**, a rename that inverts default behavior (`[open]` default-hidden → `[collapsed]` default-visible): the right migration depends on author intent. List occurrences, ask.
- **Opt-out Booleans defaulting true**, the migration only matters where a consumer explicitly disabled the affordance; sed can't tell.
- **Ownership moves**, a prop moving from wrapper to slotted child; the target child may not exist yet.
- **kebab-string property keys**, attribute name unchanged, only the JS programmatic form changed; audit programmatic access only.
- **Wide namespace renames (50+ symbols)**, tag/token/class rename waves: list by table, require human approval per cluster; blind sweeps hit false positives on shared prefixes.

## §The version-coverage table

Maintained at the top of the guide; classifies every release so a gap-jumping consumer knows which sections apply: `additive` (bump and go) · `BREAKING (N items)` (has a section) · `structural` (import retargeting) · `rename` (wide namespace rename, manual-review by cluster). Note forward-looking volatile surfaces at the bottom of the guide so the next breaking-release author knows where churn concentrates.

## §Anti-patterns

Authoring the guide after the cut ships (it's part of the release scope) · a `perl -i -pe` for a semantic flip · merged alternation sweeps · skipping the in-repo sweep · a CHANGELOG bullet with no guide section for a real break (the CHANGELOG says *what*, the guide says *how*) · doing the consumer sweep from here.

## §Done when

1. Every breaking item in the cut's CHANGELOG has a matching subsection (audit + sweep, or a labeled manual-review note).
2. In-repo surfaces swept; the sweep-verification grep reports 0 legacy hits.
3. The cut's structural/demo gates pass.
4. The release notes carry the `⚠️` heads-up linking the section.
