Skip to content

화면 표현 전량 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.md Phase별 재싱크 안내 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.tsxui/shared/gates/(onReload는 main.tsx 주입으로 전환), vite/app-host.cssui/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.jsonvite-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로 복구할 것(관련 메모리 갱신 대상).