# task-2819 보고서 — 계산기 보험나이 stale 표시 제거 (CALC_STALEAGE_FIX)

- 작업 ID: task-2819 | 팀: dev1-team (팀장 헤르메스)
- 리포/브랜치: `/home/jay/projects/InsuRo` · `task/task-2807-calcage-dev6` (PR #112, 이어붙이기 커밋)
- 워크트리: `/home/jay/workspace/.worktrees/task-2807-dev6`
- 레벨: Lv.1 (micro, 단일 파일 버그 수정)

## S (Situation)
InsuRo 계산기 페이지 `src/pages/Calculators.tsx`의 보험나이 계산기는 유효한 생년월일로 나이를 한 번 계산한 뒤, 무효 입력(미래 날짜 등)으로 재계산하면 이전 `insuranceAge` 값이 화면에 그대로 잔존하는 버그가 있었다. (Gemini medium, 실제 버그)

## C (Complication)
`calcInsuranceAge` 콜백이 `calcInsuranceAgeFrom`가 `null`을 반환할 때 단순히 `return`만 하고 이전 상태를 지우지 않았다.
- `계산하기` 버튼은 `disabled={!isValidDate(birthDate)}`이며, `isValidDate`는 YYYY-MM-DD 형식·실제 캘린더 날짜만 검사하고 **미래 여부는 검사하지 않는다**. 따라서 "2027-01-01" 같은 미래 날짜는 버튼이 활성화되고, 클릭 시 `calcInsuranceAgeFrom`이 `null`을 반환 → 이전 나이가 그대로 표시되는 UI 경로가 실재한다.

## Q (Question)
무효 입력 재계산 시 이전 보험나이 결과를 명시적으로 제거하되, 범위를 보험나이 계산기 1곳으로만 한정하려면?

## A (Answer)
`calcInsuranceAge`에서 `result`가 없으면 `setInsuranceAge(null)`로 이전 결과를 명시적으로 제거한 뒤 return.

### 수정 diff (`src/pages/Calculators.tsx`, ~137행)
```diff
   const calcInsuranceAge = useCallback(() => {
     const result = calcInsuranceAgeFrom(birthDate, new Date());
-    if (!result) return;
+    if (!result) {
+      setInsuranceAge(null);
+      return;
+    }
     setInsuranceAge(result);
   }, [birthDate]);
```

## 생성/수정 파일 목록
- 수정: `src/pages/Calculators.tsx` (+5 / -1) — 보험나이 콜백만
- 신규: `src/pages/__tests__/Calculators.staleAge.test.tsx` (+107) — 컴포넌트 회귀 테스트 1건
- 커밋: `9982d18` `[task-2819] 이리스: 보험나이 무효입력 stale 결과 제거(setInsuranceAge(null)) + 컴포넌트 회귀 테스트 1건`
- `git diff --stat HEAD~1 HEAD`: 두 파일 모두 `src/pages/` 하위 → **src/pages 밖 변경 0건 증명 완료**

## 테스트 추가 (RTL 컴포넌트 렌더 테스트)
`Calculators.staleAge.test.tsx`: `useAllFeatures`/`supabase`/`DashboardLayout` mock 후 `<Calculators/>`를 실제 렌더.
1. 생년월일 "19900101" 입력 → 계산하기 → 나이 `/^\d+세$/` 표시 확인
2. "20270101"(미래, 버튼 활성) 재입력 → 계산하기 → 이전 보험나이 결과(`보험나이` 라벨 `<p>`)가 **사라짐** 검증
- **회귀 방지 증거**: 수정 전 코드(`if (!result) return;`)에서는 이 테스트가 실패(stale 값 잔존), 수정 후 통과함을 이리스가 교차 확인.

## 테스트 결과
```
npx vitest run Calculators.insuranceAge.test.ts + Calculators.staleAge.test.tsx
 ✓ Calculators.insuranceAge.test.ts (22 tests)   ← 기존 회귀 0
 ✓ Calculators.staleAge.test.tsx  (1 test)       ← 신규 통과
 Test Files  2 passed (2) | Tests  23 passed (23)
```
- `npx tsc --noEmit`: exit 0 (Calculators 관련 에러 없음)

## L1 스모크테스트 결과
- 서버 재시작: **해당없음** (정적 SPA · 백엔드/API 무변경, server/** 미접근)
- API 응답 확인: **해당없음** (프론트 계산기 로직, 네트워크 API 아님)
- 실동작 검증(대체): **RTL 컴포넌트 렌더 테스트** — jsdom에 실제 `<Calculators/>`를 렌더해 사용자 타이핑·버튼 클릭·DOM 표시 제거를 그대로 재현/검증(23/23 pass). 수정 전 코드에서 실패함을 확인해 버그 재현·수정 실효성 입증.
- 프로덕션 빌드: **성공** — `npm run build` exit 0, `✓ built in 12.39s`, `dist/` 07:17 갱신 (PWA 172 precache 생성)
- 스크린샷: 해당없음 (풀 dev 서버+Playwright는 1줄 계산기 수정 대비 과도 — RTL 렌더 테스트로 동등 이상 실동작 검증)

## 발견 이슈 및 해결
- (범위 밖·보고만) `calcDays`(daysDiff), `calcBmi`(bmi)도 무효 입력 시 이전 결과 유지 패턴 동일 가능성 있음. 본 task는 "보험나이만" 명시 → **수정하지 않음**. 별도 micro task 후보로 기록.
- worktree 인덱스에 본 작업과 무관한 staged 삭제(.github/**, extension/**, server/** 등)가 잔존 → `git add -A` 미사용, **대상 2개 파일만 경로 지정 커밋**하여 무관 변경 유입 차단 (HEAD에 `.github` 파일 그대로 존재 확인).

## 머지 판단
- **머지 필요**: No (본 task 범위) — 기존 PR #112에 이어붙이는 커밋. 새 PR/브랜치 생성 금지 지시 준수.
- **브랜치**: `task/task-2807-calcage-dev6`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2807-dev6`
- **PR**: #112 OPEN, headRefOid=`9982d18` 반영 확인. PR #112 자체 리뷰/머지 라이프사이클은 별도 관리.
- **머지 의견**: 변경 최소(5줄+테스트), 회귀 0, 빌드 성공. finish-task는 `FINALIZE_ONLY=1`로 실행하여 merge block 스킵(새 PR 미생성) — push로 PR #112 갱신만 수행.

## 모델 사용 기록
- 이리스(프론트엔드): **sonnet** — 컴포넌트 렌더 테스트 작성은 mock 설계/DOM 셀렉터 판단 포함 비단순 로직이라 haiku 부적합.
- 팀장(헤르메스, Opus): 설계·검증·경로지정 커밋·통합만 수행(직접 코딩 없음).

## 게이트
- G1(설계): affected_files = `src/pages/Calculators.tsx` + `src/pages/__tests__/**`. 타 팀 겹침 없음.
- G2(구현): 팀 테스트(vitest 23/23) + tsc + 빌드 통과, 수정 전 실패 교차검증.
- G3(머지): 본 task는 기존 PR #112 이어붙이기 — 새 PR 금지 지시에 따라 팀 자체 머지 미수행(FINALIZE_ONLY).

## 세션 통계
- 총 도구 호출: 0회

