Skip to content

일지 하루 상세 세션 카드 로딩 성장 제거 — 상세 도착 전 종목 스켈레톤 (2026-08-29)

  • 기간: 2026-08-29 (세션 1, 오너 보고 "세션 박스가 나타났다가 내용이 채워지면서 커지는 느낌이라 별로" → 원인 분석 보고 → "응 진행해줘")
  • 랜딩: PR #913 (6ae860bd) — Vercel Production success·청크 실측(아래 5절)
  • 설계서: 없음 — 이슈 #905 본문이 계획서(원인 분석·Phase 계획·예상 효과 절)
  • 정본: calendarReadModelMapper.fullForCalendarEntry_calendarDetailPending 신호 · DayDetailBody.tsx DsvExSkel
  • 도구: 실 CSS 레이어 맵 재현 프로브(세션 scratchpad, 레포 밖 — SSR 3상태 비교 + js 기하 판정)
  • 게이트: dayDetailPendingSkeleton.test.mjs(투영 2 + 렌더 3) · 기존 calendarReadModelMapper.test.mjs(새 계약 반영)·mobilePostSaveReadModelRegression.test.mjs(저장 직후 스칼라 보존)
  • 버그리포트: BUG-036
  • 계약: docs/contracts/day-summary-props.md 상세 대기 스켈레톤 절 추가

Phase 현황

Phase내용상태
Phase 1대기 신호·이름 재료 (services) — _calendarDetailPending + exercise_ids 이름 플레이스홀더 폴백✅ PR #913
Phase 2스켈레톤 렌더 (ui) — DsvExSkel + day-summary.css, pend-reveal 300ms✅ PR #913
Phase 3테스트·검증 — 계약 테스트 5건 + 실 CSS 프로브 실측(성장 +156px → 0)✅ PR #913
Phase 4기록 — 본 문서 + BUG-036 + 등록 2곳✅ 이 PR

1. 배경

