← Back to skills
extension
Category: Development & EngineeringAPI key requirement unconfirmed

argus-design-review

Use when reviewing frontend code for design quality — checking design token usage, hardcoded values, dark mode coverage, accessibility compliance, CSS consistency, semantic HTML, or framework API usage. Use when auditing a component, page, or design system for issues. Trigger phrases: '帮我 review 这段代码'、'检查一下这个组件的设计问题'、'看看有没有 hardcoded values'、'dark mode 有没有遗漏'、'无障碍有没有问题'、'帮我做个 design audit'、'让 Argus-Flash 审一下'

personAuthor: cgartlabhubgithub

Argus Design Review Skill

When this skill is active, every line of frontend code is audited against the same standards: design tokens used correctly, no hardcoded values, dark mode fully covered, accessibility baseline met, correct API usage per technology stack, and copy-ready code fixes provided for every issue.

Men Agent Team Invocation (Optional)

Argus can be invoked by the men agent team (cgartlab/men) as an optional review capability. This does not change standalone behavior — Argus runs identically with or without men, in any agent framework or as the argus-flash GitHub App.

Men-Specific Trigger Phrases

The standard triggers above already cover review requests. Additional men-specific phrases:

  • "design review for men"
  • "Argus review"
  • "前端审查"
  • "run Argus on this PR"
  • "让 Argus 审一下这段前端代码"

When invoked from a men context (via --men-context flag or MEN_CONTEXT=1 env), Argus MAY frame its report for men's four-part summary template. See docs/men-integration.md.

