Skip to content

하단 크롬 여백 계약 (bottom chrome clearance)

2026-09-04 이슈 #1250(하단 탭바가 내용을 가리는 문제 원천 차단, 오너 결정 D1=b·D2=a·D3=a) 산출물. 이슈 #855 §2-1(크롬 상태 채널)이 .screen에만 적용하던 "여백은 채널에서 유도한다"를 하단에서 끝나는 모든 스크롤 컨테이너로 확장하고, 탭바 표시/숨김을 CSS 감지(:has)에서 완전히 떼어 채널 하나로 모은다. 구조 변경은 이 계약 개정이 선행한다.

0. 한 줄 원칙

하단 크롬(탭바·시작/복귀 바·친구 칩 바·PR 복귀 바·뒤로 바·운동 도크·세트 편집 행)의 존재와 높이는 채널 한 곳이 게시하고, 스크롤 컨테이너의 바닥 여백은 그 채널에서만 유도한다. 화면별 손 여백은 금지.

iOS 네이티브가 탭바 높이를 스크롤 뷰의 아래 여백(contentInset)에 자동 반영하는 것과 같은 모델이다. 유저가 어느 화면에서든 끝까지 내리면 마지막 요소가 하단 크롬 위에 온전히 보여야 한다.

1. 용어

용어
하단 크롬화면 바닥에 떠 있어 내용을 가릴 수 있는 것. 탭바, 탭바 위 시작/복귀 바, 친구 칩 바(fsb), PR 복귀 바, 세션 상세 뒤로 바, 운동 도크, 세트 편집 행. (완료 단계의 종료 바는 in-flow 버튼이라 크롬이 아니다)
채널.app[data-bottom-chrome="<값>"] 속성. 지금 보이는 하단 크롬이 무엇인지 열거값 하나로 게시
명목 높이채널 값 → --bottom-chrome-h 사상표(nav.css). safe-bottom 제외
숨(hush)크롬 윗변과 내용 마지막 요소 사이의 여유. --chrome-hush, 기본 14px. 화면 취향이라 CSS가 소유
유도식--bottom-clearance: calc(var(--bottom-chrome-h, var(--tabbar-h)) + var(--chrome-hush, 14px) + var(--safe-bottom)) — core.css 한 곳에서 .screen·.pg-overlay 자신에게 선언(커스텀 속성은 선언한 요소에서 계산되므로 .app에 두면 숨이 굳는다)
신고(claim)하단 크롬을 만들거나 덮는 컴포넌트가 "나 여기 있다"를 React 훅으로 알리는 것. 채널 값은 신고들에서 계산된다

2. 현황 (전 — 2026-09-04, 코드 대조)

"탭바가 보이는가"와 "바닥 여백"이 세 곳(채널·:has 감지·클래스별 z-index / 손 여백)에서 따로 결정되던 상태. ⚠ = 어긋남 또는 잠재 결함.