일지 달력 데이터는 2층이다 — 월 모델(get_calendar_month_summary: 세션 제목·상태·note·exercise_ids·서버 집계, exercises는 항상 [])과 일 모델(get_calendar_day_summary: 풀 세션·세트, 날짜를 열 때 온디맨드). 이 구조 자체는 의도된 설계(월=요약, 일=온디맨드)로, 달력 렌더 구조 정리(08-24)·통계 중앙화(08-22)를 거치며 정련됐다. 하루 상세 화면은 v81 재설계(08-27, #881·#883)로 세션 카드에 종목·세트 축약·메모 버블까지 싣게 되면서 "상세 도착 전후"의 시각 차가 커졌다.

2. 문제 제기

카드가 도착 전 상태를 표현하지 않았다

날짜 탭 → 드로어 즉시 열림 → 카드는 월 라이트 행(세트 없음)으로 렌더 — DsvExSum이 세트 없는 종목 행을 전부 건너뛰어 헤더만 있는 납작한 박스. 하루 RPC 도착(첫 진입마다) 시 내용이 채워지며 카드가 훌쩍 커진다. 오너 보고(08-29): "세션 박스의 크기가 뭔가 나타났다가 내용이 채워지면서 커지는 느낌이라 뭔가 좀 별로인데, 처음부터 내용이 채워진채로 나타나게 할 수 있는 방법이 있을까? 로딩 스켈레톤이 필요한가?"

쓸 수 있는 카드 단위 로딩 신호가 없었다

selectedDateDetailLoading은 데스크톱만 소비하는 전역(선택 날짜) 신호였고, 라이트 행의 detailLoaded는 읽기 모델 경로에서 항상 false로 박힌 구 경로 유물이라 게이트로 쓸 수 없었다. 신뢰할 수 있는 신호는 entry의 detail 유무뿐인데 full 객체에 실리지 않았다.

3. 해결 방안

  • 원칙(오너 승인 08-29): 진짜 내용을 처음부터 채우는 길(일 상세 프리페치·월 payload에 세트 동봉)은 비용·설계 위반으로 기각. **카드가 처음부터 최종 높이 근사치로 뜨도록, 대기 중 "실명 종목 행 + 세트열 자리 셔머"**를 렌더 — 기존 로딩 원칙(스피너 폐기·스켈레톤 한 계열·.pend-reveal 300ms 지연 노출) 재사용.
  • 접근: 신호는 매퍼가 만든다(_calendarDetailPending: !detail) — UI는 프롭으로 받은 세션 행의 플래그만 본다(Data in via props 경계 유지). 이름 재료는 월 행 exercise_ids(카탈로그 해석)로 충당.

4. 적용한 내용

Phase 1 — 대기 신호·이름 재료 (services)

fullForCalendarEntry가 완료 세션 full에 _calendarDetailPending을 싣고, detail 부재일 때만 exercise_ids → 이름 플레이스홀더(세트 0개) 폴백. 주요 결정: ① detailLoaded를 true로 뒤집는 안은 기각 — sessionDetailStore가 재조회 스킵 게이트로 소비 중이라 세션 상세 시트가 compact 데이터로 굳는다. 전용 언더스코어 필드 신설. ② 폴백 범위를 detail 부재로 좁힘 — 작업 중 드러난 것: detail이 도착했지만 compact가 빈 저장 직후 상태에 가짜 행을 만들면 lgSessionToFull이 "exercises 있음"을 근거로 스칼라 볼륨(350kg)을 0으로 덮는다. 기존 회귀 테스트(mobilePostSaveReadModelRegression)가 즉시 검출해 줬다.

Phase 2 — 스켈레톤 렌더 (ui)

DayDetailBody 세션 카드가 pending이면 DsvExSum 대신 DsvExSkel: A. 종목명 실명 행 + 세트열 자리 셔머. 스켈레톤 seq min-height: 1.55em으로 실제 줄 라인박스와 항등(셔머 바 12px는 그대로 두고 라인박스만 확보). 종목 미상이면 이름 자리선 1행. 메모 버블은 월 행 note가 있어 처음부터 진짜 내용으로 렌더.

Phase 3 — 테스트·검증

신규 dayDetailPendingSkeleton.test.mjs 5건(월만 투영 → pending·실명 플레이스홀더 / 일 모델 → 해제 / 렌더 스켈레톤 행 수·메모 버블·해제). calendarReadModelMapper.test.mjs의 "월만 적재 = exercises 0개" 단언을 새 계약으로 갱신(compact 의도는 "전 행 sets 빈 배열"로 유지, pending-changes 병합 신고). 작업 중 드러난 것: CSS @layer 화면의 프로브는 레이어 맵을 재현해야 한다 — 1차 프로브가 base 파일들을 무레이어로 이어 붙이자 @layer screen.dsv-exsum .row { display:block }이 무레이어 .app .row { display:flex }에 져서 셔머 폭이 0으로 나오는 가짜 실측이 나왔다. styles.css 매니페스트의 layer(tokens/base) import를 그대로 재현하자 프로덕션과 동일한 캐스케이드로 판정됐다.

5. 적용 결과

항목
하루 상세 첫 진입 세션 카드(종목 3+메모 픽스처, 실 CSS 프로브)151px로 떴다가 → 307px (+156px 점프)대기 307px = 로드 307px (성장 0, 행 39px 항등)
대기 중 카드 내용상태 칩·제목·시각만+ 실명 종목 행·메모 버블·세트열 셔머(300ms 내 로드는 셔머 미노출)
카드 단위 상세 대기 신호없음(전역 신호는 데스크톱 전용)_calendarDetailPending — 같은 본문을 쓰는 표면이 재사용 가능
게이트도착 전 상태 무계약투영·렌더 5건 상주
  • 검증: react 레인 1835/1835 · tsc 0 · dead-css·dual-key·unused·test-manifest·brand 게이트 통과 · CI scope·verify·migration-smoke green(1차 verify 실패는 테스트의 카탈로그 시딩이 CI에서 이중 모듈 인스턴스로 갈라져 미도달한 것 — 테스트를 카탈로그 무의존으로 재조준해 해소, 제품 코드 무변경) · Vercel Production success + 청크 실측: DayDetailBody-QC3ToCK2.js(dsv-exskel·sk-seq·_calendarDetailPending) · mobileRoot-CaDNwSHR.css(.dsv-exskel 규칙) · app-5IoqFXT5.js(_calendarDetailPending 발급).
  • 미검증: 오너 실기기 체감(첫 진입 성장 소멸·셔머 깜빡임 없음).

6. 이번 개선으로 향상된 것

  • 하루 상세 첫 진입의 레이아웃 점프가 사라졌다 — 카드가 처음부터 제자리 크기로 뜨고, 내용은 텍스트 교체로만 도착한다.
  • 대기 중에도 진짜 정보(종목명·메모)가 먼저 보인다 — "무엇을 했는지"는 RPC 도착 전에 이미 읽힌다.
  • 온디맨드 상세 표면의 "도착 전 상태"가 계약·테스트로 고정됐다 — 후속 개편이 이 상태를 조용히 되돌리기 어렵다.

남은 것

  • 오너 실기기 확인(이 트랙의 종결 조건).
  • 하루 RPC 실패 시 셔머 잔존(날짜 재선택이 재시도 경로) — 에러 표식은 별도 판단.
  • 자유기록(note) 행은 상세 도착 시 추가되므로 해당 세션은 소폭 성장 잔존 · 월 exercise_ids가 id 정렬이라 도착 시 행 순서 재배열 가능(높이 불변).