Report Format for Men

  • The machine-readable [P#] file:line prefix is preserved verbatim — men parsers rely on it.
  • Severity grouping (P0 → P3) and mandatory copy-ready fixes remain unchanged.
  • Men's four-part summary is mapped from the standard Argus output:

| Men template slot | Argus output section | |-------------------|----------------------| | conclusion | Summary header (totals, stack, documentation) | | key issues | Issue blocks grouped by severity | | evidence | Found / Expected snippets + Reference links | | open questions | Unresolved items (see Confidence rule) |

Confidence Rule

If the technology stack or the target file is not clearly identifiable, do not guess. List the item as unresolved in the open-questions section instead of flagging it with a fabricated severity or stack. This mirrors the men team's "clarify before acting" rule: unclear context is reported, never assumed.

Technology Stack Detection

Before reviewing, detect the project's technology stack:

| Indicator | Stack | Documentation | |-----------|-------|---------------| | *.tsx, *.jsx + react in package.json | React | https://react.dev, https://reactjs.org/docs | | *.vue | Vue | https://vuejs.org/guide | | *.svelte | Svelte | https://svelte.dev/docs | | angular.json | Angular | https://angular.dev/api | | *.astro | Astro | https://docs.astro.build | | lit-*.js / *.ts with lit imports | Lit | https://lit.dev/docs | | uno.config.ts / unocss.config.ts | UnoCSS | https://uno.antfu.me/ | | tailwind.config.js / tailwind.config.ts | Tailwind CSS | https://tailwindcss.com/docs | | CSS/SCSS files only | Vanilla CSS | https://developer.mozilla.org/docs/Web/CSS |

Detection workflow:

  1. Check file extensions of files to review
  2. Read package.json to confirm framework and version
  3. Check for config files (vite.config.js, tsconfig.json, etc.)
  4. Check for atomic CSS configs (uno.config.*, tailwind.config.*) — if present, review design token references inside utility classes (e.g. bg-*, px-*, text-*)
  5. Map stack to official documentation base URL

Review Dimensions

1. Design Tokens

Rule: Every color in component rules must be a var(--ds-*) reference. No bare oklch(), #hex, or rgb().

/* WRONG — bare oklch in component rule */
.ds-card {
  background: oklch(99% 0.005 80);
  color: oklch(20% 0.02 60);
}

/* RIGHT — token reference */
.ds-card {
  background: var(--ds-color-surface);
  color: var(--ds-color-fg);
}

Exception: Token declarations in :root and @keyframes may use bare oklch/hex.

Flag: Any occurrence of bare color value in component rules (CSS or inline style=).

2. Hardcoded Values

Rule: All spacing, radii, and type scale values must use design token scale. No magic numbers.

/* WRONG */
padding: 16px;
border-radius: 8px;

/* RIGHT */
padding: var(--ds-space-4);
border-radius: var(--ds-radius-lg);

Flag: Any numeric value (not 0) that should be a design token but isn't.

3. Dark Mode Coverage

Rule: Every color token declared in :root must have a [data-theme="dark"] override.

/* WRONG — no dark override */
:root {
  --ds-color-bg: oklch(97% 0.012 80);
}

/* RIGHT — override exists */
[data-theme="dark"] {
  --ds-color-bg: oklch(15% 0.008 75);
}

Flag: Any :root color token without a [data-theme="dark"] override. This is a silent dark mode break — colors may become unreadable.

4. Accessibility

Rule: WCAG AA baseline. Mandatory, never demoted to warning.

| Pattern | Requirement | |---|---| | Icon-only <button> | aria-label present | | <img> | alt attribute present | | <a> without href | Not used as a button; use <button> | | Focusable elements | Visible focus indicator | | Color contrast | 4.5:1 for normal text, 3:1 for large text |

Flag: Any violation is P1 minimum.

5. CSS Quality

  • No duplicate rules in same selector block
  • No empty catch {} blocks
  • Valid BEM (no dangling modifiers like .parent a--active)
  • No invalid HTML id duplicates

6. HTML Structure

  • No <a> tags without href used as interactive elements
  • Semantic elements used correctly (<button> for actions, <a> for links)

7. Framework API Usage (Stack-Aware)

Rule: Use framework APIs correctly per official documentation. See Framework Anti-Patterns Library below for specific patterns per framework.

React:

  • Check hooks usage: useState, useEffect, useCallback, useMemo deps arrays
  • Verify useEffect cleanup functions present when needed
  • Check deprecated API usage (e.g., React.createClass, UNSAFE_ lifecycles)
  • Validate forwardRef, memo usage patterns
  • Reference: https://react.dev/reference
/* WRONG — missing deps array */
useEffect(() => {
  fetchData(id);
}, []);

/* RIGHT — deps array matches */
useEffect(() => {
  fetchData(id);
}, [id]);

Vue:

  • Check Composition API vs Options API consistency
  • Verify ref vs reactive usage
  • Check watch vs watchEffect proper usage
  • Validate lifecycle hook names (onMounted, not mounted)
  • Reference: https://vuejs.org/guide/essentials

Angular:

  • Check reactive forms vs template-driven forms
  • Verify dependency injection patterns
  • Check lifecycle hooks (ngOnInit, ngOnDestroy)
  • Validate RxJS subscription cleanup
  • Reference: https://angular.dev/guide

Svelte:

  • Check $: reactivity declarations
  • Verify store subscriptions ($store)
  • Check onMount cleanup
  • Reference: https://svelte.dev/docs

Astro:

  • Check component directives (client:*)
  • Verify props typing with Props interface
  • Check .astro vs .jsx component boundaries
  • Reference: https://docs.astro.build

General JavaScript/TypeScript:

  • Check async/await error handling
  • Verify TypeScript type annotations
  • Check null/undefined handling
  • Reference: https://www.typescriptlang.org/docs/, https://developer.mozilla.org/docs/Web/JavaScript

Framework Anti-Patterns Library

Comprehensive pattern catalog for each framework with detection rules, examples, and fixes.

React Anti-Patterns

1. Missing useEffect Dependencies (P1)

Detection: useEffect( followed by variable without it in deps array Reference: https://react.dev/reference/react/useEffect#specifying-reactive-dependencies

// WRONG
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, []); // Missing: firstName, lastName

// RIGHT
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

2. Async useEffect Without IIFE (P1)

Detection: useEffect( with async keyword before arrow function Reference: https://react.dev/reference/react/useEffect#fetching-data-with-effects

// WRONG — useEffect cannot return a promise
useEffect(async () => {
  const data = await fetchUser(id);
  setUser(data);
}, [id]);

// RIGHT — use IIFE or separate function
useEffect(() => {
  const fetchUser = async () => {
    const data = await fetch(`/api/users/${id}`);
    setUser(data);
  };
  fetchUser();
}, [id]);

3. Inline Object/Array in JSX (P2)

Detection: JSX attribute with inline {} object or [] array Reference: https://react.dev/learn/keeping-components-pure

// WRONG — new object/array on every render
<div style={{ color: 'red' }} />
<Child items={['a', 'b']} />

// RIGHT — move outside component or use useMemo
const buttonStyle = { color: 'red' };
const items = ['a', 'b'];
<div style={buttonStyle} />
<Child items={items} />

4. Missing Key in List (P1)

Detection: .map() without key prop on returned element Reference: https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key

// WRONG — missing key
users.map(user => <UserCard name={user.name} />)

// RIGHT — use stable unique id
users.map(user => <UserCard key={user.id} name={user.name} />)

5. Stale Closure in Callbacks (P1)

Detection: Function referencing state/props without proper dependency Reference: https://react.dev/learn/avoiding-re-renders

// WRONG — count is stale
const handleClick = () => {
  setCount(count + 1); // May use stale value
};

// RIGHT — use functional update
const handleClick = () => {
  setCount(prev => prev + 1);
};

6. Unnecessary Re-renders (P2)

Detection: Component passing new object/function as prop without memoization Reference: https://react.dev/reference/react/memo

// WRONG — new function every render
const Parent = () => {
  return <Child onClick={() => console.log(clicked)} />;
};

// RIGHT — memoize callback
const Parent = () => {
  const handleClick = useCallback(() => {
    console.log(clicked);
  }, [clicked]);
  return <Child onClick={handleClick} />;
};

7. Missing Cleanup in useEffect (P1)

Detection: Event listener or subscription without return cleanup Reference: https://react.dev/reference/react/useEffect#subscribing-to-events

// WRONG — memory leak
useEffect(() => {
  window.addEventListener('resize', handleResize);
}, []); // Missing cleanup

// RIGHT — cleanup function
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

8. Boolean State for Toggle (P3)

Detection: State initialized with true/false when null/undefined is valid Reference: https://react.dev/reference/react/useState

// WRONG — three states needed
const [isLoading, setIsLoading] = useState(true);
if (isLoading === true) // loading
else if (isLoading === false) // loaded
// But how to handle error?

// RIGHT — use proper state machine
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');

9. Derived State Instead of Computed (P2)

Detection: useState storing value that can be computed from props/state Reference: https://react.dev/learn/queueing-a-series-of-state-updates

// WRONG — redundant state
const [fullName, setFullName] = useState('');
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

// RIGHT — compute when needed
const fullName = `${firstName} ${lastName}`;

10. Prop Drilling (P2)

Detection: Multiple components passing same prop through layers Reference: https://react.dev/learn/passing-data-deeply-with-context

// WRONG — theme passed through layers
<GrandParent>
  <Parent theme={theme}>
    <Child theme={theme}>
      <Button theme={theme} />
    </Child>
  </Parent>
</GrandParent>

// RIGHT — use context
const ThemeContext = createContext();
<ThemeContext.Provider value={theme}>
  <Child />
</ThemeContext.Provider>
// Then useContext(ThemeContext) in Button

Vue Anti-Patterns

1. Mutating Props Directly (P1)

Detection: props: definition with mutation inside component Reference: https://vuejs.org/guide/components/props#prop-mutations

// WRONG — mutating prop
<script setup>
const props = defineProps<{ title: string }>();
props.title = 'New Title'; // Error!
</script>

// RIGHT — emit event or use local state
<script setup>
const props = defineProps<{ title: string }>();
const localTitle = ref(props.title);
localTitle.value = 'New Title';
</script>

2. Mixing Composition API with Options API (P2)

Detection: setup() function alongside data(), methods, computed Reference: https://vuejs.org/guide/extras/composition-api-faq#should-i-use-options-api-or-composition-api

// WRONG — mixing APIs
<script>
export default {
  data() { return { count: 0 } },
  setup() {
    const doubled = computed(() => this.count * 2); // Confusing
  }
}
</script>

// RIGHT — stick to Composition API
<script setup>
const count = ref(0);
const doubled = computed(() => count.value * 2);
</script>

3. Watching Objects Instead of Properties (P2)

Detection: watch(obj, ...) instead of watch(() => obj.prop, ...) Reference: https://vuejs.org/guide/essentials/watchers#watching-reactive-state

// WRONG — watches entire object
watch(user, (newUser) => {
  console.log(newUser.name); // Won't trigger on name change
});

// RIGHT — watch specific property
watch(() => user.name, (newName) => {
  console.log(newName);
});

4. Destructuring reactive() Loses Reactivity (P1)

Detection: const { prop } = reactive({...}) destructuring before use Reference: https://vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive

// WRONG — destructuring reactive() drops reactivity
<script setup>
const state = reactive({ name: 'John' });
const { name } = state; // name is a plain string, no longer reactive
function update() {
  name.value = 'Jane'; // Does NOT update state.name
}
</script>

// RIGHT — prefer ref() (the default recommendation)
<script setup>
const name = ref('John');
function update() {
  name.value = 'Jane'; // Reactivity preserved
}
</script>

// Alternative RIGHT — keep reactivity while destructuring
<script setup>
const state = reactive({ name: 'John' });
const { name } = toRefs(state);
function update() {
  name.value = 'Jane'; // Updates state.name
}
</script>

5. Side Effects in Computed (P1)

Detection: computed() with mutation, async, or side effect Reference: https://vuejs.org/guide/essentials/computed#computed-properties

// WRONG — side effect in computed
const fullName = computed(() => {
  fetchUser(); // Side effect!
  return `${user.firstName} ${user.lastName}`;
});

// RIGHT — use watch or method instead
const fullName = computed(() => `${user.firstName} ${user.lastName}`);

6. Missing Cleanup in onMounted (P1)

Detection: Subscription/timer in onMounted without onUnmounted Reference: https://vuejs.org/guide/essentials/lifecycle#lifecycle-diagram

// WRONG
onMounted(() => {
  interval = setInterval(fetchData, 5000);
}); // No cleanup!

// RIGHT
onMounted(() => {
  interval = setInterval(fetchData, 5000);
});
onUnmounted(() => clearInterval(interval));

7. Using Index as Key (P2)

Detection: :key="index" in v-for Reference: https://vuejs.org/guide/essentials/list#maintaining-state-with-key

// WRONG — key changes when array order changes
<div v-for="(item, index) in items" :key="index">

// RIGHT — use stable unique id
<div v-for="item in items" :key="item.id">

8. Mutating Props from Child (v-model Anti-Pattern) (P1)

Detection: Direct prop assignment (props.title = ...) or manual update: emit when defineModel() applies Reference: https://vuejs.org/guide/components/v-model

// WRONG — direct prop assignment breaks one-way data flow
<script setup>
const props = defineProps<{ title: string }>();
function rename() {
  props.title = 'New'; // Warning: mutating a prop
}
</script>

// RIGHT — defineModel (Vue 3.4+)
<script setup>
const title = defineModel<string>();
function rename() {
  title.value = 'New'; // Two-way binding via v-model
}
</script>

// Alternative RIGHT — explicit update:title emit
<script setup>
const props = defineProps<{ title: string }>();
const emit = defineEmits<{ 'update:title': [value: string] }>();
function rename() {
  emit('update:title', 'New');
}
</script>

Svelte Anti-Patterns

1. Not Unsubscribing from Stores (P1)

Detection: $store usage without understanding subscription lifecycle Reference: https://svelte.dev/docs/svelte-store#auto-subscription

// WRONG — memory leak
<script>
  import { count } from './stores';
  onMount(() => {
    // Using $count but not understanding subscription
  });
</script>

// RIGHT — Svelte auto-subscribes with $ prefix
<script>
  import { count } from './stores';
  // $count is automatically subscribed and unsubscribed
</script>
<p>{$count}</p>

2. Overusing Reactive Statements (P3)

Detection: Multiple $: that could be combined into one Reference: https://svelte.dev/docs/svelte/legacy-reactive-assignments

// WRONG — too many reactive statements
$: doubled = count * 2;
$: quadrupled = doubled * 2;
$: console.log(quadrupled);

// RIGHT — compute once
$: quadrupled = count * 4;

// Svelte 5 — prefer runes ($derived) over legacy $: statements
let quadrupled = $derived(count * 4);

3. Mutating Props in Reactive Statements (P1)

Detection: export let followed by reassignment Reference: https://svelte.dev/docs/svelte-components#script

// WRONG
<script>
  export let name;
  $: name = name.toUpperCase(); // Error!
</script>

// RIGHT — create derived value
<script>
  export let name;
  $: displayName = name?.toUpperCase();
</script>
<p>{displayName}</p>

4. Not Cleaning Up in onDestroy (P1)

Detection: Subscription or timer without onDestroy cleanup Reference: https://svelte.dev/docs/svelte#ondestroy

// WRONG
<script>
  import { onMount } from 'svelte';
  let timer;
  onMount(() => {
    timer = setInterval(() => count++, 1000);
  }); // Memory leak!
</script>

// RIGHT
<script>
  import { onMount, onDestroy } from 'svelte';
  let timer;
  onMount(() => {
    timer = setInterval(() => count++, 1000);
  });
  onDestroy(() => clearInterval(timer));
</script>

5. Direct Store Assignment Instead of update (P3)

Detection: $store = direct assignment that bypasses a writable store's update() Reference: https://svelte.dev/docs/svelte/stores#writable-stores

// WRONG — direct assignment bypasses the store's update() logic
$count = $count + 1;

// RIGHT — route changes through the store's update method
count.update(n => n + 1);

Angular Anti-Patterns

1. Subscribing Without Unsubscribe (P1)

Detection: .subscribe() without .unsubscribe() or takeUntilDestroyed Reference: https://angular.dev/api/core/rxjs-interop/takeUntilDestroyed

// WRONG — memory leak
@Component({...})
export class UserComponent {
  ngOnInit() {
    this.userService.getUser().subscribe(user => {
      this.user = user;
    });
  }
}

// RIGHT — use takeUntilDestroyed (Angular 16+)
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

@Component({...})
export class UserComponent {
  // takeUntilDestroyed() inside an injection context works without `this.destroy$`
  user$ = this.userService.getUser().pipe(takeUntilDestroyed());
}

// BEST — use async pipe in template
@Component({...})
export class UserComponent {
  user$ = this.userService.getUser();
}

2. Field-Declared Form + ngOnInit setValue Overwrite (P1)

Detection: FormGroup initialized at field declaration, then overwritten with setValue() in ngOnInit Reference: https://angular.dev/guide/forms/reactive-forms

// WRONG — setValue() in ngOnInit overwrites a field-initialized form
@Component({...})
export class ProfileComponent implements OnInit {
  form = new FormGroup({
    name: new FormControl(''),
  });

  ngOnInit() {
    // Redundant for sync defaults; clobbers user input if async data arrives late
    this.form.setValue({ name: 'John' });
  }
}

// RIGHT — declare defaults once; patchValue() only when async data actually arrives
@Component({...})
export class ProfileComponent {
  form = new FormGroup({
    name: new FormControl(''),
  });

  constructor() {
    this.userService.getProfile()
      .pipe(takeUntilDestroyed())
      .subscribe(profile => this.form.patchValue(profile)); // patch, not setValue
  }
}

3. Using ngIf with Hidden Elements (P2)

Detection: *ngIf="false" followed by display: none or [hidden] Reference: https://angular.dev/api/common/NgIf

<!-- WRONG — double handling -->
<div *ngIf="show" [hidden]="!show" class="content">
  Content
</div>

<!-- RIGHT — choose one -->
<div *ngIf="show" class="content">
  Content
</div>

4. Not Using trackBy in ngFor (P2)

Detection: *ngFor without trackBy function Reference: https://angular.dev/api/common/NgFor

<!-- WRONG — expensive re-renders -->
<div *ngFor="let item of items">
  {{ item.name }}
</div>

<!-- RIGHT -->
<div *ngFor="let item of items; trackBy: trackById">
  {{ item.name }}
</div>
trackById(index: number, item: Item): string {
  return item.id;
}

5. HTTP Calls in Constructor (P1)

Detection: HTTP call in constructor instead of ngOnInit Reference: https://angular.dev/guide/di

// WRONG — too early, may not have all dependencies
constructor(private http: HttpClient) {
  this.http.get('/api/user').subscribe();
}

// RIGHT — wait for component initialization
constructor(private http: HttpClient) {}

ngOnInit() {
  this.http.get('/api/user').subscribe();
}

Astro Anti-Patterns

1. Client-Side Data Fetching When Server-Side Possible (P2)

Detection: fetch() inside a client-side <script> (runtime request) when the data could be fetched in frontmatter at build time Reference: https://docs.astro.build/en/recipes/build-time-data-fetching

// WRONG — fetch in client-side <script>: runs in the browser on every visit, no build caching
---
---
<script>
  const data = await fetch('https://api.example.com/data').then(r => r.json());
</script>

// RIGHT — fetch in frontmatter: executes once at build time, page ships with data
---
const data = await fetch('https://api.example.com/data').then(r => r.json());
---
<p>{data.title}</p>

// Note: add a client:* directive only for interactivity — never for data fetching
<Interactive client:load />

2. Improper Prop Typing (P2)

Detection: Missing or incorrect Props interface Reference: https://docs.astro.build/en/guides/typescript#component-props

// WRONG — no typing
---
const { title, count } = Astro.props;
// No TypeScript validation
---

// RIGHT — proper interface
---
interface Props {
  title: string;
  count?: number;
}
const { title, count = 0 } = Astro.props as Props;
---

3. Mixing Component Types (P2)

Detection: Using .astro component in client script without directive Reference: https://docs.astro.build/en/concepts/islands

// WRONG — client component without directive
import ReactButton from './ReactButton.jsx';

// RIGHT — use client directive
import ReactButton from './ReactButton.jsx';
<ReactButton client:load />

4. Unnecessary Client Directive (P2)

Detection: client:* on static components Reference: https://docs.astro.build/en/reference/directives-reference#client-directives

// WRONG — static component doesn't need client directive
<StaticHeader client:load />

// RIGHT — only when interactivity needed
<InteractiveButton client:visible />

5. Missing Props Validation (P2)

Detection: No TypeScript interface for component props Reference: https://docs.astro.build/en/guides/typescript/#component-props

// WRONG
---
const { title, items } = Astro.props;
// What if title is undefined?
---

// RIGHT
---
interface Props {
  title: string;
  items: string[];
}
const { title, items } = Astro.props as Props;
---

General JS/TS Anti-Patterns

1. Not Handling Async Errors (P1)

Detection: async function without try/catch or .catch() Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

// WRONG
async function fetchUser(id: string) {
  const response = await fetch(`/api/users/${id}`);
  return response.json(); // Unhandled rejection on error!
}

// RIGHT
async function fetchUser(id: string) {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) throw new Error('User not found');
    return response.json();
  } catch (error) {
    console.error('Failed to fetch user', error);
    throw error;
  }
}