스크롤 컨테이너탭바바닥 여백(전)결정 위치비고
.screen 탭 화면 5종·기록 도구 레이어채널 tabbar / tabbar-start / tabbar-resume유도식(50 또는 104 + 숨 + safe)core.css 52#855 Phase 6 정본
.screen 친구 판(홈·피드·리포트·일지 달력)채널 fsb(숨김)76 + safe, 화면별 4규칙home.css 144 · feed.css 30 · report.css 13 · session.css 10fsb에 사상표 값이 없어 폴백 뒤 화면 규칙이 덮음
.screen PR 복귀 바:has(.pr-return-bar) 숨김71 + safesession.css 88·89바 59 + 숨 12. 채널은 tabbar라 실제와 어긋남 ⚠
.screen 운동 플로우 기본(시작 단계 등)채널 dock(숨김)16 (safe 없음)core.css 110
.screen 운동 종료 바 있는 단계74 + safecore.css 111바 50 + 인셋 12 + 숨 12
.screen 완료 단계(종료 바 흐름 안)16 + safeworkout.css 742
.screen 세트 진행 / 휴식 도크--dock-h + 56 + safeworkout.css 956 · 716도크 채널 유도 ✓, 형식만 다름
.screen 종목 완료 검토 도크--dock-h + 66 + safe (+ 통계 패널)workout.css 903 · 904
.pg-overlay 기본(홈 리포트 오버레이)보임(z 30 < 40)--tabbar-h + 14 + safeprimitives.css 564홈은 시작 바(104)인데 50만 확보 ⚠
.gp-full.pg-overlay 그룹 보드·커피라운지보임--tabbar-h + 14 + safegroup.css 22그룹 시작 바(104) 미반영 ⚠
.gp-sess.pg-overlay 그룹 세션 상세:has(.pr-return-bar) 숨김0 (복귀 바 sticky in-flow)group.css 153
.sj-mo.pg-overlay 일지 하루·월 상세보임14 + safesession.css 58이번 결함: 탭바 50 미반영 → 마지막 버튼 가림 ⚠
.sj-mo.pg-overlay 세션 상세 풀페이지(일지·피드):has(session.detail) 숨김14 + safesession.css 58 · nav.css 123
.rc-det.pg-overlay 종목 상세:has(pr.detail) 숨김14 + saferecords.css 58 · nav.css 124
.lsov.pg-overlay PR 기록 목록z 46으로 덮음(가정)14 + safeprimitives.css 567덮임이 z-index에 의존. iOS WKWebView에서 .screen이 합성 레이어가 되면 탭바가 위로 올라올 수 있음(미검증) ⚠
.sheet-body 드로어 전체시트 z 60이 덮음16 / 닫기 바 64 + 12 / 14 + safe 등overlays.css 398 · 509, feed.css 95 · 101, workout.css 581, home.css 90시트는 크롬을 덮는 쪽. 안쪽 여백은 시트 자기 몫(닫기 바·safe)
.fd-apbody 친구 추가 모달 안 목록모달0feed.css 37모달 카드가 인셋 소유
.lgd-scroll 법적 문서앱 밖 전면32 + safelegal-doc.css 13탭바 없음
댓글 시트 .fd-cmsheet:has 숨김(#775)시트 내부nav.css 128채널은 tabbar라 어긋남 ⚠

탭바 숨김 결정 주체(전): 채널 값 3개(dock·none·fsb) + :has 감지 4건(세션 상세 풀페이지·종목 상세·댓글 시트·PR 복귀 바). 손 여백(전): 위 표의 .screen 화면 규칙 9 + 오버레이 7 + 폴백 1 = 17곳 (시트·모달·법적 문서 제외).

3. 채널 값 사상표 (후)

값을 추가할 때는 이 표와 nav.css 사상표를 함께 고친다. 신고자 = 그 크롬을 그리는 컴포넌트.

신고자--bottom-chrome-h탭바우선순위
tabbar탭 리전(기본)--tabbar-h(50)보임10
tabbar-start / tabbar-resume탭 리전--tabbar-h + 54보임 + 바10
fsb친구 페이지 스택 셸(UiStackScreen chrome="fsb", 이슈 #1393 Phase 4 — 종전 탭 리전 게시)66 (칩 바 54 + 인셋 12)숨김20
return-barPR 복귀 바(SessionSheetBody)59숨김30
back-bar세션 상세 풀페이지 뒤로 바(UiSessionDetail, 복귀 문맥 아님)64숨김30
dock운동 도크(세트 진행·휴식·검토)--dock-h숨김30
edit-row세트 편집 저장/취소 행(WorkoutRecord, 편집 중)76 (패딩 12 + 버튼 48 + 인셋 고정분 16)숨김30
none하단 크롬을 전부 덮는 것: 전체 화면 스택 셸(세션 상세·종목 상세·PR 기록 목록·일 요약 — UiStackScreen chrome="none", 이슈 #1393), 댓글 시트, 운동 플로우 기본(도크 없는 단계·완료 단계 — 완료 단계의 종료 바는 in-flow라 크롬이 아니다)0숨김40
  • 여러 신고가 동시에 있으면 우선순위가 높은 값이 게시된다(덮는 것 > 국소 바 > 친구 칩 바 > 탭바). 같은 우선순위면 나중 신고.
  • 탭바 표시/숨김은 이 채널만 결정한다. nav.css에서 .tabbar를 숨기는 선택자는 .app[data-bottom-chrome=…] 형태만 허용. 화면 표식 감지(:has)로 탭바를 숨기는 규칙은 금지 — 덮임을 z-index(페인트 순서)에 기대지 않고 탭바를 실제로 없앤다(#775와 같은 원칙).
  • 게시 방식은 #855 그대로: .app 속성을 레이아웃 이펙트로 직접 쓴다. 신고 저장소는 컨텍스트의 안정 객체(ref)이고 값 변화는 루트 재렌더 없이 속성만 바꾼다(렌더 입자성 규칙 유지).

4. 유도식 소비자 (후)

하단에서 끝나는 스크롤 컨테이너는 바닥 여백을 var(--bottom-clearance)로만 쓴다(.screen·.pg-overlay는 core.css/primitives.css의 공용 규칙이 이미 쓰고 있으므로 새 화면은 아무것도 적지 않는다). 오버레이는 --chrome-hush: 14px로 시작해 뒤 지면의 숨을 상속하지 않는다.

컨테이너여백숨(--chrome-hush)
.screen (탭 화면·레이어·친구 판·운동 플로우 전부)--bottom-clearance기본 14 · 기록 도구 12 · 풀블리드(rc-full·rp-full·sj-fullcal) 0 · 친구 판 10 · PR 복귀 바 12 · 뒤로 바 8 · 운동 플로우(도크 없는 단계·완료 단계) 16 · 세트 진행/휴식/완료 대기 56 · 검토 66(+통계 패널) · 세트 편집 164(구 240 유지)
.pg-overlay 전부(sj-mo·gp-full·rc-det·lsov·홈 리포트)--bottom-clearance기본 14
.gp-sess.pg-overlay 그룹 세션 상세유예 padding-bottom: 0복귀 바가 in-flow sticky로 내용 끝에 붙어 있어 여백을 두면 바 아래 빈 띠가 생긴다. 탭바 숨김은 채널 return-bar 신고
.fscope.screen.screen 규칙 그대로10

숨 값은 화면 CSS가 --chrome-hush로만 표현한다. padding-bottom을 직접 쓰지 않는다.

채널 밖(유예, 정적 검사 허용 목록): 시트 .sheet-body 계열(시트가 크롬을 덮으므로 안쪽 인셋은 닫기 바·safe 자기 몫), 모달 안 목록(.fd-apbody), 법적 문서(.lgd-scroll, 앱 밖), 탭바 없는 온보딩 셸(.screen:has(.ob-flow) 0 — 채널 미게시라 폴백 탭바 몫을 상쇄), in-flow 바가 safe를 소유하는 화면(운동 시작 단계 .screen:has(.wf2-ctas) 0, 그룹 세션 상세), 그리고 스크롤 컨테이너가 아닌 하단 바 자체(탭바 패딩·온보딩/로그인 도크·댓글 입력 행·운동 CTA 행·운동 도크·편집 행·픽커 발·PR 복귀 바). 유예는 tests/react/bottomChromeClearanceContract.test.mjs의 목록에 파일:선택자와 이유를 적는다.

5. 게이트

게이트무엇을 보나어디서
계약 테스트 bottomChromeClearanceContract모바일 스타일에서 padding-bottom/padding 축약에 --tabbar-h·--safe-bottom·--bottom-chrome-h·고정 px가 든 선언은 유도식 정의 1곳 + 유예 목록 외 금지. .tabbar를 숨기는 선택자는 채널 속성만. 사상표의 값 집합 = 이 문서 §3 = 신고 훅의 타입전 PR(npm run check)
e2e 채널 등가(#855)채널 값 == 실제 보이는 크롬, --bottom-chrome-h 해석값full-ci(viewport 잡)
e2e 도달 게이트(#1250 Phase 4)16표면 × safe 0/34: 끝까지 스크롤 → 마지막 누를 수 있는 요소 아랫변 ≤ 보이는 크롬 윗변, 그 점의 elementFromPoint가 그 요소full-ci(viewport 잡)

도달 게이트 표면(D2=a 전수): 홈 · 홈 리포트 오버레이 · 일지 달력 · 일지 하루 상세 · 일지 월 상세 · 세션 상세 풀페이지 · 피드 · 피드 세션 상세 · 그룹 목록 · 그룹 보드 · 커피라운지 · 리포트 · 기록 도구(스택 화면 .pg-overlay.prt-stack, 이슈 #1393 Phase 5) · 종목 상세 · PR 기록 목록 · 친구 일지(fsb). 새 표면(오버레이·스택 화면)을 만들면 이 목록에 추가한다.

6. 새 화면·오버레이를 만들 때

  1. 탭바를 덮거나 자기 하단 바를 가지면 useBottomChromeClaim("<값>")을 호출한다(값은 §3에서 고른다. 없으면 이 문서·nav.css 사상표·훅 타입에 함께 추가).
  2. 스크롤 컨테이너의 바닥 여백은 padding-bottom: var(--bottom-clearance) 한 줄. 여유를 바꾸고 싶으면 --chrome-hush만 바꾼다.
  3. §5 도달 게이트 표면 목록에 추가한다.

이 세 줄을 빠뜨리면 계약 테스트(1·2)와 도달 게이트(3)가 PR에서 잡는다.

7. Phase 2에서 달라진 값

전(2026-09-04 Phase 2 이전)과 달라진 값: 운동 플로우의 도크 없는 단계·완료 단계는 16 → 16 + safe-bottom(홈 인디케이터 위로), 세션 상세 뒤로 바 화면은 72 → 72 + safe-bottom, 세트 편집 중 240 → 240 + safe-bottom. 나머지는 값 동일(손 여백을 숨으로 옮긴 것). 이번 결함(하루·월 상세)은 14 + safe → 탭바 50 + 14 + safe.

2026-09-10 운동 편집 중 보존된 탭의 신고 (#1554)

같은 .app 안에서 탭과 운동 화면은 각각 BottomChromeProvider를 가지며 현재 모드의 제공자만 DOM 채널을 게시한다. 탭을 편집 중 숨겨도 신고를 잃지 않고, 비활성 탭의 비동기 갱신은 운동의 none·도크·편집 행을 덮지 않는다. 운동을 닫으면 보존된 탭·상세의 신고가 다시 게시된다. 화면별 여백 계산과 우선순위는 유지한다.

그룹 작성기처럼 같은 제공자 안에서 아래 화면만 숨길 때는 BottomChromeScope active=false로 해당 화면의 신고를 잠시 해제한다. 화면의 DOM·입력 상태는 유지하며 다시 활성화하면 현재 신고를 부모 채널에 복원한다. 별도 .app이나 새로운 우선순위 규칙은 만들지 않는다.