DS PR Review — Aetheron Design System
Step 1: Gather Context (parallel)
gh pr view <number> --json title,body,headRefName,files
gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/comments
gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/reviews
gh api repos/aetheronhq/aetheron-design-system/issues/<number>/comments
gh pr diff <number>
Switch to the PR branch before reading code:
git checkout <headRefName> && git pull origin <headRefName>
Filter out bots: coderabbitai[bot], github-actions[bot], any "type": "Bot".
Step 2: Read Project Rules
Always read agent.md — single source of truth for:
- Core philosophy (minimal wrapper DS, DaisyUI v5 CSS classes, no react-daisyui)
- Approved component list
- TypeScript strictness requirements
- Color architecture (Primitive → Semantic → Component)
- File naming conventions
- Accessibility requirements (WCAG 2.1 AA)
Then based on scope, read relevant docs:
| PR touches... |
Read |
| Colors / theming |
docs/foundations/color-system.md, docs/guides/multi-brand-theming.md |
| Typography |
docs/foundations/typography.md |
| Layout / spacing |
docs/foundations/spacing-layout.md |
| New component |
docs/CONTRIBUTING.md |
| CSS classes |
docs/foundations/css-class-reference.md |
| Form components |
docs/patterns/form-patterns.md |
Step 3: Review Checklist
Component Code (*.tsx)
| Check |
What to look for |
| Prop types |
Discriminated unions for coupled props (e.g., isStreaming: true requires onStop). No flat optional footguns. Use undefined not never for excluded branches. |
No any |
Strict TypeScript — no any, ts-ignore, eslint-disable. |
| HTML passthrough |
extends ComponentPropsWithoutRef<'element'>, spread ...rest last. |
| Class merging |
clsx('base', variantClass, className) — consumer className always wins. |
| DaisyUI specificity |
btn-outline forces primary border color; border-base-300 cannot override. Use btn-ghost border border-base-300 for grey borders. |
| Cursor bleed |
cursor-not-allowed on parent <label> bleeds to child buttons. Add cursor-pointer on nested clickable elements if needed. |
| forwardRef |
Components wrapping a single DOM element should forward ref. |
| Minimal logic |
Components are thin wrappers. No heavy business logic. |
| YAGNI |
Don't add defensive checks for hypothetical edge cases. Only add guards for observed production issues. |
DaisyUI & Theming
| Check |
What to look for |
| No react-daisyui |
All components use native HTML + DaisyUI CSS classes. |
| Color layers |
Never use --primitive-* directly. Use --semantic-* or DaisyUI class names. |
| Shared CSS vars |
Changes to --radius-*, --size-* affect multiple DaisyUI components. Verify cross-component impact. |
| Dark mode |
If adding colours, ensure semantic tokens flip correctly in dark mode. |
Storybook Stories (*.stories.tsx)
| Check |
What to look for |
| Timer cleanup |
setTimeout/setInterval must be cleared — useRef + clearTimeout on stop, re-trigger, and useEffect cleanup. Stale timers cause state updates on unmounted components. |
| Play functions |
Interactive guarantees (e.g., "Enter is blocked during streaming") need play functions, not just visual stories. |
| Import path |
Use storybook/test (Storybook v9), not @storybook/test. |
| Module-level mocks |
fn() at module level; mockClear() inside play. |
| Union narrowing |
Discriminated union props can't be spread via args — use conditional render or as const. |
Icons
| Check |
What to look for |
| Short form |
name="ArrowUp" not name="IconArrowUp". |
| Existence |
Icon silently returns null for unknown names. Verify against @tabler/icons-react exports. |
Accessibility
| Check |
What to look for |
| WCAG 2.1 AA |
All components must pass Level AA. |
| Touch targets |
≥44px for all interactive elements. |
| aria-label |
Icon-only buttons must have aria-label. |
| Keyboard |
All interactive elements reachable via keyboard. Focus visible. |
| Screen reader |
State changes (e.g., send → stop button) need discoverable feedback. |
General Quality
| Check |
What to look for |
| File naming |
PascalCase for components, camelCase for utils, kebab-case for folders. |
| Barrel exports |
New components exported from category index.ts and root index.ts. |
| No dead code |
Remove unused imports, unreachable branches, commented-out code. |
| Backward compat |
New optional props should not break existing consumers. |
Step 4: Severity Classification
| Category |
Criteria |
Action |
| Bug |
Incorrect behaviour, type unsafety, stale timer |
Must fix |
| Contract mismatch |
Props/docs contradiction, silent footgun |
Must fix |
| Valid improvement |
DRY, specificity, consistency — simple fix |
Recommend fix |
| A11y gap |
Missing aria, keyboard trap, no feedback |
Recommend fix |
| Test gap |
Key behaviour not covered by play/test |
Recommend fix |
| Nitpick |
Style preference, hypothetical concern |
Skip or note |
Step 5: Present Summary
Present analysis before making any changes. Format per comment:
**Comment N: {title}**
> {quote}
- Category: {Bug / Valid improvement / Nitpick}
- Analysis: {assessment}
- Recommendation: {Fix / Skip}
Summary table at the end. Ask for confirmation before implementing fixes.
Step 6: Post Review to GitHub
Use gh api to post as a single review with inline comments:
COMMIT=$(gh api repos/aetheronhq/aetheron-design-system/pulls/<number> --jq '.head.sha')
gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/reviews \
--method POST \
--field commit_id="$COMMIT" \
--field event="COMMENT" \
--field 'body=Review summary' \
--input - <<'PAYLOAD'
{ "comments": [{ "path": "...", "line": N, "side": "RIGHT", "body": "..." }] }
PAYLOAD
Reply Conventions
- Before / After format for fixed items.
- Concise, polite, logical. Reference
agent.md rules when relevant.
- For skipped items, explain reasoning clearly.
Verification Commands
pnpm lint # ESLint + Prettier
pnpm build # TypeScript build
pnpm test:a11y # Accessibility tests (requires Storybook running)
1---2name: ds-pr-review3description: Review pull requests for the Aetheron Design System (aetheron-design-system). Checks component code, DaisyUI usage, TypeScript patterns, Storybook stories, a11y compliance, and theming correctness. Use when reviewing a PR in the design system repo, or when the user shares a DS PR URL and asks to review it.4---56# DS PR Review — Aetheron Design System78## Step 1: Gather Context (parallel)910```bash11gh pr view <number> --json title,body,headRefName,files12gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/comments13gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/reviews14gh api repos/aetheronhq/aetheron-design-system/issues/<number>/comments15gh pr diff <number>16```1718Switch to the PR branch before reading code:19```bash20git checkout <headRefName> && git pull origin <headRefName>21```2223Filter out bots: `coderabbitai[bot]`, `github-actions[bot]`, any `"type": "Bot"`.2425## Step 2: Read Project Rules2627**Always read `agent.md`** — single source of truth for:28- Core philosophy (minimal wrapper DS, DaisyUI v5 CSS classes, no react-daisyui)29- Approved component list30- TypeScript strictness requirements31- Color architecture (Primitive → Semantic → Component)32- File naming conventions33- Accessibility requirements (WCAG 2.1 AA)3435**Then based on scope**, read relevant docs:3637| PR touches... | Read |38|---------------|------|39| Colors / theming | `docs/foundations/color-system.md`, `docs/guides/multi-brand-theming.md` |40| Typography | `docs/foundations/typography.md` |41| Layout / spacing | `docs/foundations/spacing-layout.md` |42| New component | `docs/CONTRIBUTING.md` |43| CSS classes | `docs/foundations/css-class-reference.md` |44| Form components | `docs/patterns/form-patterns.md` |4546## Step 3: Review Checklist4748### Component Code (`*.tsx`)4950| Check | What to look for |51|-------|-----------------|52| **Prop types** | Discriminated unions for coupled props (e.g., `isStreaming: true` requires `onStop`). No flat optional footguns. Use `undefined` not `never` for excluded branches. |53| **No `any`** | Strict TypeScript — no `any`, `ts-ignore`, `eslint-disable`. |54| **HTML passthrough** | `extends ComponentPropsWithoutRef<'element'>`, spread `...rest` last. |55| **Class merging** | `clsx('base', variantClass, className)` — consumer `className` always wins. |56| **DaisyUI specificity** | `btn-outline` forces primary border color; `border-base-300` cannot override. Use `btn-ghost border border-base-300` for grey borders. |57| **Cursor bleed** | `cursor-not-allowed` on parent `<label>` bleeds to child buttons. Add `cursor-pointer` on nested clickable elements if needed. |58| **forwardRef** | Components wrapping a single DOM element should forward ref. |59| **Minimal logic** | Components are thin wrappers. No heavy business logic. |60| **YAGNI** | Don't add defensive checks for hypothetical edge cases. Only add guards for observed production issues. |6162### DaisyUI & Theming6364| Check | What to look for |65|-------|-----------------|66| **No react-daisyui** | All components use native HTML + DaisyUI CSS classes. |67| **Color layers** | Never use `--primitive-*` directly. Use `--semantic-*` or DaisyUI class names. |68| **Shared CSS vars** | Changes to `--radius-*`, `--size-*` affect multiple DaisyUI components. Verify cross-component impact. |69| **Dark mode** | If adding colours, ensure semantic tokens flip correctly in dark mode. |7071### Storybook Stories (`*.stories.tsx`)7273| Check | What to look for |74|-------|-----------------|75| **Timer cleanup** | `setTimeout`/`setInterval` must be cleared — `useRef` + `clearTimeout` on stop, re-trigger, and `useEffect` cleanup. Stale timers cause state updates on unmounted components. |76| **Play functions** | Interactive guarantees (e.g., "Enter is blocked during streaming") need `play` functions, not just visual stories. |77| **Import path** | Use `storybook/test` (Storybook v9), not `@storybook/test`. |78| **Module-level mocks** | `fn()` at module level; `mockClear()` inside `play`. |79| **Union narrowing** | Discriminated union props can't be spread via args — use conditional render or `as const`. |8081### Icons8283| Check | What to look for |84|-------|-----------------|85| **Short form** | `name="ArrowUp"` not `name="IconArrowUp"`. |86| **Existence** | `Icon` silently returns `null` for unknown names. Verify against `@tabler/icons-react` exports. |8788### Accessibility8990| Check | What to look for |91|-------|-----------------|92| **WCAG 2.1 AA** | All components must pass Level AA. |93| **Touch targets** | ≥44px for all interactive elements. |94| **aria-label** | Icon-only buttons must have `aria-label`. |95| **Keyboard** | All interactive elements reachable via keyboard. Focus visible. |96| **Screen reader** | State changes (e.g., send → stop button) need discoverable feedback. |9798### General Quality99100| Check | What to look for |101|-------|-----------------|102| **File naming** | PascalCase for components, camelCase for utils, kebab-case for folders. |103| **Barrel exports** | New components exported from category `index.ts` and root `index.ts`. |104| **No dead code** | Remove unused imports, unreachable branches, commented-out code. |105| **Backward compat** | New optional props should not break existing consumers. |106107## Step 4: Severity Classification108109| Category | Criteria | Action |110|----------|----------|--------|111| **Bug** | Incorrect behaviour, type unsafety, stale timer | Must fix |112| **Contract mismatch** | Props/docs contradiction, silent footgun | Must fix |113| **Valid improvement** | DRY, specificity, consistency — simple fix | Recommend fix |114| **A11y gap** | Missing aria, keyboard trap, no feedback | Recommend fix |115| **Test gap** | Key behaviour not covered by play/test | Recommend fix |116| **Nitpick** | Style preference, hypothetical concern | Skip or note |117118## Step 5: Present Summary119120Present analysis before making any changes. Format per comment:121122```markdown123**Comment N: {title}**124> {quote}125- Category: {Bug / Valid improvement / Nitpick}126- Analysis: {assessment}127- Recommendation: {Fix / Skip}128```129130Summary table at the end. Ask for confirmation before implementing fixes.131132## Step 6: Post Review to GitHub133134Use `gh api` to post as a single review with inline comments:135136```bash137COMMIT=$(gh api repos/aetheronhq/aetheron-design-system/pulls/<number> --jq '.head.sha')138139gh api repos/aetheronhq/aetheron-design-system/pulls/<number>/reviews \140 --method POST \141 --field commit_id="$COMMIT" \142 --field event="COMMENT" \143 --field 'body=Review summary' \144 --input - <<'PAYLOAD'145{ "comments": [{ "path": "...", "line": N, "side": "RIGHT", "body": "..." }] }146PAYLOAD147```148149### Reply Conventions150151- **Before / After** format for fixed items.152- Concise, polite, logical. Reference `agent.md` rules when relevant.153- For skipped items, explain reasoning clearly.154155## Verification Commands156157```bash158pnpm lint # ESLint + Prettier159pnpm build # TypeScript build160pnpm test:a11y # Accessibility tests (requires Storybook running)161```