← Agent SkillsCONTENT HISTORY

Update to Agent Skills

Snapshot Sep 30, 2026 · 23:17 UTC · version 0.6.10

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "frontend-ui-engineering",
  "description": "Use when building or modifying interfaces and pages, creating components, implementing layouts, meeting WCAG accessibility requirements, managing state, or when the output needs to look and feel production-quality rather than AI-generated.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 272
    }
  ],
  "skill_md_contents": "---\nname: frontend-ui-engineering\ndescription: Use when building or modifying interfaces and pages, creating components, implementing layouts, meeting WCAG\n  accessibility requirements, managing state, or when the output needs to look and feel production-quality rather than AI-generated.\n---\n\n# Frontend UI Engineering\n\n## Overview\n\nBuild production-quality user interfaces that are accessible, performant, and visually polished. The goal is UI that looks like it was built by a design-aware engineer at a top company — not like it was generated by an AI. This means real design system adherence, proper accessibility, thoughtful interaction patterns, and no generic \"AI aesthetic.\"\n\n## When to Use\n\n- Building new UI components or pages\n- Modifying existing user-facing interfaces\n- Implementing responsive layouts\n- Adding interactivity or state management\n- Fixing visual or UX issues\n\n## Component Architecture\n\n### File Structure\n\nColocate everything related to a component:\n\n```\nsrc/components/\n  TaskList/\n    TaskList.tsx          # Component implementation\n    TaskList.test.tsx     # Tests\n    TaskList.stories.tsx  # Storybook stories (if using)\n    use-task-list.ts      # Custom hook (if complex state)\n    types.ts              # Component-specific types (if needed)\n```\n\n### Component Patterns\n\n**Prefer composition over configuration:**\n\n```tsx\n// Good: Composable\n<Card>\n  <CardHeader>\n    <CardTitle>Tasks</CardTitle>\n  </CardHeader>\n  <CardBody>\n    <TaskList tasks={tasks} />\n  </CardBody>\n</Card>\n\n// Avoid: Over-configured\n<Card\n  title=\"Tasks\"\n  headerVariant=\"large\"\n  bodyPadding=\"md\"\n  content={<TaskList tasks={tasks} />}\n/>\n```\n\n**Keep components focused:**\n\n```tsx\n// Good: Does one thing\nexport function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {\n  return (\n    <li className=\"flex items-center gap-3 p-3\">\n      <Checkbox checked={task.done} onChange={() => onToggle(task.id)} />\n      <span className={task.done ? 'line-through text-muted' : ''}>{task.title}</span>\n      <Button variant=\"ghost\" size=\"sm\" onClick={() => onDelete(task.id)}>\n        <TrashIcon />\n      </Button>\n    </li>\n  );\n}\n```\n\n**Separate data fetching from presentation:**\n\n```tsx\n// Container: handles data\nexport function TaskListContainer() {\n  const { tasks, isLoading, error } = useTasks();\n\n  if (isLoading) return <TaskListSkeleton />;\n  if (error) return <ErrorState message=\"Failed to load tasks\" retry={refetch} />;\n  if (tasks.length === 0) return <EmptyState message=\"No tasks yet\" />;\n\n  return <TaskList tasks={tasks} />;\n}\n\n// Presentation: handles rendering\nexport function TaskList({ tasks }: { tasks: Task[] }) {\n  return (\n    <ul role=\"list\" className=\"divide-y\">\n      {tasks.map(task => <TaskItem key={task.id} task={task} />)}\n    </ul>\n  );\n}\n```\n\n## State Management\n\n**Choose the simplest approach that works:**\n\n```\nLocal state (useState)           → Component-specific UI state\nLifted state                     → Shared between 2-3 sibling components\nContext                          → Theme, auth, locale (read-heavy, write-rare)\nURL state (searchParams)         → Filters, pagination, shareable UI state\nServer state (React Query, SWR)  → Remote data with caching\nGlobal store (Zustand, Redux)    → Complex client state shared app-wide\n```\n\n**Avoid prop drilling deeper than 3 levels.** If you're passing props through components that don't use them, introduce context or restructure the component tree.\n\n## Design System Adherence\n\n### Avoid the AI Aesthetic\n\nAI-generated UI has recognizable patterns. Avoid all of them:\n\n| AI Default | Why It Is a Problem | Production Quality |\n|---|---|---|\n| Purple/indigo everything | Models default to visually \"safe\" palettes, making every app look identical | Use the project's actual color palette |\n| Excessive gradients | Gradients add visual noise and clash with most design systems | Flat or subtle gradients matching the design system |\n| Rounded everything (rounded-2xl) | Maximum rounding signals \"friendly\" but ignores the hierarchy of corner radii in real designs | Consistent border-radius from the design system |\n| Generic hero sections | Template-driven layout with no connection to the actual content or user need | Content-first layouts |\n| Lorem ipsum-style copy | Placeholder text hides layout problems that real content reveals (length, wrapping, overflow) | Realistic placeholder content |\n| Oversized padding everywhere | Equal generous padding destroys visual hierarchy and wastes screen space | Consistent spacing scale |\n| Stock card grids | Uniform grids are a layout shortcut that ignores information priority and scanning patterns | Purpose-driven layouts |\n| Shadow-heavy design | Layered shadows add depth that competes with content and slows rendering on low-end devices | Subtle or no shadows unless the design system specifies |\n\n### Spacing and Layout\n\nUse a consistent spacing scale. Don't invent values:\n\n```css\n/* Use the scale: 0.25rem increments (or whatever the project uses) */\n/* Good */  padding: 1rem;      /* 16px */\n/* Good */  gap: 0.75rem;       /* 12px */\n/* Bad */   padding: 13px;      /* Not on any scale */\n/* Bad */   margin-top: 2.3rem; /* Not on any scale */\n```\n\n### Typography\n\nRespect the type hierarchy:\n\n```\nh1 → Page title (one per page)\nh2 → Section title\nh3 → Subsection title\nbody → Default text\nsmall → Secondary/helper text\n```\n\nDon't skip heading levels. Don't use heading styles for non-heading content.\n\n### Color\n\n- Use semantic color tokens: `text-primary`, `bg-surface`, `border-default` — not raw hex values\n- Ensure sufficient contrast (4.5:1 for normal text, 3:1 for large text)\n- Don't rely solely on color to convey information (use icons, text, or patterns too)\n\n## Accessibility (WCAG 2.1 AA)\n\nEvery component must meet these standards:\n\n### Keyboard Navigation\n\n```tsx\n// Every interactive element must be keyboard accessible\n<button onClick={handleClick}>Click me</button>        // ✓ Focusable by default\n<div onClick={handleClick}>Click me</div>               // ✗ Not focusable\n<div role=\"button\" tabIndex={0} onClick={handleClick}    // ✓ But prefer <button>\n     onKeyDown={e => {\n       if (e.key === 'Enter') handleClick();\n       if (e.key === ' ') e.preventDefault();\n     }}\n     onKeyUp={e => {\n       if (e.key === ' ') handleClick();\n     }}>\n  Click me\n</div>\n```\n\n### ARIA Labels\n\n```tsx\n// Label interactive elements that lack visible text\n<button aria-label=\"Close dialog\"><XIcon /></button>\n\n// Label form inputs\n<label htmlFor=\"email\">Email</label>\n<input id=\"email\" type=\"email\" />\n\n// Or use aria-label when no visible label exists\n<input aria-label=\"Search tasks\" type=\"search\" />\n```\n\n### Focus Management\n\n```tsx\n// Move focus when content changes\nfunction Dialog({ isOpen, onClose }: DialogProps) {\n  const closeRef = useRef<HTMLButtonElement>(null);\n\n  useEffect(() => {\n    if (isOpen) closeRef.current?.focus();\n  }, [isOpen]);\n\n  // Trap focus inside dialog when open\n  return (\n    <dialog open={isOpen}>\n      <button ref={closeRef} onClick={onClose}>Close</button>\n      {/* dialog content */}\n    </dialog>\n  );\n}\n```\n\n### Meaningful Empty and Error States\n\n```tsx\n// Don't show blank screens\nfunction TaskList({ tasks }: { tasks: Task[] }) {\n  if (tasks.length === 0) {\n    return (\n      <div role=\"status\" className=\"text-center py-12\">\n        <TasksEmptyIcon className=\"mx-auto h-12 w-12 text-muted\" />\n        <h3 className=\"mt-2 text-sm font-medium\">No tasks</h3>\n        <p className=\"mt-1 text-sm text-muted\">Get started by creating a new task.</p>\n        <Button className=\"mt-4\" onClick={onCreateTask}>Create Task</Button>\n      </div>\n    );\n  }\n\n  return <ul role=\"list\">...</ul>;\n}\n```\n\n## Responsive Design\n\nDesign for mobile first, then expand:\n\n```tsx\n// Tailwind: mobile-first responsive\n<div className=\"\n  grid grid-cols-1      /* Mobile: single column */\n  sm:grid-cols-2        /* Small: 2 columns */\n  lg:grid-cols-3        /* Large: 3 columns */\n  gap-4\n\">\n```\n\nTest at these breakpoints: 320px, 768px, 1024px, 1440px.\n\n## Loading and Transitions\n\n```tsx\n// Skeleton loading (not spinners for content)\nfunction TaskListSkeleton() {\n  return (\n    <div className=\"space-y-3\" aria-busy=\"true\" aria-label=\"Loading tasks\">\n      {Array.from({ length: 3 }).map((_, i) => (\n        <div key={i} className=\"h-12 bg-muted animate-pulse rounded\" />\n      ))}\n    </div>\n  );\n}\n\n// Optimistic updates for perceived speed\nfunction useToggleTask() {\n  const queryClient = useQueryClient();\n\n  return useMutation({\n    mutationFn: toggleTask,\n    onMutate: async (taskId) => {\n      await queryClient.cancelQueries({ queryKey: ['tasks'] });\n      const previous = queryClient.getQueryData(['tasks']);\n\n      queryClient.setQueryData(['tasks'], (old: Task[]) =>\n        old.map(t => t.id === taskId ? { ...t, done: !t.done } : t)\n      );\n\n      return { previous };\n    },\n    onError: (_err, _taskId, context) => {\n      queryClient.setQueryData(['tasks'], context?.previous);\n    },\n  });\n}\n```\n\n## See Also\n\nFor detailed accessibility requirements and testing tools, see `../../references/accessibility-checklist.md`.\n\n## Common Rationalizations\n\n| Rationalization | Reality |\n|---|---|\n| \"Accessibility is a nice-to-have\" | It's a legal requirement in many jurisdictions and an engineering quality standard. |\n| \"We'll make it responsive later\" | Retrofitting responsive design is 3x harder than building it from the start. |\n| \"The design isn't final, so I'll skip styling\" | Use the design system defaults. Unstyled UI creates a broken first impression for reviewers. |\n| \"This is just a prototype\" | Prototypes become production code. Build the foundation right. |\n| \"The AI aesthetic is fine for now\" | It signals low quality. Use the project's actual design system from the start. |\n\n## Red Flags\n\n- Components with more than 200 lines (split them)\n- Inline styles or arbitrary pixel values\n- Missing error states, loading states, or empty states\n- No keyboard navigation testing\n- Color as the sole indicator of state (red/green without text or icons)\n- Generic \"AI look\" (purple gradients, oversized cards, stock layouts)\n\n## Verification\n\nAfter building UI:\n\n- [ ] Component renders without console errors\n- [ ] All interactive elements are keyboard accessible (Tab through the page)\n- [ ] Screen reader can convey the page's content and structure\n- [ ] Responsive: works at 320px, 768px, 1024px, 1440px\n- [ ] Loading, error, and empty states all handled\n- [ ] Follows the project's design system (spacing, colors, typography)\n- [ ] No accessibility warnings in dev tools or axe-core\n"
}

SHA-256: 8658740468971bb22f8675831e5460a5be67a0f92c0a66a12b7f41430d6ac2de