Style Port — Narraitor Design System Porting
Port inline styles from a reference source (demo component, Figma spec, screenshot comparison) to production CSS through a systematic, code-level diff.
Hard Rules
- No
!important -- fix specificity at the selector level.
- No raw pixel values when a design token exists (
--space-*, --radius-*, --font-*). Token definitions live in src/lib/theme/themes/: spacing and --radius-full in _shared-tokens.css, --font-* and --radius-sm/md/lg in ds3.css.
- No demo-only UI -- do not port components that only exist in the reference scaffold (state switchers, mock data chrome, debug panels).
- No dark-mode changes -- stay in the base (light) rules. Do not touch
:root.dark variants unless the plan explicitly calls for it.
- No new inline styles -- the point is moving styles into CSS. Never add
style={{}} to fix a gap.
- One canon location per surface -- game-session styles go in
src/styles/manuscript-session.css. Other surfaces use their own canonical stylesheet. No component-level CSS modules for layout/theme styles.
- No DS-scoped overrides -- there's one design system since ADR-013.
data-theme="ds3" is a constant on <html>, not a switch, so don't write per-DS selector blocks; style the base rules directly.
- Merge, don't duplicate -- if a selector already exists, add properties to the existing block rather than creating a new one. Reference the fix number when extending existing blocks.
Phase 1: Inventory
Enumerate every inline style={{...}} prop in the reference component for the target theme/surface. Output a numbered list:
1. Container: { backdropFilter: 'blur(20px)', padding: '10px 10px 6px' }
2. SendButton: { height: 38, padding: '0 14px', fontWeight: 500 }
Also note any CSS classes in the reference that don't exist in the production stylesheet.
Phase 2: Map
For each inventoried style, find the production equivalent:
- Which CSS class targets the same element?
- Which properties are already declared?
- Are there
:root.dark overrides that partially cover it?
Output a mapping table:
| # |
Reference Property |
Production Selector |
Current Value |
Gap? |
Phase 3: Diff
For each gap, determine the exact CSS declaration:
- Convert px to the nearest design token or rem.
- Dark-only rules go under
:root.dark.
- Everything else goes unscoped — there's one design system, so base rules are the default.
- If an existing block partially covers the fix, merge into it.
Output a numbered fix list with CSS.
Phase 4: Port
Apply CSS changes to the production stylesheet:
- Add new rules in the correct section (base rules, then any
:root.dark overrides).
- Merge into existing rule blocks when the selector already exists.
- Keyframes go near other
@keyframes definitions.
- Maintain section order within the file.
Phase 5: Clean
Remove inline styles from production React components:
- Delete
style={{...}} props now covered by CSS.
- Replace conditional style props with class-based targeting (parent class selectors).
- Add any new CSS classes needed to support the class-based approach.
Phase 6: Verify
Visual Audit with dev-browser
Code-level diffs catch explicit property gaps but miss inherited-style artifacts (e.g., a wrapper div inheriting a larger line-height, inflating badge height). After porting, use the dev-browser skill to:
- Float the browser window -- AeroSpace tiles Chromium by default, constraining the viewport and breaking media query tests. After the dev-browser server starts, run:
aerospace layout floating (targets the focused window). Verify with window.innerWidth in a script before relying on breakpoint-dependent CSS.
- Inject both demo and production markup into the same page, in the color scheme you're porting for.
- Screenshot side-by-side for visual comparison.
- Extract computed styles from matching elements and diff key metrics: height, padding, margin, font-size, line-height, color.
- Fix discrepancies found only through computed-style comparison (not visible in source code).
This step catches the class of bugs where CSS inheritance produces different rendered output even when the declared properties appear identical.
Workflow
- Run once against ds3, then re-check in dark mode if the port touched color.
- Each invocation produces a discrete set of numbered fixes.
- Deferred items (error states, fundamentally different UX patterns, demo-only chrome) are called out but not ported.
1---2name: style-port3description: Port inline styles from a reference/demo component to production CSS in the Narraitor design system. Rigid 6-phase process that diffs inline styles against production stylesheets, ports gaps using design tokens, and removes inline styles from production components. Use when porting design system styles from any reference source to the app.4---56# Style Port — Narraitor Design System Porting78Port inline styles from a reference source (demo component, Figma spec, screenshot comparison) to production CSS through a systematic, code-level diff.910## Hard Rules1112- **No `!important`** -- fix specificity at the selector level.13- **No raw pixel values** when a design token exists (`--space-*`, `--radius-*`, `--font-*`). Token definitions live in `src/lib/theme/themes/`: spacing and `--radius-full` in `_shared-tokens.css`, `--font-*` and `--radius-sm/md/lg` in `ds3.css`.14- **No demo-only UI** -- do not port components that only exist in the reference scaffold (state switchers, mock data chrome, debug panels).15- **No dark-mode changes** -- stay in the base (light) rules. Do not touch `:root.dark` variants unless the plan explicitly calls for it.16- **No new inline styles** -- the point is moving styles into CSS. Never add `style={{}}` to fix a gap.17- **One canon location per surface** -- game-session styles go in `src/styles/manuscript-session.css`. Other surfaces use their own canonical stylesheet. No component-level CSS modules for layout/theme styles.18- **No DS-scoped overrides** -- there's one design system since ADR-013. `data-theme="ds3"` is a constant on `<html>`, not a switch, so don't write per-DS selector blocks; style the base rules directly.19- **Merge, don't duplicate** -- if a selector already exists, add properties to the existing block rather than creating a new one. Reference the fix number when extending existing blocks.2021## Phase 1: Inventory2223Enumerate every inline `style={{...}}` prop in the reference component for the target theme/surface. Output a numbered list:2425```261. Container: { backdropFilter: 'blur(20px)', padding: '10px 10px 6px' }272. SendButton: { height: 38, padding: '0 14px', fontWeight: 500 }28```2930Also note any CSS classes in the reference that don't exist in the production stylesheet.3132## Phase 2: Map3334For each inventoried style, find the production equivalent:35- Which CSS class targets the same element?36- Which properties are already declared?37- Are there `:root.dark` overrides that partially cover it?3839Output a mapping table:4041| # | Reference Property | Production Selector | Current Value | Gap? |42|---|-------------------|---------------------|---------------|------|4344## Phase 3: Diff4546For each gap, determine the exact CSS declaration:47- Convert px to the nearest design token or rem.48- Dark-only rules go under `:root.dark`.49- Everything else goes unscoped — there's one design system, so base rules are the default.50- If an existing block partially covers the fix, merge into it.5152Output a numbered fix list with CSS.5354## Phase 4: Port5556Apply CSS changes to the production stylesheet:57- Add new rules in the correct section (base rules, then any `:root.dark` overrides).58- Merge into existing rule blocks when the selector already exists.59- Keyframes go near other `@keyframes` definitions.60- Maintain section order within the file.6162## Phase 5: Clean6364Remove inline styles from production React components:65- Delete `style={{...}}` props now covered by CSS.66- Replace conditional style props with class-based targeting (parent class selectors).67- Add any new CSS classes needed to support the class-based approach.6869## Phase 6: Verify7071- [ ] `npx stylelint` on the modified stylesheet -- clean72- [ ] `npx tsc --noEmit` -- clean (no type errors from removed style props)73- [ ] Relevant Jest tests pass74- [ ] Dark mode unaffected (any dark-only rule is scoped to `:root.dark`; no stray `[data-theme]` blocks)75- [ ] No `!important` introduced76- [ ] No raw pixel values where tokens exist77- [ ] No remaining inline `style=` props for ported properties78- [ ] **Visual audit with dev-browser** -- render demo markup and production markup side-by-side under the target theme, compare computed styles (height, padding, margin, color, font) to catch inherited-style artifacts that code-level diff misses7980## Visual Audit with dev-browser8182Code-level diffs catch explicit property gaps but miss inherited-style artifacts (e.g., a wrapper div inheriting a larger line-height, inflating badge height). After porting, use the `dev-browser` skill to:83840. **Float the browser window** -- AeroSpace tiles Chromium by default, constraining the viewport and breaking media query tests. After the dev-browser server starts, run: `aerospace layout floating` (targets the focused window). Verify with `window.innerWidth` in a script before relying on breakpoint-dependent CSS.851. **Inject both demo and production markup** into the same page, in the color scheme you're porting for.862. **Screenshot side-by-side** for visual comparison.873. **Extract computed styles** from matching elements and diff key metrics: height, padding, margin, font-size, line-height, color.884. **Fix discrepancies** found only through computed-style comparison (not visible in source code).8990This step catches the class of bugs where CSS inheritance produces different rendered output even when the declared properties appear identical.9192## Workflow9394- Run once against ds3, then re-check in dark mode if the port touched color.95- Each invocation produces a discrete set of numbered fixes.96- Deferred items (error states, fundamentally different UX patterns, demo-only chrome) are called out but not ported.