모바일 입력·초안·복구를 공통 편집기로 연결 (2026-09-10)
- 기간: 2026-09-10, Codex 1세션. 오너 지시: “분석 끝나면 바로 진행해”.
- 랜딩: #1420 U02,
release/v0.18.0대상. 앱 PR #1523, HEAD4b65cd22→ mergeb0fa93ec97fab41c6bcbc59bd096f3e1926535fd, 2026-09-10 16:50 KST release 반영. Merge Check 성공. staging/Production 배포는 이번 작업에 포함하지 않는다. DB/Edge/migration 변경 없음. - 설계서: 이슈 분석·계획.
- 정본: 모바일 편집 연결 계약, A12, A13.
- 도구: 앱
tests/browser/u02/의 실제 모바일 binding·화면, Chromium, S08 IndexedDB, 통제된 저장/복구 응답. 작업 로그는 로컬 work/에 보관한다. - 게이트: Phase마다 check, 최종 unused/동일 입력 fixture/브라우저, PR 전 precheck. 최종 수치는 아래에 기록하며 Merge Check는 성공했다. 전체 CI 실행으로 보고하지 않는다.
- 버그리포트: BUG-094 유산소 표시, BUG-095 수행 IME.
- 계약: typed view/command, 원문·조합 입력, 저장 응답과 최신 초안, 명시적 복구, U07 선택자와 U06/N01 미실행 범위.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | A12 상태·명령·typed view 연결 | 구현·check 완료, 68d50849·c4f7e548 |
| Phase 2 | 실제 A13 계획과 원문·IME·단위·복합 입력 | 구현·check 완료, 7f5117ad·2afd0014 |
| Phase 3 | S08 초안·A09 저장 수명·늦은 영수증 | 구현·check 완료, 9aaf8646 |
| Phase 4 | S09 명시적 복구 화면과 owner runtime | 구현·check 완료, 35f97c0b |
| Phase 5 | 브라우저·공통 fixture·중복 제거·문서 | 구현·check 완료, 1359a53d·4b65cd22, release b62a358c 통합 |
1. 배경
A12/A13은 이미 공통 입력·저장 모델을 갖고 있었지만 모바일은 화면 배열을 바꾼 뒤 저장 순간 다시 열어 검사했다. 화면 내부의 숫자 변환과 완료 판단, 초안 재수화가 공통 모델과 분리되어 있었다. U02는 이 화면 이벤트를 실제 공통 명령으로 연결하는 이식이다.
2. 문제 제기
입력 중인 12.와 빈 값, 실패 0회는 서로 다르다. 화면이 숫자로 먼저 바꾸면 저장·재접속 때 의미를 잃는다. 저장 중 사용자가 더 입력한 뒤 과거 응답이 돌아오면 초안 삭제나 화면 닫힘이 최신 입력을 덮을 수 있다. 복구도 목록 열람과 실제 원본 복원 명령을 구분해야 한다.
브라우저에서 유산소 3. 입력 시 canonical 값이 일시 null이 되어 시간 칸이 사라지는 결함, 수행 입력의 compositionstart가 공통 상태에 전달되지 않아 저장 버튼이 잠기지 않는 결함을 재현했다. Production에 동일 결함이 있었는지는 확인하지 않았으며 이번 이식에서 수정했다.
3. 해결 방안
| 안 | 판단 | 이유 |
|---|---|---|
| 화면 배열·숫자 변환을 유지하고 guard·타이머를 추가 | 기각 | 입력과 저장의 판단 사본이 계속 갈라진다 |
| 실제 이벤트를 A12/A13 명령에 연결하고 typed view만 표현 | 채택 | 원문·검증·직렬화·ID·완료 의미가 한 모델에 있다 |
| 저장 응답마다 현재 초안을 비우기 | 기각 | 요청 이후 입력을 덮는다 |
| 요청별 원문·operation 및 최신 초안을 별도 추적하고 durable 후 정확히 정리 | 채택 | 늦은 응답과 계정 전환에서 현재 편집을 보호한다 |
4. 적용한 내용
Phase 1~2 — 입력
mobileWorkoutEditor는 A12, mobilePlanEditor는 A13을 실제로 소비한다. WorkoutRecord/Parts는 입력 원문과 composition 시작/끝을 명령으로 전달하고, lb/%·분:초/km·복합 동작·실패 0회·세트 재정렬도 같은 모델을 사용한다. 분:초 blur 정리는 공통 formattedInput에서 유효한 입력만 처리한다. 모바일 계획의 기존 저장 정책은 유지한다.
Phase 3 — 초안과 저장
mobileWorkoutFlow가 화면 단계·저장 시도·정확한 요청 본문을 소유한다. S08이 debounce와 내구 보관을 맡는다. 완료 후 기록으로 돌아와 더 편집해도 이전 저장 응답은 새 입력을 덮지 않는다. queued 응답과 서버 receipt를 구분하고 자식 ID·통계 세대는 확정 근거에서만 적용한다. 계정 세대 변경 뒤 응답은 현재 초안을 바꾸지 않는다.
Phase 4 — 복구
기존 recordsTool 스택의 보관·복구 화면이 S09 목록·미리보기·요청 상태를 표시한다. 현재 값을 못 읽으면 미확인으로 쓴다. 확인 버튼 전에는 전송하지 않고, stale 응답 뒤 새 미리보기를 받아도 다시 확인해야 한다. 실제 committed 영수증만 완료로 표시한다.
Phase 5 — 제거와 검증
WorkoutFlow의 로컬 makeFlowSet/Exercise/Draft, 재진입·수정 여부·조기 종료 사본을 제거했다. 공통 시간 판단은 feature로 옮겼다. GroupComposer의 초안/숫자 팩토리는 제거하고 canonical ID와 표시 메타 투영만 mobileGroupDraft에 남겼다. 공통 raw 직렬화는 U03 변경을 통합해 재사용한다.
구 UI 함수명을 검사하던 기존 단언은 실제 editor 경계·렌더 fixture로 옮겼다. manifest 등재 변경만 신고했다. 최신 release의 #1478 제외 정책을 보존했다. 문서 저장소의 U03·D06·D10 기록은 통합 시 함께 유지했다.
5. 적용 결과
| 항목 | 전 → 후 또는 확인 결과 |
|---|---|
| 실제 숫자/저장 판단 | 화면 상태 사본 + 저장 시 guard → A12/A13 동기 명령과 typed view |
| 분:초 미완성 입력 | 시간 칸 소실 재현 → 칸·원문 유지, 유효한 2분+3.5초는 123.5초 |
| 수행 IME | composition 중 저장 허용 재현 → composition 동안 잠김, 종료 후 유효성 적용 |
| 공통 fixture | 실제 모바일/데스크톱 7개 모두 같은 validation·payload·ID·휴식·실패 목표 |
| 브라우저 | 22/22 통과(34초). 실제 binding·IndexedDB·복구와 320/390/430px, 드래그/owner 세대/recordsTool 브라우저 뒤로가기/종료 overlay·포커스 |
| check/precheck | check 3,639개: 3,572 통과·67 skip·0 실패. unused 신규 0. Precheck 2분 12초 통과: static + unit-1 1,673/41 skip + unit-2 1,899/26 skip, 실패 0 (HEAD 4b65cd22, base b62a358c) |
| Production | public GET 200과 기존 배포 성공만 확인. 인증 기록 쓰기·복구·전체 여정 미실측 |
| native | OS 키보드·safe-area·기기 뒤로가기 조합 미실행. Chromium 결과로 대체하지 않음 |
6. 이번 개선으로 향상된 것
사용자가 입력 중인 원문은 저장 가능 값과 구분되어 재진입 뒤에도 이어진다. 늦은 저장 응답은 새 입력을 지우지 않고, 보관·전송·서버 확정·통계 반영 상태가 근거에 맞춰 표시된다. 복구 전후 차이를 직접 확인하고 복원할 수 있다.
U03은 tests/fixtures/u03EditorParity.mjs와 mobileDesktopEditorParity.test.mjs를 공유한다. U07은 선택자 표를 사용한다. U06/N01은 native 미실행 범위를 전달받는다. 다른 모바일 화면 전체나 full CI·Production을 이번 U02 검증으로 완료 표시하지 않는다.