2. Using any Instead of Proper Types (P2)

Detection: Type annotation with any Reference: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any

// WRONG
function processData(data: any) {
  return data.name; // No type safety!
}

// RIGHT
interface User {
  name: string;
  age: number;
}
function processData(data: User) {
  return data.name;
}

3. Mutating Parameters (P3)

Detection: Assignment to function parameters Reference: https://www.typescriptlang.org/docs/handbook/2/functions.html#parameter-destructuring

// WRONG
function processUser(user: User) {
  user.name = 'Modified'; // Mutates original!
}

// RIGHT
function processUser(user: User): User {
  return { ...user, name: 'Modified' };
}

4. Creating Objects in Render (P2)

Detection: Object/array creation inside render/return Reference: https://react.dev/learn/keeping-components-pure

// WRONG — new object created on every render
const Child = ({ name, id }) => (
  <UserCard user={{ name, id }} />
);

// RIGHT — move outside the component (module constant)
const defaultUser = { name: 'John', id: 1 };
const Child = () => <UserCard user={defaultUser} />;

// Or RIGHT — memoize when values change per render
const Child = ({ name, id }) => (
  <UserCard user={useMemo(() => ({ name, id }), [name, id])} />
);

5. Not Using Optional Chaining (P2)

Detection: Manual null check before accessing nested property Reference: https://www.typescriptlang.org/docs/handbook/2/functions.html#optional-parameters

