Subchapter 12.1
references/audit.mdMarkdown8 KBView on GitHub
Measure how far an existing Expo app has drifted from a single visual source of truth, then document or extend components with a consistent template.
Run the audit before proposing changes. Report findings first; apply fixes only when asked.
Do not assume a src/ folder. The default create-expo-app template puts app/, components/, and constants/ at the repo root, and running the checks below against a nonexistent src/ returns zero hits - a false clean bill.
Before any grep, identify and record:
src, or e.g. app components constants hooks).src/theme, theme, constants, or a single theme.ts). If none exists yet, note that as the first finding.package.json (NativeWind, Tamagui, Restyle, Unistyles, styled-components) per SKILL.md “Adopt Before You Build”. Record which one. The checks below assume this skill’s defaults - plain StyleSheet, boxShadow strings, one src/theme entry point. An app on a styling library fails several of them while being perfectly consistent in its own idiom. Audit such an app against its own system: its token names, its scale, its config file as the single entry point. Adapt or skip a check rather than reporting a false violation.Substitute them into the shell variables below; every check uses them.
SRC="app components constants" # this project's source directories
THEME="constants" # this project's theme directory (excluded from hits)Run from the repo root. Each hit outside the theme directory is a candidate for tokenization - not automatically a violation (check for the “one-off, with a comment” exemption in SKILL.md).
# Hardcoded hex colors outside the theme
grep -rEn '#[0-9a-fA-F]{3,8}\b' $SRC --include='*.tsx' --include='*.ts' | grep -v "^$THEME/"
# Raw fontSize (should come from the type ramp / ThemedText)
grep -rn 'fontSize:' $SRC --include='*.tsx' | grep -v "^$THEME/"
# Spacing values outside the named steps of the scale.
# The whitelist is the project's actual scale (here: the example scale from
# SKILL.md) - substitute the project's steps before running. Multiples of 4
# that are not named steps (12, 20, 40, ...) are flagged on purpose: the rule
# is "use the nearest step". If one keeps recurring, the fix is to add it to
# the scale and to this whitelist, not to ignore the hits.
grep -rEn '(padding|margin|gap)[A-Za-z]*:\s*[0-9]+' $SRC --include='*.tsx' \
| grep -vE ':\s*(0|4|8|16|24|32|48)\b' | grep -v "^$THEME/"
# Raw borderRadius (should use radius tokens)
grep -rn 'borderRadius:' $SRC --include='*.tsx' | grep -v "^$THEME/"
# Legacy shadows (banned by expo-native-ui - must be boxShadow)
grep -rEn 'shadow(Color|Offset|Opacity|Radius)|elevation:' $SRC --include='*.tsx'
# Multiple theme entry points (there must be exactly one)
ls src/theme.ts src/theme/index.ts theme.ts theme/index.ts constants/theme.ts 2>/dev/nullFor a Tailwind project (expo-tailwind-setup), also check for values that bypass global.css variables: arbitrary-value classes like p-[13px] or text-[#5B21B6].
grep -rEn 'className="[^"]*\[[^"]*\]' $SRC --include='*.tsx'Turn raw hit counts into a comparable score so runs can be tracked over time:
# Source lines of code (the denominator)
find $SRC -name '*.tsx' -o -name '*.ts' | xargs wc -l | tail -1For each category: score = escapes per 100 source lines (hits ÷ SLOC × 100, one decimal).
| Score per category | Reading |
|---|---|
| < 0.5 | Healthy - fix opportunistically |
| 0.5 - 2.0 | Drifting - schedule cleanup for the worst files |
| > 2.0 | Systemic - the token or component for this category is missing or unused; fix the system first (see §5) |
Report the per-category scores in the summary. The overall priority order falls out of the scores: the highest-scoring category is usually the first migration target.
For each component in the shared components directory (src/components/, or components/ in a root-level layout), check it against the contract in SKILL.md:
| Check | Pass condition |
|---|---|
| Variants | Visual intent is a variant prop, not boolean soup (isPrimary, isGhost) |
| Sizes | Sizes map to spacing/typography tokens |
| Pressed state | Tappable components give pressed feedback via a Pressable style function |
| Disabled / loading | Handled, and disabled blocks onPress |
| Style override | Accepts style, merged last |
| Accessibility | accessibilityRole set; touch target ≥ 44pt |
| Tokens only | No literals that duplicate a theme value |
## Design System Audit
### Summary
Screens reviewed: [X] | Components reviewed: [X] | Issues: [X]
### Token coverage
| Category | Tokens defined | Escapes found | Score (per 100 SLOC) | Worst offenders |
|---|---|---|---|---|
| Colors | [X] | [X] hardcoded hex | [X.X] | [files] |
| Spacing | [X] | [X] non-step values | [X.X] | [files] |
| Typography | [X] | [X] raw fontSize | [X.X] | [files] |
| Radius / shadows / motion | [X] | [X] | [X.X] | [files] |
### Component completeness
| Component | Variants | States | Overrides | Tokens | Notes |
|---|---|---|---|---|---|
| Button | OK | missing pressed | OK | OK | ... |
### Extraction candidates
Views repeated across ≥2 screens that are still colocated or duplicated:
1. [view] - appears in [screens] - suggested name: [Component]
### Priority actions
1. [Highest-leverage fix - usually the most-duplicated escaped value]
2. ...An app with dozens of escapes is migrated in order, never big-bang. A big-bang conversion produces one huge unreviewable diff and usually stalls half-done.
fontSize to the type ramp + ThemedText first: it is the highest-visibility win and touches the fewest layout decisions. Then spacing, then colors, then radius/shadows.## Component: [Name]
[What it is and when to use it - one paragraph.]
### Variants
| Variant | Use when |
|---|---|
| primary | The screen's single main action |
### Props
| Prop | Type | Default | Notes |
|---|---|---|---|
### States
default / pressed / disabled / loading - visual + behavior for each.
### Accessibility
Role, touch target, screen reader label.
### Do / Don't
| Do | Don't |
|---|---|
| [best practice] | [anti-pattern seen in this repo] |Before designing a new primitive, prove the existing set can’t cover it:
## Proposed: [Name]
### Problem
[The repeated need, and the ≥2 screens that have it.]
### Why existing components aren't enough
| Closest component | What's shared | What's missing |
|---|---|---|
### API
Props table (variant / size / state / style only - content via children).
### Tokens used
Colors: [...] Spacing: [...] Typography: [...] Radius: [...]
### Open questions
[Decisions that need a human, e.g. does this need a destructive variant?]If the “what’s missing” column is empty for any row, extend that component’s variants instead of adding a new one.