# Break it, then ship it

> Feed a component the realistic worst case before users do, land the production baseline, and sweep the whole app before launch with a severity tree that tells blockers from polish.

Module: Directing your agent · Level: intermediate · 9 min · https://school.agents.org.in/foundations/agents/break-then-ship

The member row looks perfect. It was designed with "Ana Ruiz", three rows and a wide screen. Production sends it "María-José Fernández de Córdoba", 1,284 members and a sidebar that leaves 280px.

Pick a scenario and watch what breaks.

*[Interactive demo: One row, five realistic cases, each failure with its one-line fix.]*

## Stress what can actually reach it

A stress scenario (One realistic but hostile input for a component: the longest real name, zero items, a 320px box. You render them side by side to see what breaks before users do) is one hostile but realistic input. The trick is to keep only the ones the component can really receive:

| Axis | Keep it when | Try |
|---|---|---|
| Content length | it shows text it didn't write | empty, one word, a paragraph, a long unbreakable URL |
| Content shape | users or other languages write the text | emoji, right-to-left, accents |
| Quantity | it repeats over items | zero, one, the usual count, ten times that |
| Container | always | 320px, squeezed by a sibling, very wide |
| State | it gets the state as a prop | loading, error, disabled |

A static button with a fixed label gets container widths, never long text.

**Every hostile value is realistic or the real limit.** A hyphenated, accented surname. A 60-character work email. The exact `maxLength` from the form. Random keyboard mash gets dismissed as "never happens", and the conversation ends.

## Read the failure signature

Most breaks have a known cause and a one-line cure:

| You see | Cause | Fix |
|---|---|---|
| avatar squished into an oval | a flex child shrinking | `flex-shrink: 0` |
| text pushes the menu out of the box | `min-width: auto` on the text column | `min-width: 0` |
| an email runs off the edge | no place to break | `overflow-wrap: anywhere` |
| "1 members", `NaN` | a hardcoded plural, a raw number | `Intl.PluralRules`, a null guard |
| 1,000 rows stutter | every row rendered | virtualize or paginate |

  **Step: Build the harness.** A throwaway page, scenarios side by side, each in a fixed-width box. Feed the data at the data boundary (the fetch or the hook), never by editing the component.

  **Step: Look once, report.** Name what broke and the node that owns each fix.

  **Step: Keep the worst-case fixture.** Delete the page; keep the data. It's the regression test for the next person.

Dark mode, browser zoom and reduced motion are the user's toggles. Name them for the person to check; don't fake them on the page.

*(Stress tests run isolated. Design variants run in context. Don't swap them.)*

**Break one component (paste into your agent):**

```text
Stress-test components/MemberRow.tsx.
List every value it renders with its real limit (schema, maxLength, API type); "unbounded" is a finding.
Build one worst-case fixture shaped like our demo data, plus Empty, One and 1,000 rows.
Render them side by side at 320px and our real width. Report what broke and the fix. Don't fix yet.
```

## The production baseline

Before launch, dozens of findings tend to share one root: the app never set its global floor. The production baseline (The global floor every app a phone will open needs: one viewport tag, a few root CSS rules, hover and reduced-motion gates, and dev tools kept out of the real build) is that floor. Land it once and whole classes of bugs vanish.

**In the head:**

- One viewport tag: `width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content`. Never `maximum-scale`; fix the 16px inputs instead.
- `<html lang>` set to the real language.
- Declare only the colour schemes you designed. A light-only app that says `color-scheme: light dark` gets dark native inputs on white cards.

**In the root CSS:**

- `font-synthesis: none`, so a weight that never loaded fails visibly instead of being faked.
- A `:focus-visible` outline, 2px, with `outline-offset: 2px`.
- Inputs at 16px on touch; `touch-action: manipulation` on controls.
- `text-wrap: balance` on headings, `pretty` on paragraphs.

**Two gates:** every `:hover` behind `(hover: hover) and (pointer: fine)`, and reduced motion handled where the shared durations live. **Dev tools** mount only in development, through a dynamic import, so their code stays out of the real bundle.

> **Merge, never append.** Most projects already have half of this. Add the missing lines to the existing rules; never create a second viewport tag or a second global stylesheet.

## The ship check

A review judges one change against a craft bar. A ship check (The last pass before launch: the whole app swept across nine lanes, each finding ranked by a severity tree, mechanical fixes applied, and what needs a real phone listed) sweeps the **whole app** against a ship bar and fixes as it goes. Its question is blunt: what breaks in the first hour?

It goes an inch deep on nine lanes: accessibility, performance, mobile, forms, states, motion, theme, leftovers (lorem, debug logs, secrets) and marketing (titles, link previews). Anything bigger than a few lines goes on a list for a human.

### The severity tree

Walk it top to bottom and stop at the first yes.

*[Interactive demo: Pick a finding. The first yes decides its severity.]*

Context can override a lane's default. A missing `alt` on the only product photo is a blocker. `100vh` on a desktop-only admin tool is nothing.

### Fix order

Accessibility blockers, then leaks, then the baseline (one change clears dozens), then per-file fixes, then polish while the diff is still readable in one sitting. Then run the typecheck and build. A ship check that breaks the build is worth less than none.

### Three ways to fail

1. **Calling it ready from a laptop.** Sticky hover, input zoom, the phone keyboard and safe areas never reproduce in a desktop browser. Say which checks need a real phone.
2. **A report where a diff belonged.** Sixty rows of advice isn't launch prep. Apply the mechanical fixes.
3. **Redesigning in launch week.** New palettes and re-timed motion are taste. Fix defects; taste goes on the human list.

"Already in good shape, small diff" is a valid, good result.

**Check yourself:** During a ship check you find cards losing their border, but only in the dark theme. Using the severity tree, what is it?

- [ ] Blocker, because it's visible
- [x] Should fix: it shows only on another theme
- [ ] Polish, because borders are decoration
- [ ] Not a finding: dark mode is the user's toggle

*Why:* It doesn't lock anyone out, a typical first session won't hit it, and it leaks nothing. It is visible only in another theme, so it is a Should fix.

## In the skill

- https://school.agents.org.in/skill/meta/stress-scenarios.md
- https://school.agents.org.in/skill/meta/production-baseline.md
- https://school.agents.org.in/skill/meta/ship-check.md