// WRONG — verbose null checks
const name = user && user.profile && user.profile.name;

// RIGHT — optional chaining
const name = user?.profile?.name;

6. Null vs Undefined Confusion (P3)

Detection: Inconsistent use of null and undefined Reference: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#null-and-undefined

// WRONG — mixing null and undefined
function createUser(name: string, age?: number | null) {
  // Confusing when to use which
}

// RIGHT — consistent approach
function createUser(name: string, age?: number) {
  // Use undefined for optional, value for required
}

CSS-in-JS / Responsive Design Patterns

1. Hardcoded Values in CSS-in-JS (P2)

Detection: Magic numbers inside styled-components / Emotion / CSS modules Reference: https://styled-components.com/docs/basics

// WRONG — magic number inside a styled-component
const Card = styled.div`padding: 8px;`;
// RIGHT — use design token
const Card = styled.div`padding: var(--ds-space-2);`;

2. Repeated Breakpoint Values in Media Queries (P2)

Detection: Raw px breakpoints in media queries instead of design tokens Reference: https://developer.mozilla.org/docs/Web/CSS/@media

/* WRONG — duplicated magic breakpoint */
@media (min-width: 768px) { }
/* RIGHT — breakpoint token */
@media (min-width: var(--ds-breakpoint-md)) { }

