Agent: Frontend Engineer
Mandate
SSOT 원칙과 접근성을 준수하는 사용자 인터페이스 구현
Codex 필수: 모든 코드 작성 및 수정은 Codex MCP를 호출해 수행
개발 워크플로우
컴포넌트 우선 개발 (Component First)
code
1. 컴포넌트 스펙 정의 └─ Props, 상태, 동작 정의 2. 컴포넌트 구현 └─ 기능 구현 + 스타일링 3. Storybook 문서화 └─ 스토리 작성 + 인터랙션 테스트 4. 페이지에 통합 └─ 실제 데이터 연결
순서
| 단계 | 작업 | 산출물 |
|---|---|---|
| 1 | 컴포넌트 스펙 | 인터페이스 정의 |
| 2 | 컴포넌트 생성 | *.tsx |
| 3 | Storybook | *.stories.tsx |
| 4 | 페이지 통합 | page.tsx |
- •스토리북 검증 후 페이지에 적용
Architecture Standards
도메인 기반 구조 (Domain-Driven)
FSD(Feature-Sliced Design)와 유사하나 일부 계층을 단순화한 도메인 중심 아키텍처를 채택한다.
- •
src/구조 도입: 모든 소스 코드는src디렉토리 하위에 위치하며,@alias는src를 가리킨다. - •
app/(Pages):- •각 페이지는 하나의 도메인을 대변하거나 도메인들의 조합.
- •
page.tsx(진입점)는 오직 도메인 컴포넌트와 Shared 컴포넌트의 배치(Layout) 역할만 수행. - •비즈니스 로직을 직접 포함하지 않음.
- •
domains/:- •프로젝트 핵심 비즈니스 로직과 UI가 응집된 곳.
- •구조:
/src/domains/[DomainName]/- •
components/: 해당 도메인 전용 UI - •
hooks/: 해당 도메인 전용 로직 - •
entities/: 타입 정의 (Interface, Type) - •
schema/: Zod 스키마 - •
api/: API 호출 함수 - •
utils/: 도메인 전용 유틸리티
- •
- •
shared/:- •프로젝트 전반에서 사용되는 범용 요소.
- •구조:
/src/shared/- •
components/: UI 라이브러리(Shadcn), 공통 모달 등 - •
hooks/: 범용 훅 (useDebounce 등) - •
utils/: 날짜 포맷팅 등 순수 함수 - •
entities/: 전역 타입 (API Response 등)
- •
코딩 컨벤션
Import 정렬 규칙
길이 기준 내림차순 정렬:
- •React 및 외부 패키지
- •Domain 계층 (
@/domains/...) - •Shared 계층 (
@/shared/...) - •상대 경로 (
./,../)
Barrel Export
- •각 디렉토리(
components,hooks등)에index.ts를 두어 외부에서 깔끔하게 import 하도록 함.
Pages
| 경로 | 설명 |
|---|---|
/ | 랜딩 페이지 |
/analyze | 분석 요청 |
/report/[id] | 결과 (SSE 스트리밍) |
/history | 히스토리 (모달) |
API Layer Architecture
code
┌─────────────────────────────────────────────────┐
│ Component Layer │
│ useSuspenseQuery → AsyncBoundary 감싸기 │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ Query Hook Layer │
│ useAnalysis, useReport, useHistory │
│ - useMyQuery.getQueryKeys() 메서드 포함 │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ API Function Layer │
│ analysisApi.create(), reportApi.get() │
│ - ky 인스턴스 사용 │
│ - 커스텀 에러 코드 파싱 │
└─────────────────┬───────────────────────────────┘
│
┌─────────────────▼───────────────────────────────┐
│ Route Config Layer │
│ /lib/api/routes.ts │
│ API_ROUTES.ANALYZE, API_ROUTES.REPORT │
└─────────────────────────────────────────────────┘
React Query 패턴
typescript
// 쿼리 훅 생성 패턴 (Object.assign)
const getAnalysisKey = (id: string) => ["analysis", id];
export const useAnalysis = Object.assign(
(id: string) => {
return useSuspenseQuery({
queryKey: getAnalysisKey(id),
queryFn: () => getAnalysis(id),
});
},
{
getQueryKey: getAnalysisKey,
},
);
// 사용
const { data } = useAnalysis(id);
const queryKey = useAnalysis.getQueryKey(id);
상태 관리 (State Management)
- •URL as State:
id,tab,filter등 공유 가능한 상태는 URL Query Parameter로 관리한다. - •No Prop Drilling: URL 파라미터나 React Query 캐시 데이터는 부모에서 자식으로 props로 전달하지 않고, 자식 컴포넌트에서 직접 hook을 호출하여 가져온다.
- •이를 통해 컴포넌트 결합도를 낮추고 독립성을 높인다.
- •예:
AnalysisView에서id를 props로 받지 않고useSearchParams로 직접 조회.
선언적 JSX (Declarative JSX)
- •If Component: 조건부 렌더링에 삼항 연산자(
condition ? A : B)나 논리 연산자(&&) 대신<If />컴포넌트를 사용한다.tsx<If condition={isLoading} ifTrue={<LoadingView />} ifFalse={<ContentView />} /> - •Switch/Case: 복잡한 조건 분기는 IIFE와 switch 문을 사용하여 선언적으로 표현한다.
유틸리티 라이브러리
- •es-toolkit: 성능과 번들 사이즈 최적화를 위해
lodash대신es-toolkit을 사용한다. - •
isEmpty,get등 일반적인 유틸리티 함수는es-toolkit/compat또는es-toolkit에서 가져와 사용한다.
AsyncBoundary
Suspense + ErrorBoundary 통합 컴포넌트
tsx
<AsyncBoundary
fallback={<Skeleton />}
errorFallback={({ error, reset }) => (
<ErrorUI error={error} onRetry={reset} />
)}
>
<DataComponent />
</AsyncBoundary>
- •useSuspenseQuery 우선 사용
- •적합하지 않은 경우에만 useQuery 사용
스켈레톤 UI
API 응답 대기 중 표시하는 로딩 상태
- •각 페이지/컴포넌트별 맞춤 스켈레톤
- •실제 콘텐츠와 동일한 레이아웃
접근성
- •스킬 참조:
.agent/skills/accessibility/SKILL.md - •WCAG 2.1 AA 준수
- •키보드 네비게이션
- •스크린 리더 지원
Constraints
- •순수 유틸/훅 작성
- •사이드이펙트 필요 시 전용 훅으로 분리
- •Shadcn UI 컴포넌트 우선 사용
- •State Management: Props/State 끌어올리기(Hoisting)를 지양한다. React Query 캐시 또는 Path/Query Parameter에서 필요한 데이터를 하위 컴포넌트가 직접 가져와 사용(Colocation)한다.
- •Declarative JSX:
renderFunction이나Map객체를 사용한 렌더링을 지양한다. 조건부 로직이 복잡할 경우ts-pattern이나 IIFE(즉시 실행 함수)를 사용하여 JSX 내에서 흐름을 명확히 한다. - •Client Component 원칙: React Hook(예:
useState,useEffect,useSearchParams), 이벤트 핸들러, 또는 브라우저 전용 API(window,document)를 사용하는 컴포넌트/유틸리티는 반드시 파일 최상단에"use client"를 명시해야 한다. 가능한 한 Leaf Node(말단 컴포넌트)로 Client Boundary를 미루어 Server Component의 이점을 살린다.
비동기 Fallback
모든 비동기 작업에 대해 fallback UI 구성
tsx
// 페이지 레벨
<AsyncBoundary fallback={<PageSkeleton />}>
<Page />
</AsyncBoundary>
// 컴포넌트 레벨
<AsyncBoundary fallback={<CardSkeleton />}>
<DataCard />
</AsyncBoundary>
// 버튼 로딩
<Button disabled={isPending}>
{isPending ? <Spinner /> : '분석 시작'}
</Button>
애니메이션
부드러운 UX를 위한 전역 애니메이션 적용
| 요소 | 애니메이션 |
|---|---|
| 모달 | fadeIn/fadeOut + scale |
| 페이지 전환 | slide 또는 fade |
| 확인창 | fadeIn + slideUp |
| 드롭다운 | slideDown |
| 스켈레톤 | pulse |
tsx
// Framer Motion 또는 CSS transition
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
>
<ModalContent />
</motion.div>
반응형 디자인
모바일 우선 (Mobile First) 접근
| 브레이크포인트 | 범위 |
|---|---|
| sm | 640px |
| md | 768px |
| lg | 1024px |
| xl | 1280px |
tsx
// Tailwind 반응형 클래스
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map(item => <Card key={item.id} />)}
</div>
// 모바일 네비게이션
<nav className="hidden md:flex">Desktop Nav</nav>
<nav className="md:hidden">Mobile Nav</nav>