하단 탭바가 내용을 가리는 문제 원천 차단 — 하루 상세 마지막 버튼이 탭바 밑에 깔리던 것에서, 하단 크롬 채널 하나·여백 유도식 하나·도달 게이트까지 (2026-09-04)
- 기간: 2026-09-04 (세션 3개
74dc3199→959ece10→13a81c15; 오너 보고 원문 "이 사진처럼 페이지의 내용이 하단 탭바에 가려서, 스크롤해도 더 안내려가져서 버튼을 못누르거나 하는 경우가 있는데 이런 일이 발생하지 않도록 원천적으로 해결하는 근본적인 방법이 있을까?") - 랜딩: PR #1262 (Phase 0~4 한 묶음, squash
9bdd835c) — 마이그레이션·엣지 함수 없음, Vercel 앱 배포 - 설계서: 없음(분석·Phase 계획·"예상 효과·개선사항" = 이슈 #1250 첫 댓글 + Phase별 완료 댓글)
- 정본: 계약
docs/contracts/bottom-chrome-clearance.md· 신고 저장소src/react/ui/mobile/shell/bottomChrome.tsx· 채널 값 사상표nav.css· 여백 유도식core.css--bottom-clearance - 도구: 없음(측정은 e2e 게이트가
test-results/bottom-clearance/measurements.json으로 남긴다) - 게이트: 계약 테스트
tests/react/bottomChromeClearanceContract.test.mjs(전 PR, 5건) · e2e 도달 게이트e2e/bottom-clearance.spec.mjs(viewport 레인, 표면 17 × safe 0/34) · 기존 채널 등가 게이트e2e/dock-geometry.spec.mjs유지.npm run ci:local -- --full통과(2026-09-04 06:57Z, mainabf6289f리베이스 뒤: verify 5단계·db reset·pgTAP 104파일/1776·e2e 11/7/6/35/14, 14분 4초 — 마이그레이션 변경 없음) - 버그리포트:
bug-report/bug-077-20260904.md - 계약:
bottom-chrome-clearance.md신설 ·shell-frames.md§2-1 이관 문장
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 현황표(하단 스크롤 컨테이너 19종·손 여백 17곳·어긋남 7건)·계약 문서 신설 | ✅ 95829ba4 |
| Phase 1 | 신고 저장소 useBottomChromeClaim — 탭바 표시/숨김을 채널 하나로, :has() 감지 4건 승격 | ✅ d822432b |
| Phase 2 | 바닥 여백 유도식을 .screen·.pg-overlay 전부로, 손 여백 17곳 → --chrome-hush, 하루·월 상세 결함 수리 | ✅ 31283cea |
| Phase 3 | 정적 검사(손 여백·:has 탭바 숨김 금지·채널 값 3곳 동일·신고 지점 잠금) + 세트 편집 행 edit-row 편입 | ✅ e8790532 |
| Phase 4 | e2e 도달 게이트 — 표면 17종 × safe 0/34, 끝까지 스크롤 후 마지막 버튼이 크롬 위에 있고 눌리는지 | ✅ 01c1da96 |
| Phase 5 | ci:local → PR 1개 → CI 1회 → 머지 → 버그 리포트·작업 기록 | ✅ PR #1262 |
1. 배경
바벨릭 모바일은 하단 탭바를 내용 위에 띄우고(반투명, 내용이 밑으로 비쳐 지나감) 스크롤 영역의 바닥 여백으로 탭바 몫을 비워 둔다. 이슈 #855(2026-08-27)가 이 여백을 탭 화면(.screen)에 대해서는 "채널 → 유도식" 하나로 통일했지만, 그 위에 뜨는 전체 화면 오버레이(하루·월·세션 상세, 종목 상세, 그룹 보드, PR 기록 목록)와 화면 로컬 하단 바(PR 복귀 바, 뒤로 바, 댓글 시트)는 "각자 정본"으로 남겨 두었다. 그 뒤 8/27 하루·월·세션 상세 오버레이 신설과 9/1 디자인 배송 v84(세션 상세 재설계)가 모두 이 구멍을 통해 손 여백을 늘렸다.
2026-09-04 오너 보고: 일지 탭에서 8/12 하루 상세를 열고 끝까지 내려도 마지막 "완료한 운동 기록 추가하기" 버튼의 윗부분만 탭바 위로 보이고 더 내려가지 않아 누를 수 없다. 오너는 이 한 건의 수리가 아니라 "이런 일이 원천적으로 발생하지 않는 근본적인 방법"을 물었다.
2. 문제 제기
하루·월 상세 오버레이의 바닥 여백에 탭바 몫이 없었다
- 하루 상세와 세션 상세는 같은 겉옷(
.sj-mo오버레이)을 쓴다. v84(PR #1090)에서 세션 상세가 탭바를 숨기게 되면서 이 겉옷의 여백에서 탭바 몫을 뺐는데(session.css"탭바 클리어런스 제거"), 하루·월 상세는 탭바를 그대로 보여준다. 여백 14 + safe 뒤에 탭바 50이 덮여 마지막 요소가 깔렸다.
"탭바가 보이는가"와 "바닥 여백"이 세 곳에서 따로 결정됐다
- 탭바 표시/숨김: 채널 값 3개(dock·none·fsb) + 화면 표식을 감지하는 CSS
:has()4건(세션 상세 풀페이지·종목 상세·댓글 시트·PR 복귀 바). 후자는 채널 밖이라 채널은tabbar인데 실제 탭바는 없는 상태가 생겼다. - 오버레이가 탭바를 덮는지: 클래스별 z-index(pg-overlay 30 < 탭바 40 < lsov·sj-mo 46). iOS WKWebView에서 스크롤 컨테이너가 합성 레이어가 되면 순서가 뒤집힐 수 있다(#775에서 실제로 났던 부류).
- 바닥 여백:
.screen만 유도식, 오버레이·친구 판·PR 복귀 바·운동 도크 등 17곳은 손으로 적음. 잠재 결함 2건 추가 확인 — 홈 리포트 오버레이·그룹 보드는 시작 바(104)가 있을 때도 탭바 50만 비워 둠.
3. 해결 방안
원칙 (오너 결정 2026-09-04)
- 방향: B(하단 여백 채널 하나로 통일) + C(도달 게이트). A(탭바가 실제 자리를 차지하는 배치)는 반투명 탭바 디자인 포기·탭바 숨김 시 내용 높이 점프라 미채택.
- D1 = (b) 선행 임시 수리 없이 트랙 완주로 한 번에("그냥 전체적으로 해결").
- D2 = (a) 도달 게이트는 16표면 전수.
- D3 = (a) 운동 도크 상태별 여백 4곳도 같은 틀에 편입("이왕이면 새로운 프레임 안에서 깔끔하게 다 정리").
접근
| 안 | 내용 | 판단 |
|---|---|---|
땜질: sj-mo 규칙에 하루 상세 예외 한 줄 | 증상 자리만 막음 | 기각 — 다음 오버레이가 또 손으로 적는다(§22 자기 검사 "같은 자리를 또 고쳐야 하는가" = 예) |
| A. 탭바 in-flow 배치 | 물리적으로 겹칠 수 없음 | 기각 — 디자인 포기·높이 점프 |
| B. 채널 하나 + 유도식 하나 | iOS 네이티브의 contentInset 자동 반영 모델. 크롬을 그리거나 덮는 컴포넌트가 신고 → 채널 → 탭바 표시와 바닥 여백이 같은 값에서 나옴 | 채택 |
| C. 정적 검사 + e2e 도달 게이트 | 손 여백 재유입을 PR에서, 실제 겹침을 e2e에서 잡음 | 채택 — B의 재발 경로 차단 |
4. 적용한 내용
Phase 0 — 현황·계약 (95829ba4)
docs/contracts/bottom-chrome-clearance.md: 현황표(전), 채널 값 사상표(후), 유도식 소비자·숨 표, 유예 원칙, 게이트 3종, 도달 게이트 표면 목록, 새 화면 3줄 절차.
Phase 1 — 신고 저장소 (d822432b)
shell/bottomChrome.tsx:BottomChromeProvider(셸 프레임.app에 배치, 플로우 모드 기본값none),useBottomChromeBase(탭 리전 기본값),useBottomChromeClaim(kind)(컴포넌트 신고). 우선순위 덮는 것(none) 40 > 국소 바 30 > 친구 칩 바 20 > 탭바 계열 10. 게시는.app속성 직접 쓰기 — #855의 "루트 재렌더 금지" 유지.- 신고 7곳: 댓글 시트·PR 기록 목록·종목 상세·피드/일지 세션 상세 오버레이 =
none, 세션 상세 풀페이지 바 =return-bar/back-bar, 운동 도크 =dock(떠 있는 동안만). nav.css: 탭바 숨김 선택자는 채널 속성만.:has()4규칙 삭제. 사상표에 fsb 66·return-bar 59·back-bar 64 추가.
Phase 2 — 여백 유도식 (31283cea)
core.css한 곳:.app .screen, .app .pg-overlay { --bottom-clearance: calc(var(--bottom-chrome-h, var(--tabbar-h)) + var(--chrome-hush, 14px) + var(--safe-bottom)) }. 변수를 소비자 자신에게 선언하는 이유 = 커스텀 속성은 선언한 요소에서 계산되므로.app에 두면 화면별 숨이.app값으로 굳는다. 오버레이는--chrome-hush: 14px로 시작해 뒤 지면의 숨(기록 도구 12·풀블리드 0)을 상속하지 않는다.- 손 여백 17곳 정리: 하루·월·세션 상세·종목 상세·PR 기록 목록·보드 오버레이 고정 여백 삭제, 친구 판 76px 4곳 → 숨 10, PR 복귀 바 71 → 숨 12, 뒤로 바 72 → 숨 8, 운동 도크 4곳 → 숨 56/66, 플로우 기본 16px → 숨 16, 죽은 종료 바 74 규칙 삭제.
- 완료 단계 종료 바(
.flow-endbar)는 finish에서position: static(in-flow)임을 확인 → 크롬이 아니므로 Phase 1의endbar값 폐기.
Phase 3 — 정적 검사 (e8790532)
tests/react/bottomChromeClearanceContract.test.mjs5건. 검사가 즉시 잔존 1건(세트 편집 중 240px)을 찾아edit-row(76) + 숨 164로 편입.- 유예 목록(이유 포함): 하단 바 자체(탭바·도크·편집 행·CTA 행·댓글 입력 행·PR 복귀 바·픽커 발·온보딩 도크), 시트 안쪽, 탭바 없는 온보딩 셸, in-flow 바가 safe를 소유하는 화면 2곳(운동 시작 단계·그룹 세션 상세).
Phase 4 — e2e 도달 게이트 (01c1da96)
e2e/bottom-clearance.spec.mjs(viewport 레인 testMatch 등재): 표면마다 ① 여백 항등(내용 상자 아랫변 ≤ 보이는 크롬 윗변) ② 넘치는 표면은 끝까지 스크롤 후 마지막 누를 수 있는 요소의 아랫변 ≤ 크롬 윗변 +elementFromPoint가 그 요소 ③ 채널 값 ↔ 탭바 표시 일치. safe-bottom 0/34 두 번.- 표면 17: 홈·홈 리포트 오버레이·PR 기록 목록·일지 달력·하루 상세(마지막 요소가 "완료한 운동 기록 추가하기"임을 단언)·세션 상세(일지)·월 상세·피드·피드 세션 상세·친구 판·친구 일지·그룹 목록·커피라운지·화이트보드·리포트·종목 상세·기록 도구 레이어.
- 픽스처: 같은 날 세션 3 × 종목 4 × 세트 3(하루 상세 강제 넘침), 다른 날 3, 팔로우 친구(세션 2), 그룹 1.
주요 결정과 그 근거
- 신고 방식(컴포넌트가 훅으로 알림)을 택한 이유: 탭바를 숨겨야 하는 상태(세션 상세 오버레이·종목 상세 등)는 화면 안쪽 컴포넌트가 알고, 컨테이너(
mobileApp)는 모른다. 컨테이너로 상태를 끌어올리면 화면마다 배선이 늘고 누락된다. - 우선순위 해소(덮는 것 > 국소 바 > 칩 바 > 탭바): 오버레이 위에 바가 겹치는 경우 탭바를 숨기는 쪽이 항상 이겨야 안전하다.
- 숨(hush)만 화면 CSS에 남긴 이유: 여유는 취향이지 크롬 존재가 아니다. 크롬 존재를 CSS 감지에 맡기면 이번 결함이 재발한다.
작업 중 드러난 것
- Bash 도구의 heredoc이
\\를 하나로 줄여 파이썬 편집 스크립트 안의 정규식이 깨졌다(테스트 파일에 실제 CR/LF가 들어감). 편집 스크립트는 Write 도구로 파일을 만든 뒤 실행할 것. - 서버 렌더(renderToStaticMarkup) 계약 테스트에서
useLayoutEffect경고 → 브라우저에서만 레이아웃 이펙트를 쓰는useIsoLayoutEffect. - 소스 슬라이스 앵커 게이트(
sourceSliceAnchors.test.mjs)가 테스트의indexOf("...")리터럴을 전부 수집한다 — 문서 절 슬라이스는 정규식 match로. .flow-endbar는 완료 단계에서 in-flow — core.css의 74px 규칙은 죽은 코드였다.- 로컬 e2e 첫 실행이 다른 브랜치의 앱을 봤다: playwright 설정이 4173에 이미 떠 있는 미리보기 서버를 재사용(
reuseExistingServer)하는데, 다른 세션이 4173에 다른 브랜치 빌드를 띄워 두고 있었다. 홈 리포트가 안 열려 실패한 첫 실행은 그래서였고, 이전 세션의 "후속 표면 타임아웃"도 같은 원인일 가능성이 크다. 내 빌드를 4174에 띄우고E2E_APP_URL=http://127.0.0.1:4174로 지정해 해결. 4173이 점유되어 있으면 ci:local은 종료 코드 3. - 이전 세션의 도달 게이트 타임아웃 원인 2가지: 월 상세 오버레이의 뒤로 버튼은
.sj-back이 아니라.pg-back(240초 대기), 리포트 랭킹 행pr.open이 숨은 홈 판(마운트 유지)에도 있어 첫 일치가 숨은 요소 →.rp-rank안으로 한정. - 보이는 크롬 검출을 "컨테이너 아랫변에 붙은 것"에서 "바닥 여백 밴드에 걸친 것"으로 넓혀야 했다 — 바닥에서 12px 띄운 친구 칩 바(fsb)·홈 시작 바가 크롬으로 안 잡혀 친구 판에서 크롬 0개로 헛통과하고 있었다.
- 워크트리의
node_modulesjunction이 ci:local 실행 뒤 사라지는 일이 있었다(원인 미확인) —cmd /c mklink /J로 다시 건다. - 버그 리포트 번호: 트랙 진행 중 main에 #1237의 BUG-076이 먼저 실렸다 → 이 트랙은 BUG-077.
5. 적용 결과
| 항목 | 전 → 후 |
|---|---|
| 하루 상세 끝까지 스크롤 시 "완료한 운동 기록 추가하기" 버튼 | 탭바 밑에 깔림(여백 14+safe) → 탭바 위 온전히 보임(여백 50+14+safe), e2e 도달 게이트가 마지막 요소 = 그 버튼·hit-test 통과 단언 |
| 탭바 표시/숨김 결정 주체 | 채널 3값 + CSS 감지 4건 → 채널 9값 하나, CSS 감지 0건 |
| 바닥 여백 손 지정 | 17곳 → 0곳(유도식 1 + 유예 = 바 자체·시트·온보딩·in-flow 바 화면) |
| 홈 리포트 오버레이·그룹 보드 여백(시작 바 104 상태) | 탭바 50만 확보 → 채널 값 그대로(104) |
| 채널과 실제 크롬 불일치(PR 복귀 바·댓글 시트) | 채널 tabbar/실제 없음 → 일치 |
| 정적 검사 | 없음 → 5건(npm run check 통과, main abf6289f 리베이스 뒤) |
| e2e 도달 게이트 | 없음 → 표면 17 × safe 0/34: 17표면 전부 통과(safe 0/34, 34초). 전: 하루 상세 마지막 버튼 아랫변 822 > 탭바 윗변 794(safe 0)·788 > 760(safe 34), 월 상세 여백 14+safe, 채널 불일치 4곳 → 후: 772 ≤ 794·hit-test 통과, 여백 50+14+safe, 불일치 0. ci:local full에서 viewport 묶음 14/14(도달 게이트 포함) |
| 값이 달라진 곳(계약 §7) | 운동 플로우 도크 없는 단계·완료 단계 16 → 16+safe, 세션 상세 뒤로 바 72 → 72+safe, 세트 편집 240 → 240+safe (홈 인디케이터 위로) |
| 미검증 | 시각·감각 품질(여백이 "보기 좋은가")은 기계로 판정하지 않았다. iOS WKWebView 합성 레이어에서 z-index 역전 가설은 검증하지 않았고, 탭바를 display:none으로 없애 그 가설과 무관하게 만들었다 |
6. 이번 개선으로 향상된 것
유저: 어느 화면에서든 끝까지 내리면 마지막 버튼이 눌린다
하루 상세의 기록 추가 버튼이 바로 그 사례이고, 시작 바가 떠 있는 홈에서 리포트 오버레이나 그룹 보드를 열었을 때의 잠재 가림도 같은 식으로 사라졌다.
개발: 새 오버레이는 세 줄이면 끝난다
탭바를 덮거나 자기 바를 가지면 useBottomChromeClaim("값") 한 줄, 여백은 아무것도 적지 않음(공용 규칙), 도달 게이트 표면 목록에 한 줄. 빠뜨리면 정적 검사와 e2e가 PR에서 잡는다.
구조적으로 남는 것
계약 문서 1(bottom-chrome-clearance.md), 신고 저장소 1, 채널 값 사상표 1(nav.css), 여백 유도식 1(core.css), 정적 검사 1, e2e 도달 게이트 1.
남은 것
- 그룹 세션 상세(
.gp-sess)의 여백 0 유예: 복귀 바가 in-flow sticky여서 남겼다. 바를 스크롤 컨테이너 밖 형제로 옮기면 유예를 없앨 수 있다(범위 밖). - 세션 상세 뒤로 바(
.session-back-bar)는 자기 높이에 safe를 더하지 않는다(바 자체가 홈 인디케이터 밑에 깔릴 수 있음) — 이번 트랙은 여백만 다뤘다.