Appearance
Refactoring
Restructure code without changing behavior. Make one small change at a time, verify after each, commit or revert. Never be more than one step from a working system.
The loop
- Establish verification — set up a mechanism that catches regressions (tests, screenshot diffs, type checks, whatever fits the change)
- Verify green — confirm the baseline passes before touching anything
- Make the smallest possible change — one file, one concept, one moving part
- Verify — run the verification mechanism
- Pass → commit. Fail → investigate or revert.
- Repeat
Choosing a verification mechanism
Match the mechanism to what you're changing:
| Change | Mechanism |
|---|---|
| Internal logic, APIs | Unit/integration tests |
| Visual layout, CSS, DOM structure | Screenshot diff tests |
| Type system, module boundaries | Type checker |
| Build output, bundle size | Build + size check |
| Database queries | Query result assertions |
Multiple mechanisms can layer. A light DOM refactor might use screenshot diffs for visual correctness and a build step to catch import errors.
Investigating failures
When verification fails, don't guess. Locate the exact difference:
- Tests: read the assertion message — it tells you expected vs actual
- Screenshots: generate a pixel-level diff image to see exactly what moved and where (use pixelmatch or similar)
- Types: the compiler points to the exact line
If the change was truly small (one concept), the cause is usually obvious. If it's not obvious, the change was too big — revert and split it smaller.
Order matters
When converting a system from one architecture to another, work outside-in:
- Convert outer containers before inner ones (a child component can't receive global styles if its parent still encapsulates them)
- Migrate callers before implementations (or use an adapter during transition)
- Move data before removing old schemas
Each ordering decision follows the same question: which direction keeps the system working at every step?
Example: shadow DOM to light DOM
Converting three web components from shadow DOM to light DOM:
- Establish verification — screenshot diff tests covering 11 app states
- Convert outermost component first (unicode-picker) — inner components still work because their shadow DOM is self-contained
- Convert inner component (char-grid) — fails screenshot diff due to
box-sizingdifference. Revert. Split into smaller steps:- Add CSS file alongside shadow DOM styles (verify: pass)
- Remove shadow DOM (verify: fail —
box-sizingshift) - Add
box-sizing: content-boxto affected element (verify: pass) - Remove redundant shadow DOM styles (verify: pass)
- Convert simplest component last (copy-toast) — trivial, passes
The key insight: the first attempt at step 3 made all changes at once. When it failed, the cause wasn't obvious. Reverting and splitting into sub-steps isolated the box-sizing issue immediately.