모바일 온보딩 화면 props 계약 (UiOnboarding)
화면 픽스처(앱 저장소):
src/react/ui/mobile/fixtures/OnboardingScreen.fixture.ts
src/react/ui/mobile/screens/OnboardingScreen.tsx · 2026-08-04 신설. 순수 프레젠테이션 — OAuth 실행·계정 생성·아이디 중복 검사·저장·라우팅은 전부 컨테이너 소유. 시안: mobile/preview/_design-onboarding-flow.html (Claude 전용).
플로우
로그인(view="login") → [컨테이너 OAuth] → view="setup": 약관 → 프로필+아이디 → 신체 → 메인 종목(직업, 5스텝 중 4) → 1RM → 웰컴 → onEnterHome(). 스텝 순서는 데스크톱(약관→신체→프로필)과 다르다 — 2026-08-04 지시(프로필이 2번). 데스크톱 동조는 Codex 후속.
Props
| prop | 타입 | 설명 |
|---|---|---|
| view | "login" | "setup" | 기본 "login". 전환은 컨테이너(OAuth 후 프로필 미완성 → setup, 기존 회원 → 홈 직행) |
| socialProfile | { nickname?, photo? } | null | 소셜에서 온 값 — 프로필 스텝은 확인·수정 |
| suggestedHandle | string | 아이디 초기 제안(소셜 닉네임 기반 생성은 컨테이너) |
| oneRmExercises | Array<{ id, onboardingKey, name, nameEn?, unit?, glyph? }> | null | 1RM 스텝 종목 풀 카탈로그 — 컨테이너가 DB 발급 UUID와 onboardingKey를 함께 주입한다. 표시 목록은 선택 직업의 onboardingKey 순서로 필터링하며 UI가 종목 식별자를 지어내지 않는다. |
| startDateLabel | string | 웰컴 요약의 시작일 표기 (예: "8/4") |
| welcomeAutoMs | number | >0이면 웰컴에서 타이머 자동 진입 병행 (기본 0 = 버튼만) |
| onSocialLogin | (provider: "kakao" | "apple" | "google") => unknown | 로그인 버튼 |
| onOpenTerms | (kind: "terms" | "privacy") => unknown | 약관 전문 열람 라우팅(약관·처리방침 2건 한정, 구 호출자 호환) |
| consentPlan | ConsentPlanItem[] | null | 동의 단계 항목(#1625, 2026-09-15) — 서버 요구 onboarding_consent_requirements를 resolveConsentPlan으로 옮긴 목록 { consentType, documentVersion, required, accepted, path, label, summary }. 비어 있으면 현재 등재 약관·처리방침 2건으로 그린다. 필수/선택 판정은 서버 required 값이며 화면이 바꾸지 않는다 |
| onOpenDocument | (path: string) => unknown | 동의 항목의 전문 보기 — consentPlan[].path(/legal/*.html)를 인앱 뷰어로 연다. 동의문 3종(민감정보 수집·이용, 프로필·팔로우 공개, 민감정보 제공)도 이 경로 |
| mode | "onboarding" | "consentRefresh" | 기본 "onboarding". consentRefresh = 가입은 끝났지만 서버 요구 필수 동의가 새 버전으로 바뀐 회원의 재동의 — 동의 단계만 보이고 onSubmitConsents로 끝난다 |
| onSubmitConsents | (decisions: OnboardingConsentDecision[]) => unknown | 재동의 제출(apply_user_consents_v1). async 허용 — reject 시 인라인 오류 |
| allowDeferConsents / onDeferConsents | boolean / () => unknown | 기존 회원 유예 기간(2026-10-15 전)에는 "나중에"를 보여 이 세션 동안 미룬다. 유예가 끝나면 컨테이너가 false를 준다 |
| onOpenServiceInformation | () => unknown | 현재 운영자·문의처·팔로우 공개 범위 안내를 인앱 법률문서 뷰어로 열람. 아래 운영·공개 안내 계약 적용 |
| onComplete | (draft) => unknown | 1RM 스텝 종료(시작하기/건너뛰기) 시 1회. async 허용 — reject 시 인라인 오류 표시(publicErrorMessage) 후 재시도 가능 |
| onEnterHome | () => unknown | 웰컴 "홈으로" |
draft 형태 (onComplete 인자)
{
agreements: { terms: boolean; privacy: boolean }; // consentDecisions의 약관·처리방침 항목을 투영한 값(구 소비자 호환)
consentDecisions?: Array<{ consentType: string; documentVersion: string; required: boolean; accepted: boolean }>;
// #1625: consentPlan 항목별 실제 체크 값. 컨테이너는 이 목록을 그대로 complete_onboarding(consents)에 보낸다 —
// 약관 동의로 선택 동의를 참으로 추론하지 않고, 필수 항목이 하나라도 false면 어댑터 호출 전에 거부한다.
nickname: string;
handle: string; // 소문자·숫자·_ · 3~20자 — 2026-08-23부터 컨테이너가 complete_onboarding(payload.handle)로 저장(20260821470000). 중복이면 서버 23505 → 인라인 오류 "이미 사용 중인 아이디" 후 재시도
usePhoto: boolean;
avatarVariant: "photo" | "navy" | "lavender";
body: { sex: "" | "male" | "female"; h: number|null; w: number|null; m: number|null; f: number|null }; // f는 체지방률(%)
primaryTrainingStyle: "powerlifter" | "olympic" | "bulkup" | "bodyweight" | "gymnast" | "metcon";
oneRms: Array<{ exerciseId: string; value: number }>; // 입력한 종목만, exerciseId는 DB 발급 UUID
}마커
식별용 hook: mobileOnboarding, onboardingLogin, onboardingTerms, onboardingProfile, onboardingBody, onboardingOneRm, onboardingWelcome, onboardingAgeConfirm(만 14세 확인 — 동의 단계 첫 행, #1637), onboardingConsentAll(전체 동의), onboardingConsentRow(+data-consent-type, data-lg-state="checked"). action(재동의 모드만): onboarding.submitConsents, onboarding.deferConsents.
가입 동의 구조 (#1625, 2026-09-15)
- 로그인 화면(#1637, 2026-09-15 개정): 소셜 버튼 3개는 바로 열려 있다. 아이콘은 각 서비스 정식 아이콘(카카오 말풍선 심볼·Apple 로고·Google 4색 G,
shared/icons/socialProviderIcons)이며 하단 안내는 "가입 단계에서 이용약관과 개인정보처리방침 동의를 확인해요." 한 문장 + 운영·공개 안내 링크다. (종전 #1625의 로그인 화면 만 14세 확인·버튼 잠금은 폐기.) - 만 14세 확인(#1625 → #1637): 동의 단계의 첫 행
[필수] 만 14세 이상입니다(onboardingAgeConfirm,data-lg-state="checked"). "전체 동의"에 포함되고 확인 전에는 "다음"이 잠긴다(약관 v1 제4조 2항·처리방침 v1 제10항). 확인은 화면 상태이며 저장하지 않는다. 재동의 모드(기존 회원)에는 없다. - 동의 단계:
consentPlan을 위에서 아래로 그린다 — 약관(필수) → 처리방침(필수) → 민감정보 수집·이용(필수, 오너 결정 D1) → 프로필·팔로우 공개(선택) → 민감정보 제공(선택). 각 행 = 체크 · 필수/선택 배지 · 문구 · 한 줄 설명 · 전문 보기(onOpenDocument(path)). "전체 동의"는 선택 항목을 포함한다는 사실을 표시하고 항목별로 해제할 수 있다. "다음"은 필수 항목이 모두 체크돼야 열린다. 서버 요구에 선택 항목이 없으면(전환 전) 배지가 필수 2건뿐이다. - 재동의(
mode="consentRefresh"): 제목 "약관과 동의 항목이 새로워졌어요", 같은 행 구성, 버튼 "동의하고 계속하기"(onSubmitConsents) + 유예 중 "나중에". 제출 성공 뒤 컨테이너가 프로필을 새로 읽어 이 화면을 내린다. - 컨테이너(
onboardingBinding)는 서버 요구 목록을resolveConsentPlan으로 옮기고, 앱 사전에 없는 필수 항목이 오면 오류로 드러낸다(선택 항목은 생략). 약관·처리방침은 등재되지 않은 새 버전도 파일명 규칙/legal/<kind>-<version>.html로 연다. #1643(2026-09-15, 앱 v0.19.3)부터 그 HTML은 앱 자신의public/legal/이 제공하므로, 새 버전은 그 HTML이 실린 앱 릴리스가 먼저 나간 뒤 서버 요구 버전을 전환한다(문서 사이트에 먼저 게시하는 것만으로는 구 번들이 새 버전을 열 수 없다).
운영·공개 안내
v0.17.1 대상 변경(2026-09-07): 로그인과 약관 단계의 onOpenServiceInformation()은 /legal/service-info.html을 기존 legal 히스토리 레이어에서 연다. 뒤로가기는 뷰어를 닫고 원래 로그인·가입 단계로 돌아가며, OAuth 실행·온보딩 제출·동의 상태 변경을 유발하지 않는다. 앱 셸의 로그인 오류 화면도 같은 경로를 제공한다. #1643(2026-09-15, 앱 v0.19.3)부터 /legal/service-info.html을 포함한 /legal/*와 /account/delete.html은 문서 사이트 프록시가 아니라 앱 자신의 정적 파일이며, 앱 안 뷰어는 그 파일에서 본문을 읽는다.
로그인 문구는 가입 단계에서 동의를 확인한다고 안내한다. 로그인 버튼 클릭이나 운영 안내 열람을 동의로 간주하지 않는다. 약관 단계는 상대방의 개별 승인 없는 팔로우와 운동 기록에 포함된 체중·컨디션·메모의 열람 범위를 표시한다. 이 공개 안내는 별도의 민감정보 처리·제공 동의가 아니다. 기존 필수 동의 종류·버전·체크 상태·다음 단계 조건은 이 변경에서 유지한다.
현재 운영 안내는 버전별 동의 문서와 분리한다. v1~v5 원문·레지스트리·서버 요구 버전은 불변이다. v5와 별도 동의문 3종은 2026-09-15에 게시됐고(#1625 Phase 2) 앱 레지스트리의 현재 문서는 v5다. 서버 요구 버전의 v5 전환은 #1625 Phase 5에서 마이그레이션으로 한다. 법률문서 뷰어는 과거 원문 바깥에 현재 운영자·보호책임자·문의처와 운영 안내 링크를 표시한다. 공개 안내를 다시 여는 동안에는 같은 요약을 중복 표시하지 않는다.
Codex 후속
① 소셜 OAuth 3종(카카오·Apple·Google) 배선 + 중복 계정 연결 정책 ② 아이디 중복 검사 RPC → 완료(2026-08-23, 완료 시점 서버 판정 + 내 정보 탭 설정/변경) · 아이디 자동 제안(suggestedHandle)·실시간 검증 UI는 요청 시 추가 ③ 라우팅(비로그인 → login, 프로필 미완성 → setup, 완료 → 홈) ⑤ 1RM 종목 카탈로그 주입(글리프 에셋 경로 포함) ⑥ 데스크톱 UiDesktopOnboarding 스텝 순서·아이디 스텝 동조 ⑦ 회 단위 종목(풀업·딥스)의 기록 보드 ⑧ primaryTrainingStyle과 기록 탭·기본 즐겨찾기 매핑(6종 중 bulkup·metcon의 티어 계산 범위 확정 필요) ⑨ 1RM 카탈로그에 푸시업·머슬업·핸드스탠드 푸시업 공급(+글리프 에셋).
로그인 진입 전환 (2026-08-19 — 부팅 스플래시 연속)
로그인 락업(워드마크+태그라인)은 부팅 스플래시(AuthLoadingGate)와 동일 지오메트리·색(중앙·40px·152deg 그라디언트·글로우, stack padding-bottom 4vh)이다. 셸 LoginGateView(shell.css lg-login-*)도 동일 문법을 공유한다.
| 트리거 | 상태 변화 | 복귀 |
|---|---|---|
| 부팅 스플래시 해제 → 로그인 마운트 | 락업: 스플래시 위치(중앙)에서 −58px 상승(0.6s ease-out — 진입 첫 프레임 = 스플래시와 픽셀 동일) · 소셜 dock: +26px→0 상승 + 페이드 인(0.55s ease-out, 0.12s 지연) | 없음(원샷 — 재마운트 시 재생) |
- 컨테이너 조건: 스플래시 해제와 로그인 마운트 사이에 중간 프레임(흰 화면·스피너)을 끼우지 않는다 — 같은 렌더 패스에서 스왑해야 픽셀 연속이 성립한다(index.html·HostLoadingScreen 3단계 부팅 통일과 동일 원칙).
prefers-reduced-motion: reduce: 애니 생략, 정지 상태 즉시 렌더.- 판정·타이밍 로직 없음 — 순수 CSS 진입 애니메이션(표현). 데이터 prop 변화 없음.
단계 프리뷰 초기값 (#1568)
initialStep?: "terms" | "profile" | "body" | "style" | "onerm" | "welcome"와 initialDraft?: Partial<ObDraft>를 선택적으로 받는다. 마운트 시 로컬 입력의 시작값이며 기본값은 기존 약관 단계·미동의·미입력 상태다. 운영 컨테이너는 이 두 값을 공급하지 않는다. 디자인은 createOnboardingScreenFixtures(today)의 단계별 데이터로 직접 렌더하며 단계 변경 시 key를 바꿔 재마운트한다. 콜백 없는 프리뷰가 실제 동의나 온보딩 완료를 저장하지 않는다.
onReviewLogin?({ email, password }): Promise<void>는 일반 화면에 표시하지 않는 심사 로그인 콜백이다. 로그인 로고5회로만 창을 열며 데이터 fixture에는 포함하지 않는다. 자세한 진입·인증 경계는 Auth 계약을 따른다.