렌더 입자성 캠페인 Phase 3 — 파일럿 바인딩 (라이브 운동 화면)
통합 기록(형식 표준, Phase 현황·결과·남은 것): 2026-08-24-render-granularity.md
- 날짜: 2026-08-23 (Phase 2 #614 직후, 오너 결정 D2 (나))
- 목표: 컨테이너 계층에
WorkoutFlowBinding을 세워 Phase 4 확산의 템플릿(구독 → 조립 → 같은 props)을 만들고, renderCtx에서 드래프트 몫 키를 처음으로 은퇴시킨다. 화면(ui/**)은 무접촉 — 같은 props를 받는다.
바뀐 것
| 항목 | 파일 | 내용 |
|---|---|---|
| Phase 3-1 바인딩 | features/active-workout/WorkoutFlowBinding.tsx (신설) | useOwnerStores().workoutDraft를 uSES로 구독해 인텐트 파생(backfill·lockedStart·planMode)·제목·계획 편집 상태를 읽고, 라이브 스냅샷은 ref에서. mobileApp에 있던 props 조립(buildWorkoutPlanData 드래프트 몫·lgFlowInitialDraft·lgNormalizeWorkoutSnapshotForUi·buildMobileWorkoutYearStats)과 화면 lazy 정의(WorkoutFlow + workout.css)를 verbatim 이동. onDraftSnapshot은 스토어의 handleWorkoutSnapshot 직결 |
| Phase 3-1 mobileApp | mobileApp.tsx | 운동 화면 분기는 드래프트 밖 입력(plans·sessionDate·gymData·카탈로그·프로필·1RM·이력·볼륨·sessionsByDate)과 커맨드만 바인딩에 건넨다. 탭 이탈 persist는 useOwnerStores()의 스토어를 직접 읽는다. Suspense 경계(탭 골격 스켈레톤)는 그대로 mobileApp |
| Phase 3-2 키 은퇴 | appController.tsx | renderCtx에서 11키 은퇴: workoutStartOptions currentPlanDraft flowBackfill flowLockedStart workoutSnapshot workoutSnapshotRef selectedPlanDraft workoutPlanOptions liveTitle planName handleWorkoutSnapshot. 조립 루트의 buildWorkoutPlanData 호출은 양쪽 셸이 쓰는 exerciseCatalog만 남기고 제거(드래프트 몫 계산이 루트에서 사라짐). 루트는 게이트 키(flow·planMode·locked·isWorkoutActive)만 유지 |
데스크톱 셸은 은퇴한 키를 하나도 소비하지 않는다(실측). TS Pick<>가 은퇴 키의 잔여 소비자를 컴파일 단계에서 적발했다(0건).
검증
npm run check1807/1807(rebase 후) ·check:unused· ts-boundary(바인딩을 mobileApp과 같은 "mobile component bridge boundary"로 등재).- 재조준 1:
mobileCssBoundaries(WorkoutFlow lazy + 스타일시트 동일 경계를 바인딩 파일에서 확인, pending-changes 기존 항목에 사유 병합). 청크 프리로드 목록·Suspense fallback 계약은 mobileApp에 그대로 남아 변경 없음. - 브라우저 회귀 CASE-001·004·011·015 4/4(프로덕션 빌드, 로컬 스택).
- 계획 편집 모드 스모크(dev 서버, 임시 스크립트): 일지 → 오늘 날짜 시트 → 운동 계획 작성(flow plan mode, 바인딩 경유) → 종목·세트 입력 → 저장 →
planned_sessions행 생성, 콘솔 오류 0. - Phase 0-2 계측 재실행: 시나리오 ① 세트 입력당 루트 커밋 0 유지(Phase 1과 동일), ②·③ 불변.
전 → 후
| 지표 | 전 | 후 |
|---|---|---|
| renderCtx 키 | 176 | 165 (−11) |
| 드래프트 몫 props 조립 위치 | 조립 루트 + mobileApp | 바인딩 1곳(드래프트 스토어 직접 구독) |
| 바인딩 템플릿 | — | WorkoutFlowBinding (구독 → 조립 → 같은 props) |
| 렌더 효과 | — | 없음(계획대로 — 루트는 게이트를 위해 드래프트 스토어 구독 유지) |
남는 것(Phase 4 판정 재료)
- 루트가 드래프트 스토어 전체를 구독하는 구조는 그대로다. 제목 편집(liveTitle 변화)은 여전히 루트를 깨운다 — 홈 이어하기 카드의 원천이라 필요하다. 루트의 드래프트 구독을 게이트 슬라이스로 좁히려면 쓰기 커맨드 env의 드래프트 값(exercises·planName·saving·selPlanId) 도 ref/getter로 바꿔야 한다(핫 키 규칙) — 실측상 편집당 루트 커밋이 이미 0이라 Phase 4 후보 목록의 뒤쪽.