Skip to content

[ARCHIVED 2026-08-19] 역사 자료 — 현행 규범이 아니다. 아카이브 사유와 대체 문서는 docs/README.md의 Archive 섹션을 참조.

React 앱 소스 아키텍처

한국어 번역본

이 문서는 원문(영어)의 한국어 번역이다. 정본은 원문이며, 계약·게이트 판단이 갈리면 원문을 따른다. 원문: docs/archive/feature-slice-architecture.md

갱신: 2026-06-14

이 프로젝트는 이제 React 앱을 유일한 프런트엔드 소스로 취급한다.

구 바닐라(vanilla) 프런트엔드 폴더들은 아카이브되지 않고 제거되었다. Claude Design과 Codex는 그 경로들을 참조로 사용해서는 안 된다.

정본(source of truth)

  • 앱 진입점: index.html -> src/react/vite/main.jsx
  • React 표면: src/react/
  • Supabase/데이터 파사드: src/react/services/barbelicApi.js
  • Supabase 리포지토리/인증/매퍼 모듈: src/react/services/barbelicRepository.js, src/react/services/supabaseAuth.js, src/react/services/barbelicMappers.js
  • 데이터 로딩·부트스트랩 전략: docs/architecture/data-loading-strategy.md
  • 디자인 마커 계약: src/react/contracts/designContract.js
  • 웹 앱 매니페스트: public/manifest.webmanifest
  • 오프라인 서비스 워커 지원: 현재는 제거됨. 필요하면 별도로 복원한다

소유권 경계

Claude Design이 소유하는 것:

  • 새로운 순수 UI 모듈이 도입될 때의 src/react/ui/**
  • src/react/ui/screens/HomeScreen.jsx, src/react/ui/screens/SessionScreen.jsx, src/react/ui/screens/WorkoutFlow.jsx 같은 순수 UI 화면
  • src/react/ui/styles/**
  • 프레젠테이션 마크업, 컴포넌트 위계, 시각 상태, 레이아웃, 애니메이션, fixture 프리뷰

Codex가 소유하는 것:

  • src/react/services/**
  • src/react/contracts/**
  • 데이터 하이드레이션, Supabase 읽기/쓰기, 인증, 영속화, 테스트
  • 순수 UI 화면으로 props/콜백을 전달하는 컨테이너 배선

목표 방향

단기 마이그레이션은 React 내부의 컨테이너/프레젠테이셔널 분리다:

txt
src/react/
  app.jsx                    thin compatibility export for GymApp
  appController.jsx          current composition root
  vite/
    main.jsx                 Vite entry and viewport shell
  services/
    barbelicApi.js           ESM data/auth facade
    barbelicRepository.js    Supabase reads/writes
    supabaseAuth.ts           Supabase client and provider-neutral auth helpers
    barbelicMappers.js       DB row <-> app data mapping
    barbelicShared.js        shared constants and pure helpers
  contracts/
    designContract.js         living data-lg contract
  ui/
    screens/
      SessionScreen.jsx       Claude-owned presentational screen
      HomeScreen.jsx          Claude-owned presentational screen
      RecordsDetail.jsx       Claude-owned record detail screen
      PrTools.jsx             Claude-owned PR management screens
      ProfileScreen.jsx       Claude-owned profile presentational screen
    fixtures/
      sessionFixture.js       Claude preview data
    styles/
      styles.css              Claude-owned app CSS
      session.css             future Claude-owned screen CSS

src/react/ui/**는 Supabase, barbelicApi, 영속 스토리지를 임포트해서는 안 된다. 열린 패널, 인라인 에디터, 스와이프 위치, 드래그 상태, 모달 상태, 일시적 토스트 표시 같은 UI 전용 로컬 상태는 가질 수 있다.

서버 데이터 변경은 props/콜백을 통해 이동한다:

jsx
<SessionScreen
  today={today}
  visibleMonth={visibleMonth}
  sessionsByDate={sessionsByDate}
  selectedDate={selectedDate}
  selectedDateSessions={selectedDateSessions}
  selectedSession={selectedSession}
  conditionByDate={conditionByDate}
  monthSummary={monthSummary}
  onSelectDate={handleSelectDate}
  onChangeMonth={handleChangeMonth}
  onGoToday={handleGoToday}
  onOpenSession={handleOpenSession}
  onCloseSession={handleCloseSession}
  onCreatePlan={handleCreatePlan}
  onEditPlan={handleEditPlan}
  onStartWorkout={handleStartWorkout}
  onUpdateSession={handleUpdateSession}
  onDeleteSession={handleDeleteSession}
  onSetCondition={handleSetCondition}
/>

동결된 화면 props 계약은 다음 문서에 기록되어 있다:

  • docs/contracts/session-screen-props.md
  • docs/contracts/workout-screen-props.md

데이터 계약

세션 데이터는 위계를 명시적으로 유지해야 한다:

js
{
  id: "uuid",
  source: "sessions",
  status: "completed",
  date: "2026-06-09",
  title: "벤치프레스 외 1종목",
  summary: "2종목 · 5세트",
  exercises: [
    {
      id: "exercise-id",
      name: "벤치프레스",
      review: "오늘은 락아웃이 안정적이었음",
      sets: [
        { id: "set-id", reps: 5, load: 60, difficulty: null, memo: "" }
      ]
    }
  ]
}

그 구분이 중요할 때, 완료된 운동 기록에는 source: "sessions"를, 계획되었거나 놓친 기록에는 source: "planned_sessions"를 사용한다.

디자인 계약

src/react/contracts/designContract.jsdata-lg-* 마커의 정본이다.

Claude는 PR 설명에서 새 마커를 요청할 수 있지만, 계약에 등록하고 테스트를 통과 상태로 유지하는 것은 Codex다. 마커 계약은 기능적 통합 표면이지 레이아웃이나 시각적 제약이 아니다.

SessionScreen 파일럿에서 Claude는 세션 객체에 source: "sessions" | "planned_sessions"를 유지하고, onDeleteSession(sessionId)가 아니라 onDeleteSession(session)을 호출해야 한다.