Skip to content

하단 탭바가 내용을 가리는 문제 원천 차단 — 하루 상세 마지막 버튼이 탭바 밑에 깔리던 것에서, 하단 크롬 채널 하나·여백 유도식 하나·도달 게이트까지 (2026-09-04)

  • 기간: 2026-09-04 (세션 3개 74dc3199959ece1013a81c15; 오너 보고 원문 "이 사진처럼 페이지의 내용이 하단 탭바에 가려서, 스크롤해도 더 안내려가져서 버튼을 못누르거나 하는 경우가 있는데 이런 일이 발생하지 않도록 원천적으로 해결하는 근본적인 방법이 있을까?")
  • 랜딩: 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, main abf6289f 리베이스 뒤: 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 4e2e 도달 게이트 — 표면 17종 × safe 0/34, 끝까지 스크롤 후 마지막 버튼이 크롬 위에 있고 눌리는지01c1da96
Phase 5ci: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.mjs 5건. 검사가 즉시 잔존 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_modules junction이 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를 더하지 않는다(바 자체가 홈 인디케이터 밑에 깔릴 수 있음) — 이번 트랙은 여백만 다뤘다.