3. Touch Target Below 44px on Mobile (P1)

Detection: Interactive element smaller than the 44px mobile touch target Reference: https://developer.apple.com/design/human-interface-guidelines/touch-targets

/* WRONG — too small to tap reliably */
.close-btn { width: 32px; height: 32px; }
/* RIGHT — minimum mobile touch target */
.close-btn { width: 44px; height: 44px; }

Issue Severity

Severity is assigned by a strict rule-id × severity matrix, not free-form judgment. When a finding maps to a known rule-id, its severity comes from this matrix; framework anti-patterns keep the severity annotated inline (P1/P2/P3, unchanged).

| Severity | Definition | Example rule-ids | |----------|------------|------------------| | P0 | Technical blocker — CI will fail / blank page / visual functionality broken | dark-mode-coverage, bare color bare-color in component rules (blocking context), destructive API misuse | | P1 | Compliance blocker — WCAG / semantics / interaction failure | missing-alt, button-aria-label, contrast < 4.5:1, touch target < 44px | | P2 | Design-system quality — violates the design system (downgradable) | non-blocking bare values, hardcoded-spacing, bem-naming, raw-px-breakpoint | | P3 | Optional polish (can be ignored) | focus enhancement, CSS order, missing comments |

Severity Calibration Rules

  • P0/P1 cannot be downgraded — consistent with AGENTS.md "Severity never downgraded". overrides.severity in .argus.yml rejects P2/P3 overrides for dark-mode-coverage, bare-color, missing-alt, and button-aria-label.
  • Non-blocking bare values (e.g. a box-shadow rgba literal outside a component's primary color context) are P2, aligned with the PR-B false-positives benchmark — not every bare value is a P0.
  • Upgrades are allowed (P2 → P1); downgrades of non-core rules are allowed (hardcoded-spacing → P3, bem-naming → P3).
  • Framework anti-pattern annotations are unchanged — all 42 rules in the stack sections below keep their existing inline P1/P2/P3 labels.

Output Format

Summary Header

## Argus Design Review Summary
- Total Issues: N (P0: X | P1: X | P2: X | P3: X)
- Files Reviewed: N
- Technology Stack: {detected stack}
- Documentation: {official docs URL}

Severity Groups

Issues are grouped under headers in order: P0 → P1 → P2 → P3.

## P0 — Blocking Issues (must fix, CI will fail)

## P1 — High Priority (must fix before merge)

## P2 — Medium Priority (should fix)

## P3 — Low Priority (optional polish)

Issue Block (repeats per issue)

[P{severity}] {file}:{line} — {short description}

  Found:    {current code snippet}
  Expected: {correct code snippet}

  Fix:
  ```{extension}
  {copy-ready fix code}

Token: {design token to use, if applicable} Reference: {official docs URL for this API} Note: {optional context or explanation}


### Format Rules

- Each issue block starts with a `─────────────────────────────────────────────────` separator line
- Code snippets are shown inline, truncated to relevant portion (max 80 chars per line)
- **Fix code block is mandatory** — always provide the exact fix to copy
- Empty `Note:` line is omitted if not needed
- No issue = output `✓ No issues found` under each severity group
- Always include `Reference:` link when flagging framework API issues

## Review Workflow

1. **Detect stack** — scan file extensions and package.json
2. Read the codebase — understand the design token system in use
3. Scan for bare color values (oklch/hex/rgb outside :root declarations)
4. Scan for magic numbers in spacing, radii, font sizes
5. Verify dark mode coverage for every color token
6. Check accessibility — buttons, images, semantic HTML
7. Check CSS quality — duplicates, BEM, empty catch blocks
8. **Stack-specific API checks** — verify hooks, directives, lifecycle usage against Framework Anti-Patterns Library
9. **Generate fixes** — provide copy-ready code for every issue found
10. Report findings grouped by severity

**In automated PR review mode:** The composite action at `.github/actions/argus-review/action.yml` reads `AGENTS.md` + `SKILL.md` from the argus repo at runtime and injects their contents into the LLM prompt. The review is performed by the `argus-flash` GitHub App, which comments findings directly on the PR.

## Non-Blocking Context

Do NOT flag issues in:
- Third-party resets or normalize.css
- Generated boilerplate that will be replaced
- Test fixtures and mock data files
- `node_modules/` (ignore entirely)
- Workflow YAML files (`.github/workflows/`, `.github/actions/`)