Skip to content

Conversation

@ywkim
Copy link
Contributor

@ywkim ywkim commented Jan 31, 2026

요약

신규 팀원을 위한 자가학습 온보딩 슬라이드 페이지를 추가합니다.

목적

프로젝트 아키텍처 패턴(Repository, Data Provider)을 7개 슬라이드로 설명하여 신규 팀원의 온보딩을 지원합니다.

주요 개선 사항

📚 슬라이드 콘텐츠 (7개)

학습 흐름 최적화: 전체 구조 → 공통 규약 → 구현

  1. 프로젝트 계층 구조 (FE/BE 공통)
  2. 응답 형식 - { data: ... } 래퍼와 Refine 호환성
  3. 쿼리 파라미터 - FE/BE 비교
  4. aioia-core (프론트엔드) - BaseCrudRepository
  5. aioia-core (백엔드) - BaseCrudRouter
  6. Factory 패턴 (백엔드) - DI 구현
  7. 새 파일 생성 전 체크리스트

🔗 외부 리소스 연결

  • Refine Data Provider 공식 문서, Martin Fowler Repository 패턴
  • @aioia/core npm 패키지, aioia-core PyPI 패키지
  • BaseCrudRepository/BaseCrudRouter/BaseRepositoryFactory GitHub 소스

🎨 UI 컴포넌트

  • SlideContainer, Slide, CodeBlock, ProgressIndicator

테스트 체크리스트

  • /ko/onboarding 페이지 접근
  • 이전/다음 버튼 및 키보드 화살표로 슬라이드 이동
  • 외부 링크가 새 탭에서 열림
  • 다크모드 스타일 정상 표시

🤖 Generated with Claude Code

ywkim and others added 9 commits January 31, 2026 10:54
온보딩 자가학습 슬라이드를 위한 기본 컴포넌트:
- Slide: 개별 슬라이드 래퍼 (StepCard 패턴 적용)
- SlideContainer: 네비게이션 컨테이너 (키보드 좌/우 지원)
- ProgressIndicator: 진행률 표시 (프로그레스 바 + 도트)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
온보딩 슬라이드에서 코드 예제 표시를 위한 CodeBlock 컴포넌트.
모노스페이스 폰트와 neutral 배경색으로 가독성 확보.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
/[lang]/onboarding 경로에 자가학습 슬라이드 페이지 추가.
5개 슬라이드 구조 (콘텐츠는 별도 커밋에서 추가 예정).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5개 슬라이드 콘텐츠 추가:
1. 왜 이 구조인가 - Refine Data Provider 요구사항 설명
2. 프로젝트 계층 구조 - repositories, services, schemas, mocks
3. aioia-core 패턴 - BaseCrudRepository 사용 예제
4. 백엔드 응답 형식 - SingleItemResponse, PaginatedResponse
5. 체크리스트 - 새 파일 생성 전 확인사항

출처 표기: Repository 패턴(Martin Fowler, PoEAA)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
onboarding 네비게이션 UI 번역 키 추가:
- title, subtitle: 페이지 제목
- previous, next, done: 네비게이션 버튼
- keyboardHint: 키보드 단축키 안내

지원 언어: en, es, id, ja, ko, th, tl, vi, zh

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- rounded → rounded-sm (eslint 규칙)
- h-2 w-2 → size-2 (eslint 규칙)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
개선 사항:
- 카드 가로 폭 확장 (max-w-[600px] → max-w-3xl)
- 모든 출처 클릭 가능하게 (Refine 공식 문서, Martin Fowler PoEAA)
- 프론트엔드/백엔드 계층 명시 + Frontend 배지 추가
- mocks/ 설명 수정: "Storybook/테스트용 Mock 데이터. 타입별 fixture 제공"
- aioia-core 패키지 및 소스 코드 링크 추가 (npm, GitHub)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 온보딩 전용 layout.tsx 추가 (fixed 레이아웃으로 부모 제약 우회)
- 부모 layout의 max-w-mobile(393px) 제약을 무시하고 전체 너비 사용
- page.tsx에 w-full 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6개 슬라이드로 재구성:
1. 프로젝트 계층 구조 (프론트엔드/백엔드 공통)
2. aioia-core (프론트엔드) - @aioia/core, BaseCrudRepository
3. aioia-core (백엔드) - aioia-core (PyPI), BaseCrudRouter
4. Factory 패턴 - BaseRepositoryFactory, DI
5. 응답 형식 - { data: ... } 래퍼 (Admin 도구 Refine 호환)
6. 체크리스트

