Skip to content

유산소 세트 시간·거리 입력 — 초·m 단일 입력에서 분|초·km|m 토글과 [분]:[초] 입력까지 (2026-08-25)

  • 기간: 2026-08-25 (1세션, 오너 지시 원문: "유산소 세션에서 초단위 말고 분+초 단위로 시간 입력할 수 있게 옵션 추가해줘" + 실기기 피드백 3회 8건)
  • 랜딩: PR #790(Phase 1, edfc04ce) · PR #793(Phase 3, eecea0a5) · PR #796(Phase 4, 04b5f2d3) · PR #804(Phase 5, 0827b2b3) — 마이그레이션 없음, Vercel 자동 배포
  • 설계서: 없음(소건) — Phase 계획서·예상 효과 표는 이슈 #788 본문
  • 정본: src/react/ui/mobile/screens/WorkoutRecordParts.tsx WfMinSecInput([분]:[초])·WfKmInput(km)·WfMeasureJoin(in 구분자) · 종목별 상태 durOv·distOv(WorkoutRecord.tsx) · 표시행 구분자 formatSetMeasurements(measureSemantics.ts)
  • 도구: 없음
  • 게이트: tests/react/workoutRecordMinSecDuration.test.mjs(회귀 10건)
  • 버그리포트: 없음(기능 건) — 같은 보고 턴에서 나온 별건 도크 결함은 BUG-028(이슈 #801)로 분리
  • 계약: 변경 없음 — durationSeconds(초)·distanceMeters(m)가 저장 정본 그대로, 마커 유지

Phase 현황

Phase내용상태
Phase 1분·초 입력 + 종목별 시간 단위 토글, 3표면 배선✅ PR #790 (edfc04ce)
Phase 2CI·머지·작업 기록✅ PR #791 (2d1e78d7)
Phase 3오너 실기기 피드백 4건 — MM:SS 단일 입력·분|초 토글·옵션 행 필드 게이트·in 구분자✅ PR #793 (eecea0a5)
Phase 4오너 실기기 피드백 2차 3건 — 콜론 고정 분·초 분리 입력·자동 0 제거·in 글씨 통일✅ PR #796 (04b5f2d3)
Phase 5오너 지시 — 거리 km|m 토글·km 입력 + 표시행 구분자 in 일반 적용✅ PR #804 (0827b2b3)

1. 배경

유산소(러닝 등 시간 기록) 종목의 세트 시간 입력이 "초" 단일 필드였다. 20분 러닝을 기록하려면 1200을 암산해 입력해야 했고, 표시(formatDurationSeconds)는 이미 mm:ss라 입력과 표시의 단위가 서로 달랐다. 오너가 2026-08-25 분+초 입력 옵션을 지시했다(이슈 #788).

2. 문제 제기

시간 입력만 원자 단위(초) 그대로라 사용자가 환산을 대신했다

무게는 kg/lb/% 토글(unitOv), 휴식은 분·초 2필드(patchRest)가 이미 있었지만, 세트 시간은 세 입력 표면(세트 편집 행·빠른 추가·라이브 세트 컴포저) 모두 초 단일 입력뿐이었다.

3. 해결 방안

원칙 (오너 지시, 2026-08-25)

"초단위 말고 분+초 단위로 시간 입력할 수 있게 옵션 추가" — 저장 정본(durationSeconds)은 손대지 않고 입력 표면만 바꾼다.

접근

대안판단
분·초 2필드를 한 칸에 + 종목별 초|분:초 토글, 기본값은 데이터 추론채택 — kg/lb unitOv 패턴 재사용, 계약·그리드 불변
분:초를 무조건 기본으로기각 — 오너 요청이 "옵션", 초 단일에 익숙한 흐름 보존
mm:ss 텍스트 파싱 단일 입력기각 — number 키패드 상실, 파싱 오류 표면 추가
localStorage 사용자 설정기각 — UI 계층 no-storage 계약 위반, 데이터 추론으로 사실상 지속성 확보

4. 적용한 내용

Phase 1 — 분:초 입력 + 종목별 시간 단위 토글 (#790)

  • WfMinSecInput(WorkoutRecordParts): 분·초 2필드 한 칸. 합성값은 기존 mPatch(durationSeconds) 경로 그대로, 초 필드 0–59 클램프(patchRest와 동일 규칙), 소수점 초 유지, 외부 값 변경(아래 세트 동일 적용)은 드래프트 합성값 불일치 감지로 재파생.
  • 표면 3곳 배선: 세트 편집 행 · 빠른 추가 컴포저 · 세트 컴포저(라이브). data-lg-field="workout.set.duration" 마커는 한 칸 래퍼에 유지(designContract 레지스트리 동일).
  • 토글: 편집 옵션 "시간 단위" 행(wfx-seg) + 컴포저 "세트 내용" 캡 우측 세그. 종목별 상태 durOv, 기본값 = 해당 종목 세트에 60초 이상 시간 보유 시 분:초(kg/lb 데이터 추론과 동일 패턴).
  • CSS: .minsec 한 칸 2입력 + 편집 행 여백 조정(입력폭 23→28px).

주요 결정과 그 근거

  • 기본값 데이터 추론: 토글 상태는 세션 로컬(UI no-storage 계약)이라 지속성이 없다. 유산소 기록은 사실상 전부 60초 이상이므로 "기존 기록 보유 → 분:초"가 매 세션 자동 복원과 같은 효과를 낸다 — unitOv(loadLb 보유 → lb)와 같은 전례.
  • 한 칸 2입력: 세트 편집 행이 고정 그리드(20px 60px 1fr 13px 1fr 30px)라 필드 수가 늘면 파손 — 기존 필드 박스 안에서만 나눴다.

Phase 3 — 오너 실기기 피드백 반영 (#793)

오너가 계획 작성 화면 스크린샷과 함께 4건을 지시했다(2026-08-25 채팅):

  1. 옵션 행 필드 게이트 — "러닝에 무게 단위가 나와야 할 이유는 없잖아?" → 무게 단위 행은 기록 프로필에 load가 있을 때만 노출. 옵션 행은 기록 필드를 따라간다는 원칙이 섰다.
  2. 토글 선택지 = 분 | 초 — "분:초" 라벨 대신 두 단위 이름, 분 우선.
  3. 분 모드 = MM:SS 단일 입력 — "MM분 SS초 대신 MM:SS. 입력하는 부분이 조금 작으니까" → 2필드를 버리고 타이머식 자릿수 밀기 한 입력("3030" 타이핑 → "30:30" 표기, blur 시 "0:90" → "1:30" 정규화). 숫자 키패드에 콜론이 없어 자유 텍스트 파싱 대신 자릿수 밀기를 골랐다.
  4. 거리 다음 시간 구분자 × → in — "5km in 30:00" 의미. WfMeasureJoin이 거리→시간 사이만 in, 그 외 × 유지. 편집 행 13px 구분자 컬럼에 맞게 j-in(10.5px) 축소.

Phase 4 — 오너 실기기 피드백 2차 반영 (#796)

Phase 3 배포 화면(MM:SS 자릿수 밀기 단일 입력)을 보고 오너가 3건을 추가 지시했다(2026-08-25 채팅):

  1. 콜론 고정 + 분·초 분리 입력 — "입력칸 하나에 한번에가 아니라, 가운데 :는 고정, 왼쪽 클릭=분· 오른쪽 클릭=초" → 자릿수 밀기를 버리고 [분]:[초] 두 입력. 콜론은 고정 텍스트, 분 우측·초 좌측 정렬로 콜론에 붙는 타이머 표기. 초 칸은 0–59 클램프(휴식 입력과 동일)·소수점 허용.
  2. 자동 0 제거 + 탭 시 전체 선택 — 값 0·빈 값은 두 칸 모두 빈 칸, 탭하면 select()로 기존 텍스트 전체 선택 상태에서 바로 덮어쓴다. 저장된 값의 표기(20:00의 "00")는 유지 — 자동 삽입이 아니라 실제 값.
  3. in 글씨 통일 — in 구분자를 표면별 단위 라벨(m·kg)과 동일 값으로: 편집 행 12px/600(=setbox-unit), 빠른 추가 11px/600(=qa-line qa-unit), 컴포저 11px/700(=spv-unit). computed style 실측으로 일치 확인.

Phase 5 — 거리 km|m + 표시행 in 일반 적용 (#804)

오너 지시(2026-08-25 채팅, 라이브 화면 스크린샷): "거리도 km, m를 구분할 수 있게 해줘".

  • WfKmInput: km 단일 입력 — [분]:[초]와 같은 규칙(자동 0 없음·탭 전체 선택·소수점·blur 정규화), 저장은 distanceMeters(m 정본) 환산(3.5 → 3500).
  • 종목별 거리 단위 토글(km|m): 편집 옵션 "거리 단위" 행 + 컴포저 캡(시간 세그와 나란히 — 321px 캡에 세그 2개 84+76px 수납 실측). 기본값 = 1km 이상 기록 보유 시 km(같은 데이터 추론 패턴).
  • 표시행 구분자도 in: 오너 스크린샷의 세트 행이 "3500m × 30:00"으로 입력(in)과 어긋나 있어, 같은 지시("거리 x 시간인 경우 × 말고 in")를 formatSetMeasurements까지 일반 적용 — 세트 행·완료 메모·피드 카드·세션 상세·데스크톱 일지의 유산소(거리+시간) 조합만 in, 무게×횟수는 × 그대로.

작업 중 드러난 것

  • 분·초를 매 렌더 합성값에서 파생하면 초 필드의 소수점 타이핑("34.")이 끊긴다 — 드래프트 보유 + 외부 변경만 재동기화가 정답(컨트롤드 파생의 고전 함정).
  • ESLint 게이트는 억제 주석에 -- 사유(영어 관행) 그리고 eslint-baseline.json suppressionsByFile 등록을 함께 요구한다 — 사유만 붙이면 "new ESLint suppression requires review"로 다시 떨어진다.
  • PowerShell 5.1에서 한글·따옴표 섞인 본문을 gh/git 인자로 직접 넘기면 깨진다 — --body-file/-F 파일 경유가 정본 절차.

5. 적용 결과

항목결과
20분 입력"1200" 환산 암산 → 분 칸에 "20"(초 칸은 빈 칸 유지 가능)
기존 기록(예: 1830초) 재편집 표기1830 단일 → [30]:[30], 탭하면 해당 칸 전체 선택
빈 값·0 표기"0:00" 자동 채움 → 두 칸 모두 빈 칸
시간 입력폭(375px 실측)컴포저 분·초 각 60 → 84px, 편집 행 각 28 → 34px(콜론 고정)
러닝 편집 옵션무게 단위 행 노출 → 비노출(load 프로필에만)
거리×시간 구분자× → in (편집 행 13px 컬럼 수납 실측)
저장값·통계·표시변경 없음(초 정본 그대로) — 회귀 테스트로 고정
거리 입력(예: 3.5km)m 환산 암산("3500") → km 칸에 "3.5"
표시행 구분자(세트 행·피드·상세)"3500m × 30:00" → "3500m in 30:00" (무게×횟수는 × 유지)
컴포저 회귀(기존 4건+신규 10건)+영향권 13파일 171건전부 통과, CI verify 4회(#790·#793·#796·#804) 통과
375px 레이아웃 실측(로컬 vite+실 CSS+SSR 실마크업)3표면 신규 가로 오버플로 0
실기기 화면 왕복오너 iOS 실기기 확인 완료(2026-08-26) — 피드백 2회(Phase 3·4) 반영 뒤 "잘 적용되었어", 세션 종결 지시

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

유산소 기록 입력이 표시 단위와 같아졌다

입력(분+초)과 표시(mm:ss)의 단위가 일치 — 환산 암산 제거.

구조적으로 남는 것

  • WfMinSecInput·WfKmInput — durationSeconds·distanceMeters를 편집하는 다른 표면(향후 데스크톱 등)에서 재사용 가능한 단위 입력 짝.
  • WfMeasureJoin(입력)·formatSetMeasurements(표시) — 기록 항목 구분자 규칙(거리→시간 = in)의 소유처 2곳.
  • "옵션 행은 기록 필드를 따라간다" — 편집 옵션 노출 원칙(무게 단위 = load 프로필에만).
  • workoutRecordMinSecDuration.test.mjs — 초 단일/분(MM:SS) 두 모드 + in 구분자의 마크업 회귀 가드.

남은 것

  • 종결 — 오너 실기기 확인(2026-08-26), 이슈 #788 닫음.
  • 데스크톱 계획 편집기 시간·거리 입력(DesktopPlanEditorCardParts)은 단일 필드 그대로 — 별도 트랙.