종목 검색 synonym 계약 — 밀리터리 프레스 미검색에서 전 표면 표기 보존까지 (2026-08-24)
- 기간: 2026-08-24 (1세션. 오너 보고: "데스크톱 앱에서 계획을 작성하던 도중 밀리터리 프레스가 종목 검색결과에 나오지 않음")
- 랜딩: PR #690(Phase 1·2) → #692(Phase 3) → #694(Phase 4,
20260821680000) → #699(Phase 5,20260821710000) → #710(Phase 6). 마이그레이션 2건 Production 적용·실측, Vercel 자동 배포 확인 - 설계서: 없음 — 계획·오너 계약 확정·Phase 진행은 이슈 #673 쓰레드가 정본(작업 계획서 v1~v4)
- 정본: exercise-search 계약(신설) · 엔진
src/react/exerciseSynonymSearch.ts - 도구: Management API 러너(
scratchpad/mgmt-query.mjs) — Production 실측·dry-run(raise 롤백)·왕복 실증 - 게이트:
tests/react/exerciseSynonymSearch.test.mjs(엔진 12) ·tests/react/exerciseSearchSynonyms.test.mjs(리포트 검색 계약 7 재작성) ·tests/react/plannedSynonymPreservation.test.mjs(계획 왕복 3) · pgTAPexercise_catalog_synonym_aliases(8)·planned_sets_synonym_id(7) - 버그리포트: BUG-017
- 계약: 카탈로그 RPC contract v5→v6(synonym 상한 4→10·synonym별 aliases 16·64B) ·
planned_sets.synonym_id신설 · 검색 계약 문서 신설
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 진단(피커 필터 synonym 미참조·alias 미배선·계획 표기 칸 부재) + 오너 계약 확정 | ✅ 이슈 #673 |
| Phase 1 | synonym 합집합 검색 엔진 + 계약 문서 | ✅ PR #690 |
| Phase 2 | 데스크톱 계획/기록 피커 적용 + synonymId 드래프트 관통 | ✅ PR #690 |
| Phase 3 | 모바일 피커·복합 빌더 정렬(1행+치환 폐기) | ✅ PR #692 |
| Phase 4 | 카탈로그 RPC synonym별 aliases + 상한 10 (680000) | ✅ PR #694 · Production |
| Phase 5 | 계획 표기 보존 planned_sets.synonym_id (710000) | ✅ PR #699 · Production |
| Phase 6 | 전 표면 적용(리포트 공용 검색·데스크톱 5표면·모바일 세션 검색) | ✅ PR #710 |
| Phase 7 | 통합 검증(Production 실측 왕복·번들 확인) | ✅ 이 문서 §5 |
| Phase 8 | 기록(BUG-017·이 문서) | ✅ 이 문서 |
1. 배경
08-21 카탈로그 병합 캠페인에서 밀리터리 프레스는 바벨 오버헤드 프레스의 비-default synonym으로 흡수됐고, 병합 계약은 "흡수 이름은 synonym으로 남아 검색·표기 자격을 유지한다"였다. 그러나 그 자격이 실제로 구현된 표면은 모바일 피커 하나뿐이었고, 그마저 "종목당 1행 + 라벨 치환 + default 우선" 모델이었다.
2. 문제 제기
데스크톱 피커는 synonym을 아예 보지 않았다
계획/기록 피커 2곳이 대표 이름·영문명만 includes 필터. "오버헤드 프레스"가 떴던 것도 synonym 매칭이 아니라 대표 이름의 부분 문자열 우연이었다.
alias는 어느 피커에서도 검색되지 않았다
카탈로그 RPC가 synonym에 이름만 싣고 alias("ohp"·"o.h.p"·붙여쓰기)를 실어주지 않아 데이터부터 없었다. 종목당 synonym 상한 4도 잠재 절단 경계(실측 최대 3).
계획은 표기 선택을 저장할 칸 자체가 없었다
완료 기록에는 session_exercises.synonym_id가 있지만 planned_sets에는 없어서, 어떤 플랫폼에서든 계획을 저장하고 다시 열면 대표 표기로 회귀했다.
3. 해결 방안
원칙 (오너 계약 확정, 이슈 #673)
- 검색 모집단 = 모든 종목의 모든 synonym 합집합. 매칭된 synonym은 각각 독립된 결과 행.
- synonym은 서로 동등 — default는 표기가 하나만 필요한 자리(브라우즈·집계 라벨)의 tie-breaker일 뿐.
- alias는 소유 synonym 행으로 귀속 — "o.h.p" → 오버헤드 프레스 행만.
- 행 선택 = exerciseId(BRID, 집계 키) + synonymId — 유저가 검색해 고른 표기가 운동 진행/계획/기록/세션 조회 전 구간에서 유지된다. 혼용 시에도 엔트리별로 고른 값이 최우선.
- D1: 종목 검색이 들어가는 모든 표면에 적용. D2: 계획 표기 보존 포함. synonym 상한은 10으로.
접근
화면별 필터에 synonym 조건 추가— 같은 결함이 표면마다 재발한 이력(모바일만 구현됨) → 공용 엔진 + 계약 문서로 봉인.- 표면 유형 2종으로 정리: 피커·검색 결과(synonym별 독립 행,
filterExerciseSynonymRows) / 필터(행=종목 유지, 매칭 모집단만 합집합,createExerciseSynonymMatcher). - 엔진 배치는
src/react/exerciseSynonymSearch.ts(ui·services 공용 계층) — ui의 "NO services imports" 관례와 리포트 검색(services)을 한 구현으로 잇는 자리.
4. 적용한 내용
Phase 1·2 — 엔진 + 데스크톱 (#690)
synonym 행 전개(미탑재 항목 default 1행 합성)·NFKC 압축 매칭(붙여쓰기 흡수, 1자 토큰 오탐 차단)·행 선택(pickedExerciseFromSynonymRow = 원본 + 표기 + synonymId). 데스크톱 피커 2곳 교체, 드래프트(추가·종목 변경·왕복)에 synonymId 관통 — 기록 저장은 기존 session_exercises.synonym_id 계약 재사용.
Phase 3 — 모바일 정렬 (#692)
피커·복합 빌더를 같은 엔진으로. =default 병기 배지 제거(동등 계약). CI ESLint hooks 게이트(useMemo deps) 1회 적발 → 수리.
Phase 4 — alias 데이터 배관 (#694, 680000)
카탈로그 RPC 전문 재발행: synonym별 aliases(16개·64B — 실측 최대 14개·53B 전량 수용)·상한 4→10·contract v6. 클라 예산·어댑터·캐시 재직렬화(구버전 블롭은 재검증 축출→신선 fetch). payload 실측 891KB/1.2MB. 워크아웃 카탈로그 매퍼는 "표기 >1 또는 alias 보유"면 synonym 트리 탑재.
Phase 5 — 계획 표기 보존 (#699, 710000)
planned_sets.synonym_id(FK on delete set null·note 금지 check·부분 인덱스) + 저장/검증(구조 core 화이트리스트·validated_entry_synonym_id_v1 소유 검증)/엔진 insert/상세 RPC(additive 키) 전문 재발행 4종. 클라 왕복: savePlan payload → 행 → plannedSetView 표기 해석(exerciseDisplayName, 기록과 동일 규칙) → 그룹핑 → 운동 시작·템플릿 드래프트 승계.
Phase 6 — 전 표면 (#710)
리포트/PR 공용 검색(exerciseSearch.ts)을 synonym-행 계약으로 재구현(대표 우선 1행·sameAs 폐기, rowKey·synonymId), 데스크톱 수동 PR 모달(synonym 행)·PR 상세 드롭다운·볼륨 선택·즐겨찾기 그룹·통합 검색(세션 내 종목 synonym 매칭+하이라이트, catalog prop 신설)·세션 달력 종목 필터(매처) + 모바일 세션 검색 탭(synonym 행, 카탈로그 매퍼 alias 캐리지).
작업 중 드러난 것
- 마이그레이션 번호 선점 3연속(670000 컨디션·690000 관측성·700000 모트라 — 재번호 680000·710000). 커밋 직전과 PR 직전
ls-tree+ Production 장부 실측이 필수. - CI CASE-010 실패 1회 —
list_following_v1일시 오류가 새 관측성 그물에 걸린 플레이크(재실행 통과, 제 diff 무관 판정). supabase db push권한 차단 → 오너 권한 부여 후 재개. DB-first 창(새 번들 v6 기대 vs 서버 v5)이 잠시 열렸었음 — 다행히 680000이 즉시 적용되어 실피해 미확인.- 다른 세션 PR #709(세션 상세 서버 이름 해석)가 synonym 표기 우선으로 정합 — 계약이 트랙 간에 이미 작동.
5. 적용 결과
| 항목 | 전 | 후 |
|---|---|---|
| 데스크톱 피커 "밀리터리 프레스" | 0건 | 1행(그 표기로 입력·저장) |
| "프레스" 검색 결과(해당 종목) | 대표 1행 | synonym 3행 각각(같은 BRID 합산) |
| 피커 alias 검색("ohp"·붙여쓰기) | 전 플랫폼 0건 | 소유 synonym 행 히트 |
| 계획 저장·재로드 표기 | 대표로 회귀(전 플랫폼) | 고른 표기 유지 → 운동 진행·완료 기록 승계 |
| 리포트/PR·볼륨·달력 필터·통합 검색·세션 검색 탭 | 대표 이름만 매칭 | synonym 합집합 매칭(통합 검색은 하이라이트 포함) |
| 카탈로그 wire | synonym 이름만·상한 4 | +alias(16·64B)·상한 10·contract v6 |
| Production 실측 | — | RPC v6·synonym_id 컬럼·엔진 반영 + save_plan_v3 왕복(밀리터리 프레스 synonym 저장→상세 반환) raise-롤백 실증 + Vercel 번들 v6·synonym_id 확인 |
미검증: 오너 실기기/실브라우저 화면 확인(검색 행 표시·=배지 제거 시각) — 이슈 #673에서 확인 대기.
6. 이번 개선으로 향상된 것
- "표기는 synonym, 정체성은 BRID" 계약이 검색→입력→계획→운동 진행→기록→조회 전 구간에서 완결 — 이후 종목 병합 시 유저 표기 습관이 자동 보존된다.
- 검색 계약 문서 + 공용 엔진 + 계약 테스트 3계열(엔진·리포트·pgTAP)이 표면별 재발을 구조로 차단.
- Production 왕복 실증 기법(JWT 클레임 시뮬 + raise 롤백)으로 실배포 환경 검증을 흔적 없이 수행.
후속(08-25) — 입력 버벅임 회귀 + 매칭 단순화·표기 우선 (#717, BUG-021)
배포 직후 오너 보고 2건: 검색창 입력 불가 수준 버벅임(키 입력마다 행×용어 전체 로케일 정규화 — alias 데이터로 용어 폭증) + "밀리터리 프레스" 입력에 같은 종목 다른 표기 중복 노출(대표 표기의 alias 경유). 수리 = 오너의 "괜히 복잡해진 거 아냐" 지적을 수용한 단순화 — 매칭은 사전 계산 문자열 includes 하나(접기는 행 생성 1회·토큰 폴백 제거, 변형 흡수는 alias 데이터 담당) + 표기 매칭 우선(alias 행은 표기 매칭이 없을 때만). 실측 15ms+/타 → 0.32ms/타(~45배). 계약 규칙 4·6 갱신.
이후 3차 보강까지 같은 날 랜딩: #723 타이핑 표면 4곳 리스트를 useDeferredValue로 입력에서 분리, #746 alias는 prefix 매칭("ov"가 alias 중간에 걸려 스플릿 저크·바벨 로우가 나오던 소음 7건 → 0건, 표기는 substring 유지), #752 리스트 렌더 게이트 — 오너 기준(스페이스/엔터 즉시 + 100ms 무입력에만 반영, useDebouncedSearchQuery 신설) + 결과 리스트 element 동일성 고정(질의 미반영 키의 ~950행 재diff 제거). 최종 실측: 자모 연속 입력 키당 10.4~15.6ms → 0.5~1.6ms, 타이핑 중 무거운 리스트 커밋 11회 → 0회. 상세: BUG-021 4-1·4-2. 이후 수리 연쇄(자모·토큰·창 렌더·전 표면, #762~#770)는 별도 트랙 기록 2026-08-25-search-input-latency로 분리(이슈 #771).
남은 것
- 오너 실사용 확인(이슈 #673) — 확인 후 이슈 종결.
- 관리자 페이지(barbelic-docs /admin) 종목 검색 표면은 이 트랙 범위 밖(관리자 전용, 이슈 #661 트랙에서 카탈로그 개편 예정).