추가된 링크:
- aioia-core PyPI
- BaseRepositoryFactory 소스

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

신규 팀원을 위한 자가학습 온보딩 슬라이드 추가는 매우 좋은 아이디어입니다. 프로젝트의 아키텍처를 이해하는 데 큰 도움이 될 것입니다. 전반적으로 컴포넌트 구조와 구현이 잘 되어 있지만, 몇 가지 개선점을 제안합니다.

주요 피드백은 다음과 같습니다:

  • 다국어(i18n) 미적용: 새로 추가된 컴포넌트들(OnboardingPage, SlideContainer, ProgressIndicator)의 사용자 표시 텍스트가 대부분 하드코딩되어 있습니다. 이미 추가된 다국어 리소스를 활용하여 react-i18next를 적용해야 합니다.
  • 스타일 가이드 및 규칙 준수: 일부 코드에서 AIdol 개발 가이드의 타이포그래피 규칙(77행)과 조건부 클래스 관리 규칙(64행)을 위반한 부분이 발견되었으며, 스타일 일관성 관련 규칙에 따라 디자인 드래프트에 맞춰 통일해야 합니다.
  • 리팩토링 기회: OnboardingPage의 슬라이드 렌더링 로직은 데이터 기반 접근 방식으로 리팩토링하여 유지보수성을 높일 수 있습니다.

자세한 내용은 각 파일에 대한 개별 코멘트를 참고해 주세요.

Comment on lines +344 to +349
<h1 className="text-headline-s text-base-content">
프로젝트 아키텍처 가이드
</h1>
<p className="text-body-m text-base-content/60">
자가학습 온보딩 슬라이드
</p>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

페이지 제목과 부제목이 한국어로 하드코딩되어 있습니다. 이 컴포넌트는 "use client"로 선언되어 있으므로 react-i18nextuseTranslation 훅을 사용하여 다국어를 지원해야 합니다. ko/aidol.jsononboarding.titleonboarding.subtitle 키가 이미 추가되어 있습니다.

슬라이드 내부의 모든 텍스트(제목, 본문 등)도 동일하게 다국어 처리가 필요합니다. 이는 AIdol 개발 가이드의 다국어 지원 원칙(89행)에 해당합니다.

