엄밀히는 아닙니다. 우리가 만든 건 하나의 유스케이스와 그 안을 흐르는 플로우이고, 시나리오는 그 플로우의 특정 경로(happy path / emergency / disconnect 등)를 가리킬 때 써야 정확합니다.
| 용어 | 정의 | 지금 만든 게 이것인가 |
|---|---|---|
| 유스케이스 (use case) | 하나의 goal — "트레이너가 회원을 수업시킨다" | ✅ 전체를 감싸는 단위 |
| 플로우 (flow) | 화면/상태 전이 시퀀스 | ✅ ExerciseSelect ↔ Console ↔ Live |
| 시나리오 (scenario) | 한 유스케이스 안의 특정 경로: 정상 완료 / 비상정지 / 연결 끊김 / 운동 교체 … | △ 플로우를 포함한 복합 개념 — 단독 호칭으론 부적절 |
→ 앞으로 정식 문서에선 "UC-01: 트레이너 주도 수업" 과 "S1~S4 시나리오"로 분리하는 쪽이 정확합니다. 캐주얼 대화에서 "기본운동 시나리오"로 쓰는 건 v1 관용어이므로 통합니다.
허브 패턴 — 세트 간 휴식/조정이 일어나는 상주 화면은 SessionConsole. LiveWorkoutScreen은 세트 실행 동안만 잠깐 push되었다가 pop되면서 다시 콘솔로 돌아옴.
각 화면이 MCU에 쏘는 명령 + 사용자가 조작하는 컨트롤. v1 gym 앱의 workout_setup / workout_progress를 v2 패키지 구조로 재구현한 매핑.
운동 카테고리 → 개별 운동 → 그립 선택 (필요 시). 첫 진입 시 1회 + "다른 운동" 시 재호출.
{exercise, grip?}트레이너가 세션 내내 상주하는 허브. v1 workout_setup 의 control surface 를 모두 포팅 — minimal viable 상태에서 기능 완전체로.
가동범위 초기화 (→ ResetRangeCommand) · 세트 설정 (→ SetConfigBottomSheet)[+0.5/-0.5] | [모드 버튼] | [+5/-5] — 모드 버튼 탭 시 WeightModeBottomSheet 열림세트 실행 동안만 push. 뒤로가기 버튼 자체 제거 + PopScope 완전 차단 — v1 gym workout_progress와 동일.
nameKo) · "Set N" 인디케이터AdjustWeightCommand ±0.5/±5) · 비상정지 (EmergencyStopCommand) · "세트 완료"세트/세션 완료 직후 띄우는 평가 시트. 건너뛰기 허용 — RPE 없이도 세트는 기록됨.
SessionConsole._startSet()의 BLE 명령 순서. v1 startWithFirstSet() 의 7-step 시퀀스로 완전 정렬. 순서 바뀌면 device 가 sync 안 됨.
NEW 모드별 세부 파라미터 송출 — Eccentric → SetEccLevel, Iso/Hydro/Vib → SetModeParam (0xF5 0x05 sub-id별 N개 프레임). NEW GetSystemInfo handshake 선두 — device state 초기 시드.
펌웨어 protocol.c 의 0xF5 0x05 [paramId] [u16 value] 한 프레임 = 한 파라미터. 13개 헬퍼 ctor (단위 변환 캡슐화).
SetEccLevelCommand (별개 0x69)
콘솔의 ±5/±0.5 stepper / 모드 버튼 / 드롭셋 세팅 / ROM 초기화 · 라이브의 비상정지 — 모두 즉시 송출. 드롭셋 자동 전이는 DropSetStepAdvancer 가 rep count 감지 시 SetTargetWeight(new kg) 발송.
같은 "트레이너 주도 수업" 유스케이스 내에서 분기하는 구체 경로. 그 중 S1·S3는 integ로 이미 검증, S2·S4는 수동 QA 또는 후속 integ 대상.
가장 흔한 happy path. RPE ≤ 7 + target 달성 → 다음 세트 +2.5 kg.
같은 수업 안에서 운동을 바꾸는 경로. 세션은 살아있고 새 ExerciseEntry만 추가.
세트 진행 중 위험 상황. EmergencyStopCommand 즉시 발송, 부분 기록은 폐기.
중간에 BLE transport가 끊기면 active set을 cutoffTrigger=timeout으로 마감하고 다이얼로그 제시.
모든 suite 녹색. 도메인/프로토콜은 pure Dart 유닛, UI 는 Flutter widget, e2e 는 MockDeviceGateway + 실 Supabase.
| 스위트 | 레벨 | 개수 | 결과 | 주 타깃 |
|---|---|---|---|---|
roomfit_protocol/test/commands_test | Unit | 29+ | ✅ | 신규 SetModeParamCommand 단위 변환 (×1/×10/×100) · 헬퍼 13개 |
roomfit_protocol 전체 | Unit | 113 | 113/113 ✅ | 프로토콜 codec · 26 Commands · 17 Responses |
roomfit_workout/test/domain/drop_set_config | Unit | 11 | 11/11 ✅ | DropSetConfig 누적 감량 / validation / advance / shouldAdvance |
drop_set_step_advancer_test | Unit | 6 | 6/6 ✅ | rep 도달 시 자동 advance · multi-step jump · reset |
next_set_preset_modes_test | Unit | 9 | 9/9 ✅ | withMode/withSetType · Eccentric ↔ Drop 상호배제 · intensity 교체 |
roomfit_workout 전체 | Unit | 45 | 45/45 ✅ | workout 도메인 전부 |
design_system/test | Widget | 4 | 4/4 ✅ | WeightStepperColumn — Single/Double grip 라벨 · 탭 콜백 · disabled |
roomfit_device/test | Unit + Widget | 39 | 39/39 ✅ | MockDeviceGateway · projectResponse · RepCycleGenerator · buildReportResponse |
mcu_simulation_test.dart | Integ (Mock) | 9 | 9/9 ✅ | S1 SetTargetWeight 투영 · S2 SetWeightMode · S3 8 reps · S4 RestDetector · S5 EmergencyStop · S6 ResetRange · S7 드롭셋 자동 전이 · S8 SetModeParam · S9 live stepper |
app_e2e_test.dart | Integ (Mock) | 7 | 7/7 ✅ | Console flow · preset 20→22.5kg · RestDetector 다이얼로그 · emergency · 수업 종료 |
auth_role_gate_e2e_test | Integ (실 Supabase) | 5 | 5/5 ✅ | role routing · display_name 렌더 |
| Total (패키지 unit + widget) | 201 | 201/201 ✅ | workout 45 + protocol 113 + design_system 4 + device 39 | |
실행: flutter test (각 패키지) · e2e 는 DEVICE_ID=<id> scripts/run-b2b-e2e.sh integration_test/<file>
v1 workout_setup/workout_progress의 기능을 v2 패키지 구조로 이식. 완료/일부/미완 단계적으로.
| v1 기능 | v2 구현 위치 | 상태 |
|---|---|---|
| 세트 시작 lifecycle (StartReport → SetWeightMode → SetTargetWeight → SetWeightPower) | SetExecutor.startSet | ✅ |
| 무게 ±0.5 / ±5 kg 실시간 조정 | SetControlBar + AdjustWeightCommand | ✅ |
| 비상정지 | EmergencyStopCommand | ✅ |
| 세트 간 무게/모드/side preset 조정 | SessionConsole + NextSetPreset | ✅ |
| 세션 타이머 (1Hz tick) | sessionElapsedProvider | ✅ |
| 뒤로가기 차단 (mid-set) | PopScope(canPop:false) + automaticallyImplyLeading:false | ✅ |
운동명 한글화 (nameKo) | AppBar title | ✅ |
| Progressive overload 제안 | NextSetPreset.fromLastSet (RPE ≤ 7) | ✅ |
| Eccentric 레벨 1~100 slider (0.5 kg 단위) | WeightModeBottomSheet + EccentricIntensity.level + SetEccLevelCommand | ✅ |
| 6 모드 세부 파라미터 (Iso / Hydro / Vib) | WeightModeBottomSheet + ModeIntensity sealed + SetModeParamCommand (0xF5 0x05 신규) | ✅ |
| 드롭세트 (1~3 단계 + 자동 전이) | DropSetConfig · DropSetStepAdvancer · SetConfigBottomSheet | ✅ |
| Eccentric ↔ Drop 상호배제 룰 | NextSetPreset.withMode/withSetType → IncompatibleModeAndSetTypeError | ✅ |
| Range of Motion 재측정 (리셋) | 콘솔 "가동범위 초기화" 버튼 → ResetRangeCommand(side:0) | ✅ |
| ConfigStatusBox 요약 패널 (설정 한눈에 보기) | design_system/molecules/config_status_box.dart | ✅ |
| 3-column 스테퍼 레이아웃 (v1 workout_control_box) | WeightStepperColumn × 2 + 가운데 모드 버튼 | ✅ |
| Single/Double grip 무게 표시 (각 / 총) | ConfigStatusBox 에서 자동 분기 | ✅ |
| 음성 가이드 토글 UI | ConfigStatusBox switch (실 TTS 연결 후속) | △ |
좌/우 독립 무게 (SetWeightCommand(L,LMode,R,RMode)) | 현재는 단일 weightKg 유지 — 표시만 각/총 분리. 실제 L/R 독립은 후속 | — |
| ROM 캘리브레이션 신규 등록 플로우 (리셋 말고 신규) | 후속 PR | — |
| 드롭셋 진행률 표시 (LiveWorkoutScreen) | 콘솔 설정 후속 PR | — |
| 코치마크 / 첫 사용자 가이드 | 후속 PR | — |
| BLE 연결 끊김 복구 다이얼로그 | LiveWorkoutFlowScreen._handleBleDisconnect | ✅ |
| 세션 abandoned 복구 | MainShellScreen._checkAbandonedSessions | ✅ |
packages/roomfit_protocol/lib/src/commands/command.dart (SetModeParamCommand)packages/roomfit_workout/lib/src/domain/models/{next_set_preset, drop_set_config, mode_intensity, set_type}.dartpackages/roomfit_workout/lib/src/domain/use_cases/drop_set_step_advancer.dartpackages/design_system/lib/src/components/molecules/{config_status_box, weight_stepper_column, set_config_bottom_sheet, weight_mode_bottom_sheet}.dartapps/b2b/lib/features/workout/presentation/screens/{session_console_screen, live_workout_flow_screen, live_workout_screen}.dartapps/b2b/integration_test/{app_e2e_test, mcu_simulation_test}.dart