U03 — 고정 축소 화면에서 공통 입력·창 크기 대응 화면으로 (2026-09-10)
- 기간: 2026-09-10, Codex 1세션. 오너 지시: “바벨릭 #1421 진행해줘”, “분석 끝나면 바로 진행해”.
- 계획 ID: U03 / 리팩터링 3-3. 총괄, 이슈 #1421.
- 랜딩: release/v0.18.0 통합 완료 — 앱 PR #1520, merge
c1af78e8090cc7b6464770ee2331a6b4a819b902, 문서 PR #44. DB·Edge·migration 변경 없음. staging·Production 배포 없음. - 기준: release
a1d462a8, baseline fixture83f51695. 선행 U0145e58c33, A09ba3b77bf, A121514fc52, A13d5e12e9d, S0806db380c, S092518e156포함 확인. #13980fef8132, #1401365f16d4보존. - 설계서: 별도 artifact 없음. 이슈 분석·Phase 계획 댓글.
- 정본: desktop props, workout editor, plan editor, shell, CSS 인계, G05.
- 도구: 앱
tests/browser/u03의 실제 Desktop root/shell/editor fixture와 Chromium. work 폴더의 전후 CDP 계측 스크립트는 임시 파일이며 아래 수치·조건을 기록한다. fixture는 외부 API 호출 없이 UI/수명주기를 고정하며 실제 서비스 전구간 검증과 구분한다. - 게이트: 소유 신규 경로 7개는 G05 분류에 등록(미분류 82→75, 기존 75개를 본 작업에서 전체 해결로 보고하지 않음).
npm run check,npx playwright test --config tests/browser/u03/playwright.config.mjs,npm run ci:precheck-local -- --release origin/release/v0.18.0, 자기 PR의 Merge Check. 이번 작업은 DB/pgTAP 변경 없음. - 버그리포트: BUG-092 최신 입력 보존.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 현행 fixture·공통 editor 동치·layout 기준선 | 완료 83f51695 |
| Phase 2 | 계획 A13 / 완료 A12 typed adapter 분리 | 완료 cd7a3613 |
| Phase 3 | S08 최신 초안·지연 저장·owner, S09 typed 복구 | 완료 4eed602b, 85c642c6, 391619ba |
| Phase 4 | intrinsic host/shell/editor, common raw input | 완료 a9ad4725, adc70779 |
| Phase 5 | 실제 플랫폼 동치·최종 browser·문서·통합 검증 | 완료 f0b51ba1, Precheck 및 Merge Check 통과 |
1. 배경
계획 편집은 A13을 사용했지만 완료 기록도 계획 UI 상태와 backfill 추가 필드 조립을 통과했다. host는 1913×1063 캔버스를 창 비율로 축소했고 최소 배율 0.7 아래로 줄이지 않았다. U03은 기존 색·서체·카드·통계 의미를 유지하면서 입력의 소유자와 창 크기 대응 구조를 바꾼다.
Production www.barbelic.com 공개 응답은 HTTP 200이고 당시 app-DNcxNjoT.css/app-C-ftLo4w.js/desktopRoot-eX47sKLU.js에서 고정 캔버스 계산을 확인했다. 인증 데이터·사용자 기록에 쓰기는 하지 않았으며 Production에서 모든 화면이 잘못 보였다고 주장하지 않는다.
2. 문제 제기
완료 기록의 입력 판단이 계획 표시 상태에 섞여 있었다
휴식·실패·자식 ID·메모를 다시 붙이는 별도 상태가 필요했다. 사용자가 12.를 입력하는 중인지 0인지 구분하고 같은 payload를 만들려면 두 플랫폼 모두 A12의 입력 상태·판정을 소비해야 한다.
저장한 사본을 정리하는 동안 새로 입력한 내용도 영향을 받았다
완료 기록 화면은 모바일 활성 편집 의도가 idle인 상태로 열려 S08의 보관 의도와 직접 연결되지 않았다. 저장 시작 뒤 입력 변경·기기 큐 보관·서버 응답을 순서대로 재현해, 이전 작업 정리가 최신 입력과 같은 생애주기를 쓰지 않도록 분리했다.
창 축소가 본문 재배치를 대신했다
960×600 창에서도 루트는 1339.09×744.09px(zoom 0.7)이었다. 1440×480에서도 같은 하한에 걸렸다. 실제 200% 브라우저 확대에서는 가용 CSS 폭이 절반이 되는데 전역 배율 하한이 콘텐츠의 최소 폭을 유지했다.
3. 해결 방안
원칙
D1(2026-09-10): “분석 끝나면 바로 진행해”. 기존 승인 범위에서 구현·검증·release 통합을 수행한다. 서버 통계와 사용자 원본 정책, 모바일 스택·native 구현은 바꾸지 않는다.
| 접근 | 판단 |
|---|---|
| A12/A13 모델과 typed command/view model을 플랫폼별로 연결 | 채택. 업무 의미 한 벌, 표현은 각 화면에 유지 |
| 지연 저장을 타이머/추가 재시도로 맞추기 | 기각. 입력 변경과 durable/receipt 순서를 해결하지 못함 |
| 제출 시점 operation과 이후 원문 초안을 구분하고 receipt identity 채택 | 채택. 이전 작업 정리가 새 입력을 지우지 않음 |
| zoom 하한을 더 낮추거나 전역 overflow를 숨김 | 기각. 글자·focus 접근과 마지막 행 문제를 감춤 |
| Grid/container·document scroll, 표/선택 목록만 내부 scroll | 채택. 가용 공간과 실제 콘텐츠 요구를 분리 |
4. 적용한 내용
Phase 1 — 기준선
실제 desktopRoot/HostFrame/Shell/편집기를 렌더하는 fixture를 만들었다. API·컨트롤러 조합은 fixture로 대체하여 입력·화면 조건을 고정했다. 1913×1063, 1366×768, 960×600, 2560×1440, 1440×480을 측정했다.
Phase 2 — 공통 편집기
DesktopWorkoutEditorAdapter/useDesktopEditorModel로 계획과 완료 기록 상태를 분리했다. 카드의 typed event를 A12 command로 변환하고 view model을 표현한다. 이전 backfill extra 조립·중복 DTO·카드 내 시간/휴식 원문 사본을 제거했다. m:ss로 저장된 과거 휴식 문자열만 좁은 호환 입력으로 남긴다.
Phase 3 — 초안·저장·복구
실제 completed editor request에서 S08 snapshot을 발행한다. 저장 도중 새 원문은 별도 operation으로 보존하고 durable와 receipt를 차례로 채택한다. owner 변경 후 늦은 응답은 새 owner 화면을 변경하지 못한다. 원래 요청의 큐 정리도 다른 operation의 예약 autosave를 취소하지 않는다.
S09는 useRecoveryPanel에서 port·binding을 구성하고 typed props만 화면에 전달한다. 사용자 확인 없는 자동 복원은 없으며 현재값 미조회·권한·오프라인·stale·queued·committed가 구분된다. queued 결과에 서버 확정 배지를 붙이지 않고 기존 receipt의 statsRequestedVersion만 전달한다.
Phase 4 — 창 크기 대응
루트 배율 함수와 resize listener를 제거했다. desktop.css는 sidebar와 minmax 본문, 본문 dk-main/compose dk-editor container로 구성한다. 홈·일지·리포트는 document 스크롤을 사용하고 홈 추가 조회는 실제 viewport를 관찰한다. PR 표의 76rem 최소 열 구조는 키보드로 접근 가능한 region 안에서만 가로 스크롤한다.
compose와 폐기 확인은 UiDialog에 연결해 top layer·focus·Escape를 관리한다. 30개 세트와 짧은 창에서 마지막 입력·저장/취소에 접근한다. 모바일 pointer·safe area·부팅 splash 정책은 보존했다. U07 selector/overflow 인계는 시각 계약에 기록했다.
Phase 5 — 실제 비교와 기록
동일한 초기 DTO·카탈로그·입력 시나리오를 tests/fixtures/u03EditorParity.mjs로 제공한다. U02의 실제 createMobileWorkoutEditor와 U03의 실제 desktop adapter를 비교했다. 빈칸/typing/invalid/IME/0/kg/lb/% 7개 시나리오에서 판정과 canonical exercises·date·note·expectedRevision이 같다. 실패 세트·기존 자식 ID·휴식·메모·통계 투영 무효화도 포함한다. 비교 당시 U02 worktree의 Phase 3 9aaf8646 기반 adapter를 직접 읽은 임시 스크립트 결과이며, 모바일 전체 화면 여정을 통과했다는 뜻은 아니다.
작업 중 드러난 것
- raw 분/초·RPE를 canonical 숫자만으로 초안에 남기면 입력 중 원문을 복원할 수 없었다. 공통 draft 직렬화에 원문 문자열을 추가하고 복원 때 공통 parser로 재검증했다. U02와 커밋 단위로 공유했다.
- 고정 canvas를 단언하던 소스 검사는 새 root 행동으로 재조준했다. audit manifest 대상은 변경 이유를 신고했다. 기존 bundle 격리 fixture 일시 실패는 단독 2/2 및 이후 전체 check에서 통과했고 무관한 구조 수리는 하지 않았다.
- 설치된 full Chromium 한 경로에서 spawn UNKNOWN이 발생했다. 원인은 미확인이다. 공유 설치를 수정하지 않고 작업용 경로에 동일 Chromium을 설치해 실제 zoom을 검증했다. 테스트 우회는 없다.
- 브라우저 screenshot은 확대 시 캡처 경계의 제한이 있어, 확대 성공의 판정은 이미지 크기가 아니라 아래 API·DPR·CSS viewport·DOM geometry·실제 입력/키보드 동작을 함께 사용한다.
5. 적용 결과
| 항목 | 전 → 후 / 증거 |
|---|---|
| 960×600 루트 | 1339.09×744.09px, zoom 0.7 → width 960px, zoom 1 |
| 1440×480 루트 | width 1339.09px, zoom 0.7 → width 1440px, zoom 1 |
| 5회 resize 루트 inline style mutation | 13 → 0 |
| 같은 resize 구간 LayoutCount | 15 → 10, RecalcStyleCount 14 → 15 |
| resize LayoutDuration / RecalcStyleDuration | 11.758ms / 7.235ms → 4.748ms / 6.492ms. 공유 PC 단회 관측으로 성능 보장 아님 |
| 초기 LayoutCount / RecalcStyleCount | 3 / 5 → 4 / 10; 초기 시간 13.685ms / 2.719ms → 34.359ms / 6.292ms. 단회 cold fixture 차이를 최적화 성과로 해석하지 않음 |
| 실제 browser 확대 | Chrome 151.0.7922.34 tabs.setZoom(2)/getZoom=2, DPR 1→2, CSS 2538×1342→1269×671, visualViewport.scale=1. root width 1269px/zoom 1, 문서 가로 overflow 없음 |
| 입력 원문 | 2분+3.초 JSON 왕복 후 원문 유지, 3.5로 완료하면 123.5초. 변조 RPE 99는 invalid |
| Phase 5 전체 검사 | 3,721건 중 3,671 통과·50 제외·0 실패 |
| 브라우저 | 최종 29/29 통과(54.1s). 휴식 raw 입력 판정·실제 메뉴 키보드 이동 추가 |
| 실제 adapter parity | 7/7, kg 0 / lb 100→45.36kg / 75% of 200→150kg, payload·판정·자식 identity 동일 |
| 최종 최신 release Precheck | base daca15d9 포함 head 0cbc1054에서 static/unused/build/artifact 통과, 3,534 pass·0 fail·60 skip, 2분 13초. #1478 제외 정책으로 이전 head와 수가 다름 |
| Merge Check | run 34449559425, checked merge c1af78e8090cc7b6464770ee2331a6b4a819b902를 release에 그대로 반영. full CI 실행 아님 |
| 문서 | check 324 routes·build·artifact 31 files 통과. 앱 코드와 독립 게시 |
| Production/DB | 공개 번들 read-only 관측. 신규 UI Production 배포·인증 여정 미실행. DB/migration 변경 0 |
실제 확대 방법은 Chrome tabs.setZoom과 Playwright extension을 사용한다. 창 폭만 줄인 테스트로 확대를 대체하지 않는다. fixture의 controller/save 응답은 결정적 이벤트이며 서버·네트워크 전체 E2E를 뜻하지 않는다.
6. 이번 개선으로 향상된 것
입력과 저장의 의미가 플랫폼에서 분리된다
사용자가 입력 중인 값과 제출한 값이 구분된다. 같은 단위·실패 세트·자식 identity는 공통 모델을 거쳐 저장되고, 입력 중 원문은 초안에 남는다.
작은 창에서도 작업 공간을 다시 배치한다
글자 전체를 축소하는 루트 계산 대신 본문 폭으로 열을 재배치한다. 표·차트의 필요한 스크롤만 남기고 긴 편집은 대화상자와 키보드로 접근한다.
남은 것
이번 요청의 release 통합과 staging·Production 배포는 별개다. U06/N01의 실제 WebView·키보드·safe area 조합, Firefox/WebKit·스크린리더 음성, U07의 전체 CSS 정리·U04 가상화는 이 결과로 완료 처리하지 않는다. 통계 부재 값을 합성하거나 모바일 스택을 활성화하지 않았다.