import { useTranslation } from "react-i18next";
// ...
export default function OnboardingPage(...) {
  const { t } = useTranslation("aidol");
// ...
<h1 className="text-headline-s text-base-content">
  {t("onboarding.title")}
</h1>
<p className="text-body-m text-base-content/60">
  {t("onboarding.subtitle")}
</p>

Comment on lines +82 to +96
Previous
</button>
<button
type="button"
onClick={goToNext}
disabled={isLastSlide}
className="btn btn-primary btn-sm flex-1"
>
{isLastSlide ? "Done" : "Next"}
</button>
</div>

{/* Keyboard hint */}
<p className="text-body-s text-base-content/40 text-center">
Use ← → arrow keys to navigate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

네비게이션 버튼의 텍스트("Previous", "Next", "Done")와 키보드 사용 안내 문구가 영어로 하드코딩되어 있습니다. 이 문자열들은 이미 aidol.json 파일에 onboarding 키로 추가되었으므로, useTranslation 훅을 사용하여 다국어를 지원해야 합니다. 이는 AIdol 개발 가이드의 다국어 지원 원칙(89행)에 위배됩니다.

Suggested change
Previous
</button>
<button
type="button"
onClick={goToNext}
disabled={isLastSlide}
className="btn btn-primary btn-sm flex-1"
>
{isLastSlide ? "Done" : "Next"}
</button>
</div>
{/* Keyboard hint */}
<p className="text-body-s text-base-content/40 text-center">
Use arrow keys to navigate
{t("onboarding.previous")}
</button>
<button
type="button"
onClick={goToNext}
disabled={isLastSlide}
className="btn btn-primary btn-sm flex-1"
>
{isLastSlide ? t("onboarding.done") : t("onboarding.next")}
</button>
</div>
{/* Keyboard hint */}
<p className="text-body-s text-base-content/40 text-center">
{t("onboarding.keyboardHint")}

Comment on lines 76 to 338
const renderSlide = (index: number) => {
switch (index) {
case 0:
return (
<Slide slideNumber={1} title="프로젝트 계층 구조">
<p className="text-body-m text-base-content mb-4">
프론트엔드와 백엔드가 동일한 계층 구조를 공유합니다.
</p>
<div className="grid grid-cols-2 gap-4">
<div>
<h3 className="text-title-s text-primary mb-2">Frontend</h3>
<ul className="text-body-s text-base-content/80 space-y-1">
<li>
<strong>repositories/</strong> - 데이터 접근
</li>
<li>
<strong>services/</strong> - 비즈니스 로직
</li>
<li>
<strong>schemas/</strong> - Zod 스키마
</li>
<li>
<strong>mocks/</strong> - 테스트 fixture
</li>
</ul>
</div>
<div>
<h3 className="text-title-s text-primary mb-2">Backend</h3>
<ul className="text-body-s text-base-content/80 space-y-1">
<li>
<strong>api/</strong> - FastAPI 라우터
</li>
<li>
<strong>models/</strong> - SQLAlchemy 모델
</li>
<li>
<strong>repositories/</strong> - 데이터 접근
</li>
<li>
<strong>schemas/</strong> - Pydantic 스키마
</li>
<li>
<strong>services/</strong> - 비즈니스 로직
</li>
<li>
<strong>factories.py</strong> - DI 팩토리
</li>
</ul>
</div>
</div>
<p className="text-body-s text-base-content/60 mt-4">
출처:{" "}
<a
href={LINKS.martinFowlerRepository}
target="_blank"
rel="noopener noreferrer"
className="link"
>
Repository 패턴 - Martin Fowler, PoEAA
</a>
</p>
</Slide>
);
case 1:
return (
<Slide slideNumber={2} title="aioia-core (프론트엔드)">
<p className="text-body-m text-base-content">
<a
href={LINKS.aioiaCoreNpm}
target="_blank"
rel="noopener noreferrer"
className="link link-primary"
>
@aioia/core
</a>
에서 제공하는{" "}
<a
href={LINKS.baseCrudRepositorySource}
target="_blank"
rel="noopener noreferrer"
className="link link-primary"
>
BaseCrudRepository
</a>
를 상속합니다.
</p>
<CodeBlock
code={FRONTEND_REPOSITORY_CODE}
language="TypeScript"
title="repositories/CompanionRepository.ts"
/>
<div className="text-body-s text-base-content/80 space-y-1">
<p>
<strong>resource:</strong> API 엔드포인트 경로
</p>
<p>
<strong>getDataSchema():</strong> Zod 스키마로 응답 유효성 검증
</p>
</div>
</Slide>
);
case 2:
return (
<Slide slideNumber={3} title="aioia-core (백엔드)">
<p className="text-body-m text-base-content">
<a
href={LINKS.aioiaCorePyPI}
target="_blank"
rel="noopener noreferrer"
className="link link-primary"
>
aioia-core
</a>{" "}
(PyPI)에서 제공하는{" "}
<a
href={LINKS.baseCrudRouterSource}
target="_blank"
rel="noopener noreferrer"
className="link link-primary"
>
BaseCrudRouter
</a>
를 상속합니다.
</p>
<CodeBlock
code={BACKEND_ROUTER_CODE}
language="Python"
title="api/companion.py"
/>
<div className="text-body-s text-base-content/80 space-y-1">
<p>
<strong>prefix:</strong> URL 경로 접두사
</p>
<p>
<strong>tags:</strong> OpenAPI 문서 태그
</p>
</div>
</Slide>
);
case 3:
return (
<Slide slideNumber={4} title="Factory 패턴 (백엔드)">
<p className="text-body-m text-base-content">
<a
href={LINKS.baseRepositoryFactorySource}
target="_blank"
rel="noopener noreferrer"
className="link link-primary"
>
BaseRepositoryFactory
</a>
를 상속하여 의존성 주입(DI)을 구현합니다.
</p>
<CodeBlock
code={FACTORY_CODE}
language="Python"
title="factories.py"
/>
<div className="text-body-s text-base-content/80 space-y-1">
<p>
<strong>db_session:</strong> SQLAlchemy 세션 (자동 주입)
</p>
<p>
<strong>@property:</strong> 지연 초기화로 필요할 때만 생성
</p>
</div>
</Slide>
);
case 4:
return (
<Slide slideNumber={5} title="응답 형식">
<p className="text-body-m text-base-content">
모든 API 응답은{" "}
<code className="bg-base-300 rounded-sm px-1">
{"{ data: ... }"}
</code>{" "}
형식으로 래핑됩니다.
</p>
<CodeBlock
code={RESPONSE_CODE}
language="TypeScript"
title="응답 타입"
/>
<div className="bg-info/10 border-info rounded-lg border-l-4 p-4">
<p className="text-body-s text-info-content">
<strong>설계 이유:</strong> Admin 도구에서{" "}
<a
href={LINKS.refineDataProvider}
target="_blank"
rel="noopener noreferrer"
className="link"
>
Refine 프레임워크
</a>
를 사용합니다. aioia-core가 Refine Data Provider 구조와 호환되어
여러 프로젝트를 통합 관리할 수 있습니다.
</p>
</div>
</Slide>
);
case 5:
return (
<Slide slideNumber={6} title="체크리스트">
<p className="text-body-m text-base-content mb-4">
새 파일 생성 전 확인사항
</p>
<div className="text-body-m text-base-content space-y-3">
<label className="flex items-start gap-3">
<input
type="checkbox"
className="checkbox checkbox-primary mt-1"
readOnly
/>
<span>
<code className="bg-base-300 rounded-sm px-1">
repositories/
</code>
에 기존 Repository가 있는지 확인
</span>
</label>
<label className="flex items-start gap-3">
<input
type="checkbox"
className="checkbox checkbox-primary mt-1"
readOnly
/>
<span>
<code className="bg-base-300 rounded-sm px-1">schemas/</code>
에 관련 스키마가 있는지 확인
</span>
</label>
<label className="flex items-start gap-3">
<input
type="checkbox"
className="checkbox checkbox-primary mt-1"
readOnly
/>
<span>
aioia-core 베이스 클래스를 상속하고 있는지 확인
(BaseCrudRepository, BaseCrudRouter)
</span>
</label>
<label className="flex items-start gap-3">
<input
type="checkbox"
className="checkbox checkbox-primary mt-1"
readOnly
/>
<span>
API 응답이{" "}
<code className="bg-base-300 rounded-sm px-1">
{"{ data: ... }"}
</code>{" "}
형식인지 확인
</span>
</label>
</div>
</Slide>
);
default:
return null;
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

renderSlide 함수가 매우 길고 switch 문이 반복적인 구조를 가지고 있습니다. 각 슬라이드의 데이터를 배열로 분리하고, 이를 기반으로 슬라이드를 렌더링하는 데이터 기반 접근 방식을 사용하면 코드의 유지보수성이 향상되고 중복을 줄일 수 있습니다.

예를 들어, 슬라이드 제목, 내용, 코드 스니펫 등을 담은 객체 배열을 만들고 이를 map으로 순회하여 Slide 컴포넌트를 렌더링하는 방식을 고려해볼 수 있습니다. 이렇게 하면 TOTAL_SLIDES 상수도 데이터 배열의 길이에 따라 동적으로 관리할 수 있습니다.

<div className="bg-base-300 text-body-s text-base-content/70 px-4 py-2">
{title}
{language && (
<span className="text-primary ml-2 text-xs">{language}</span>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

text-xs 클래스는 Tailwind의 기본 유틸리티로, AIdol 개발 가이드의 타이포그래피 규칙(77행)에 위배됩니다. tailwind.config.ts에 정의된 MD3 타입 스케일(text-label-m 등)을 사용해야 합니다. text-xs0.75rem이므로 text-label-m으로 대체하는 것이 적절해 보입니다.

Suggested change
<span className="text-primary ml-2 text-xs">{language}</span>
<span className="text-primary ml-2 text-label-m">{language}</span>
References
  1. AIdol development guide's typography rules (line 77) are violated. MD3 type scale defined in tailwind.config.ts should be used instead of basic Tailwind utilities like text-xs. (link)
  2. When there is a style inconsistency, the style should be unified to match the design draft, rather than just making them consistent with each other.

Comment on lines +40 to +44
className={`size-2 rounded-full transition-colors ${
index === currentIndex
? "bg-primary"
: "bg-base-300 hover:bg-base-content/30"
}`}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

조건부 클래스를 적용하기 위해 템플릿 리터럴을 사용하고 있습니다. AIdol 개발 가이드(64행)에 따라 clsx 라이브러리를 사용하여 조건부 클래스를 관리해야 합니다. 파일 상단에 import clsx from "clsx";를 추가하는 것을 잊지 마세요.

Suggested change
className={`size-2 rounded-full transition-colors ${
index === currentIndex
? "bg-primary"
: "bg-base-300 hover:bg-base-content/30"
}`}
className={clsx(
"size-2 rounded-full transition-colors",
index === currentIndex
? "bg-primary"
: "bg-base-300 hover:bg-base-content/30",
)}
References
  1. AIdol development guide (line 64) requires using the clsx library for managing conditional classes instead of template literals. (link)

? "bg-primary"
: "bg-base-300 hover:bg-base-content/30"
}`}
aria-label={`Go to slide ${index + 1}`}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

aria-label의 텍스트가 "Go to slide"로 하드코딩되어 있어 다국어를 지원하지 않습니다. 스크린 리더 사용자를 위해 이 부분도 번역되어야 합니다. useTranslation 훅을 사용하여 번역된 문자열을 적용해 주세요.

ywkim and others added 6 commits January 31, 2026 12:09
- Factory 패턴 코드 예제를 실제 패턴으로 수정 (Repository별 개별 Factory)
- "범용 REST API 표준이 아닌" 설명 복원
- 쿼리 파라미터 슬라이드 추가 (Slide 6)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
팀원 학습 흐름 개선:
- 전체 구조 → 공통 규약 → 구현 순서로 재배치
- FE/BE 미숙 팀원이 맥락 이해 후 구현 코드 학습

순서 변경:
1. 계층 구조 (유지)
2. 응답 형식 (기존 5번)
3. 쿼리 파라미터 (기존 6번)
4. FE Repository (기존 2번)
5. BE Router (기존 3번)
6. Factory 패턴 (기존 4번)
7. 체크리스트 (유지)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Zod 스키마: 런타임 검증 목적 설명 (TS 타입 vs Zod, Sentry 보고)
- 쿼리 파라미터: Refine Data Provider 호환 형식임을 명시

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add providers/ pattern (LLMProvider Protocol)
- Add services/ pattern (ResponseGenerationService)
- Reference chatroom domain implementation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants