화면 표현 전량 ui 이사 — 디자인 불가시 5계열에서 셸 골격 ui 소유까지 (2026-08-25)
- 기간: 2026-08-25 (세션 1개, 오너 지시 "실제로 앱에서 보이는 모든 부분은 클로드 디자인이 보게 하고싶으면" → D1 "골격 바꿔야되니까 a로 해줘")
- 랜딩: PR #754(Phase 1,
92493741) · PR #755(Phase 2,282ed397) · PR #757(Phase 3,5fa64c9e) · PR #759(Phase 4,dd4d1e4a) · Phase 5 docs PR — 마이그레이션·엣지 0, Vercel 자동 배포 - 설계서: 이슈 #753 본문("예상 효과·개선사항" 절 포함)
- 정본:
docs/contracts/shell-frames.md(프레임 인벤토리·keep-alive 계약·ui 불가 표면 3종) ·ui/mobile/shell/MobileShellFrames.tsx·ui/desktop/shell/DesktopShellFrames.tsx·ui/shared/gates/HostFrames.tsx - 도구:
e2e/profile/tabSwitch.profile.mjs(기존 계측 하네스 — 전→후 회귀 판정에 사용, 로컬 스택 필요) - 게이트: 기존 소스 앵커 테스트 8파일이 새 소재(ui 프레임·콜사이트)를 단언하도록 갱신 —
backgroundTokens·viewportTiers·barbelicWebMetadata·appErrorBoundary·adminStandaloneShell·controllerBoundaries·homeBootLoadingOrder·mobileCssBoundaries - 버그리포트: 없음
- 계약:
docs/contracts/shell-frames.md신설,ui/CHANGE-NOTES.mdPhase별 재싱크 안내 4항목
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 부팅·에러 표면 이사 (HostLoadingScreen·AppErrorScreen·app-host.css) | ✅ PR #754 (92493741) |
| Phase 2 | 유저 열람 오버레이 ui화 (인라인 311줄 → .lg-imp-*) | ✅ PR #755 (282ed397) |
| Phase 3 | 관리자 독립 셸 chrome (MessageGate·PlatformGate·AdminSidebar·admin-host.css 삭제) | ✅ PR #757 (5fa64c9e) |
| Phase 4 | 앱 셸 골격 프레임 추출 (D1(a) 본체, 계측 전→후 동일) | ✅ PR #759 (dd4d1e4a) |
| Phase 5 | 계약문서 + 작업 기록 (docs-only) | ✅ 이 문서의 PR |
1. 배경
클로드 디자인의 열람·전달 스코프는 src/react/ui/** + docs/contracts/**다(ui/README.md). 오너가 "지금 화면에 보이는 내용이 ui에 다 구현되어 있나"를 물어 전수 조사한 결과, 화면 표면 중 5계열이 ui 밖에 있었다: ① 부팅·스플래시·에러 표면(vite/ 소유 + app-host.css), ② 유저 열람 오버레이(인라인 스타일 311줄), ③ 관리자 독립 셸 chrome(AdminStandaloneRoot 마크업 + admin-host.css 이원화), ④ 앱 셸 골격 래퍼(CSS는 ui, DOM은 컨테이너 산재), ⑤ ui가 될 수 없는 정적 표면(index.html 스플래시·네이티브 스플래시·legal). 오너는 셸 리디자인 계획을 전제로 "모든 부분을 디자인이 보게" 하기로 했다.
2. 문제 제기
부팅·에러·오버레이 표면이 디자인 불가시였다
HostLoadingScreen·AppErrorBoundary 폴백·app-host.css(호스트/폰 목업/캔버스 시각 규칙 전부)·ImpersonationOverlay(인라인 311줄)가 ui 밖 — 디자인이 고칠 수도, 볼 수도 없었다.
관리자 셸 스타일이 두 곳으로 갈라져 있었다
.adm-shell-*은 vite/admin/admin-host.css, 나머지 chrome 클래스는 ui(shell.css·desktop.css) — 같은 사이드바의 정의가 소유 경계를 넘나들었다.
셸 골격 DOM은 ui CSS를 입고도 컨테이너가 소유했다
.app/.screen/.tab-keep/.tab-pane/gym-app-host 등의 클래스 정의는 전부 ui에 있는데 DOM 중첩·토글은 mobileApp·desktopApp·루트 3곳에 산재 — 디자인이 생김새는 만지되 골격은 못 바꾸는 상태였다.
3. 해결 방안
원칙 (오너 결정, 2026-08-25)
- D1 = (a): "골격 바꿔야되니까 a로 해줘" — 셸 골격을 ui 프레임 컴포넌트로 추출해 디자인이 소유한다. (b) 계약문서화(코드 무이동)는 기각 — 셸 리디자인 예정이 근거.
- D2 기본값 = 예외 문서화: index.html 정적 스플래시는 JS 로드 전 페인트라 ui가 될 수 없어 계약문서로 가시화(오너 별도 지시 시 빌드 주입 승격은 별도 트랙).
접근
2026-08-04 "셸 UI 4종 ui/ 이동"(ShellGates·DesktopSidebar) 선례의 패턴을 그대로 확장: 마크업·스타일은 ui로, 상태·BarbelicApi·구독은 컨테이너 잔존. 대안이었던 "디자인 열람 스코프를 ui 밖 파일로 확장"은 소유 경계(표현/의미론 2층)와 전달 계약을 흐려 기각. 전 Phase 시각 변화 0 전제, Phase 4는 렌더 입자성 회귀를 계측으로 게이트.
4. 적용한 내용
Phase 1 — 부팅·에러 표면 (#754)
vite/HostLoadingScreen.tsx→ui/shared/gates/(onReload는 main.tsx 주입으로 전환), vite/app-host.css→ui/shared/styles/(호스트 스코프라 @layer 밖 유지, 이동만), ui/shared/gates/AppErrorScreen.tsx 신설(폴백 마크업 — 캡처·진단·클립보드는 AppErrorBoundary 잔존). 앵커 4테스트 경로 갱신, "새로고침 복구 보장" 앵커는 콜사이트 단언으로.
Phase 2 — 유저 열람 오버레이 (#755)
표현부 → ui/shared/gates/ImpersonationViews.tsx(배너·런처·검색 모달), 인라인 스타일 전량 → shell.css .lg-imp-*(@layer shell, 값 1:1). 컨테이너는 상태·API·권한 판정만.
Phase 3 — 관리자 독립 셸 chrome (#757)
MessageGate·PlatformGate→ShellGates, 사이드바→ui/desktop/shell/AdminSidebar.tsx (탭 목록·이메일 props, 브랜드 마크·아이콘 해석은 ui). admin-host.css 삭제 — 유일한 내용(.adm-shell-* 20줄)을 desktop-admin.css 말미로 이동(@layer shell 원본 유지).
Phase 4 — 앱 셸 골격 프레임 (#759, D1(a) 본체)
프레임 신설 3파일(1절 정본 참조). mobileApp의 MobileTabsRegion은 구독(useMobileNavTab)· visitedTabs·복귀/시작 바 게이트 계산만 남기고 MobileTabFrame 호출, 운동 플로우는 MobileWorkoutFrame(workoutView 마커 유지·nav 슬롯), desktopApp은 테마 루트·.dk-app· 메인 폴백을 프레임으로(계획 선택 클릭 위임은 props 주입), 루트 3곳은 Host 프레임으로, app.tsx는 PlatformGate 재사용. 앵커 5테스트 갱신.
Phase 5 — 계약문서 (이 PR)
docs/contracts/shell-frames.md: 프레임 인벤토리·keep-alive 계약·ui 불가 표면 3종 (정적 스플래시는 ui AuthLoadingGate와 쌍이라는 동조 규칙 포함).
주요 결정과 그 근거
- keep-alive 토글은 ui로 옮겨도 의미론:
.tab-pane off토글이 keep-alive 메커니즘 그 자체라, 프레임 상단 주석 + 계약문에 "판 언마운트 구조 변경은 Codex 경유"를 남겼다 — D1(a)로도 이 부분의 소유권은 완전히 넘어가지 않는다는 것을 명시. - app-host.css는 @layer 밖 유지: 레이어로 감싸면 캐스케이드 지위가 내려가 시각 불변이 깨진다. 파일 이동만 하고 지위는 그대로.
- 검증은 같은 하네스·같은 머신 전→후: 문서 수치(08-24) 재인용 대신 트랙 이전 main(2349edad)을 임시 워크트리 dev 서버로 띄워 베이스라인을 직접 측정했다.
작업 중 드러난 것
- 로컬 설치 결손이 lint를 넘어
.bin전체 부재(tsc·vite CLI 포함) — tsc는node node_modules/typescript/lib/tsc.js로, dev 서버는 launch.json에 node 직접 실행 항목으로 우회.npm install로 복구 가능하나 다른 세션의 dev 서버가 같은 node_modules를 쓰는 중이라 이번엔 건드리지 않았다. - 브라우저 프리뷰의 launch.json은 primary checkout의 것만 읽는다 — 워크트리 서버는 primary
.claude/launch.json에vite-wt###항목(cmd /c cd + node vite.js)을 추가하는 기존 세션들 패턴을 따라야 한다. - Vite dev의 동적 import는 HMR 갱신 후에도 구버전 모듈을 반환할 수 있다 — 단독 마운트 스모크는 페이지 리로드 +
?t=캐시 버스팅 후에 판정. - 소스 앵커 테스트의 줄바꿈이 파일마다 LF/CRLF 혼재 — 문자열 치환 스크립트는 파일별 줄바꿈을 감지해서 맞춰야 한다(heredoc 이스케이프 손상 함정 포함, 스크립트는 Write로).
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 디자인 불가시 화면 표면 | 5계열 → 0 (코드 이사 4계열 + 계약문서 1계열) |
| 앱 화면 CSS의 ui 밖 파일 | 2(app-host·admin-host) → 0 |
| 인라인 스타일 표면 | ImpersonationOverlay 311줄 → .lg-imp-* 클래스 20종 |
| 셸 골격 DOM 소유 | 컨테이너 6파일 산재 → ui 프레임 12종(계약문 인벤토리) |
| 렌더 입자성(계측, 전→후) | 재방문 탭 전환 조립 루트 커밋 0 → 0 · 복귀 4.22 → 3.93ms · 첫 방문 진입 커밋 2·4·8 동일 — 회귀 0 |
| 시각 | 변화 0 (단독 마운트 computed style 원본 일치·프리뷰 스모크) — 오너 실기기 확인은 안 거침(시각 불변 전제라 요청 안 함) |
| 테스트 | 1,953/1,953 통과 · 소스 앵커 8파일 새 소재 단언 · CI verify 전 PR 통과 |
6. 이번 개선으로 향상된 것
클로드 디자인이 앱의 모든 표면을 보고, 셸 골격을 바꿀 수 있다
부팅 스플래시부터 에러 카드·관리자 chrome·탭 골격까지 재싱크 한 번에 들어온다. 셸 리디자인(오너 예정)을 디자인이 Codex 왕복 없이 착수할 수 있다 — keep-alive 토글 구조만 계약으로 잠갔다.
구조적으로 남는 것
docs/contracts/shell-frames.md— 프레임 인벤토리·keep-alive 계약·ui 불가 표면 3종의 정본- "표현은 ui, 배선은 컨테이너" 경계가 부팅·오버레이·셸 골격까지 확장 적용된 선례
- 소스 앵커 테스트가 골격의 새 소재를 계속 감시(프레임 마커·마운트 경계·게이트 식)
남은 것
- D2 승격(index.html 스플래시를 transformIndexHtml 빌드 주입으로 ui 정본화)은 오너가 원할 때 별도 트랙.
src/react/vite/AppRoot.tsx+vite-scaffold.css는 렌더되지 않는 Vite 스캐폴드 잔재 (참조 0) — 이 트랙에서 손대지 않음, 삭제는 별도 판단.- 로컬 npm 설치 결손(
.bin부재)은 다른 세션의 dev 서버가 내려간 틈에npm install로 복구할 것(관련 메모리 갱신 대상).