> find-word@0.1.0 test > vitest run tests/App.test.tsx tests/release.test.ts -t 브랜드 RUN v5.0.4 /home/hermes/worktrees/find-word-v0.1 ❯ tests/release.test.ts (1 test | 1 failed) 45ms ❯ 릴리스 문서 계약 (1) × 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 43ms ❯ tests/App.test.tsx (13 tests | 1 failed | 12 skipped) 225ms ❯ 시작부터 결과까지 사용자 흐름 (13) × 브랜드·버전·크레딧을 보여주며 직접 시작하기 전에는 재생하지 않는다 224ms ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯ FAIL tests/App.test.tsx > 시작부터 결과까지 사용자 흐름 > 브랜드·버전·크레딧을 보여주며 직접 시작하기 전에는 재생하지 않는다 TestingLibraryElementError: Unable to find an element with the text: Designed by OZ at Heat-T Medical Inc.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style  
  본문으로 건너뛰기          

 단어를 맞춰라 

  v0.1  
   플레이 방법   ↓    소리에 집중하는 시간         귀를 기울이면, 발견하는 즐거움 

  소음 속 작은 단어, 
  들리시나요?    도시의 소리 사이에 에스가 숨겨 둔 단어. 
 조금 더 귀 기울여, 오늘의 행운을 찾아보세요. 

   10개의 질문   5단계 도전   나만의 속도로         에스의 사운드 룸   8 가지 환경      ♪ 
  ✳   행운의 단어를 찾아보거라!         S                                          소음 사이, 숨겨진 한마디        01  
  나의 소리 탐험 준비  

 편한 방식으로 시작하세요. 난이도는 조금씩 높아져요. 

 
  약 3–5분      어떻게 답할까요?      ◈   골고루 풀기   선택 + 직접 입력      ☷   보기에서 선택   4개 단어 중 하나      ✎   직접 입력   기억한 단어 쓰기       배경 환경    여덟 가지 일상 속으로   길거리   쇼핑몰   극장   백화점   시장   지하철   카페   비 오는 거리   

 길거리부터 비 오는 거리까지, 매번 다른 공간을 만나요. 

   길거리   카페   지하철   +5개 환경        ◖))   전체 음량    35 %    

  ♬   편안한 작은 음량으로 시작해 보세요. 

  게임 시작 · 소리 듣기   →       작은 단어를 발견하는 방법   
  01  

 먼저, 귀 기울이기 

 

 에스의 안내와 소음 속 문장을 들어요. 
 처음에는 또렷하게, 점점 더 작게. 

 
 
  02  

 기억한 단어 찾기 

 

 소리가 끝나면 질문이 나타나요. 
 보기에서 고르거나 직접 입력해요. 

 
 
  03  

 나의 발견 돌아보기 

 

 열 문제 뒤 나의 결과를 확인해요. 
 놓쳤다면 언제든 다시 들어도 좋아요. 

 
     

 Designed by OZ at HearT-M Medical Inc. 

 

 청각적 주의·식별 게임 · 의료적 진단이나 치료를 제공하지 않습니다. 

  개인정보 수집 없이, 오직 플레이만.     ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ tests/App.test.tsx:215:14 213| ).toBeInTheDocument(); 214| expect( 215| screen.getByText("Designed by OZ at Heat-T Medical Inc.", { exac… | ^ 216| ).toBeInTheDocument(); 217| expect(screen.getByText("v0.1")).toBeInTheDocument(); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: README.md: expected '# 단어를 맞춰라 · Find-Word v0.1\n\n소음 속에 숨…' to contain 'Designed by OZ at Heat-T Medical Inc.' - Expected + Received - Designed by OZ at Heat-T Medical Inc. + # 단어를 맞춰라 · Find-Word v0.1 + + 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다. + + **Designed by OZ at HearT-M Medical Inc.** + + React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장, + 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며 + 의료적 진단·치료나 효과를 주장하지 않습니다. + + ## 실행 + + Node.js **22.12 이상**의 지원되는 LTS 버전을 권장합니다. `package-lock.json`을 + 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다. + + ```bash + npm ci + npm run dev + ``` + + 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를 + 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다. + + ## 플레이 + + - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다. + - 8개 환경을 순환하거나 하나를 지정합니다. + - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다. + - 10문제 동안 2문제마다 난이도가 올라갑니다: **SNR +6, +3, 0, −3, −6 dB**. + 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다. + - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 최대 40점의 보너스를 받습니다. + - 결과에서 점수·정답률·최고 연속 정답·단계별 결과를 확인하고 다시 시작할 수 있습니다. + - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다. + + 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과 + 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은 + [WORDS.md](docs/WORDS.md)에 설명했습니다. + + ## 검증과 정적 빌드 + + ```bash + npm run lint + npm test + npm run build + npm run preview + ``` + + `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기 + 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개가 원본 해시 그대로 + 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다. + + 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증: + + ```bash + # Chromium이 없는 개발 환경에서 한 번만 실행합니다. + npx playwright install chromium + npm run test:browser + # 이미 설치된 Chrome을 사용할 때의 대안입니다. + CHROME_PATH=/usr/bin/google-chrome npm run test:browser + ``` + + 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요. + 실제 음성 재생이 끝나기를 기다려 약 1–2분이 걸립니다. 로컬 4173 포트를 + 테스트 중에만 열고 종료합니다. 스크린샷은 `.agent/evidence/`에 남습니다. + + ## 음성과 문서 + + 한국어 WAV는 직접 쓴 문장을 로컬 eSpeak NG로 합성했습니다. 환경음은 + 브라우저에서 고정 seed로 합성합니다. 외부 음원이나 신경망 음성 모델을 사용하지 않습니다. + 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를 + 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다. + + - [설계와 상태 전이](docs/ARCHITECTURE.md) + - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md) + - [RED/GREEN 및 실제 검증 결과](.agent/RESULT.md) + + 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다. + ❯ tests/release.test.ts:11:53 9| const credit = "Designed by OZ at Heat-T Medical Inc."; 10| for (const path of ["README.md", ".agent/TASK.md", ".agent/ACCEPTA… 11| expect.soft(readFileSync(path, "utf8"), path).toContain(credit); | ^ 12| } 13| for (const path of ["README.md", ...["docs", ".agent"].flatMap((di… ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/TASK.md: expected '# TASK — Find-Word v0.1\n\n## 역할\n\n당…' to contain 'Designed by OZ at Heat-T Medical Inc.' - Expected + Received - Designed by OZ at Heat-T Medical Inc. + # TASK — Find-Word v0.1 + + ## 역할 + + 당신은 구현 담당 Codex다. 이 저장소에서만 작업하고 프로덕션 서버·DNS·인증정보에는 접근하지 않는다. Neo가 코드 리뷰·배포를 별도로 수행한다. + + ## 제품 + + - 서비스명: **단어를 맞춰라** + - 표기: **Designed by OZ at HearT-M Medical Inc.** + - 버전: **0.1** + - 배포 예정 도메인: `findword.heart-m.com` + - 언어: 사용자 UI와 상세 소스 주석은 한국어 + + ## 목표 + + 여러 환경 소음 속에 작은 음량으로 섞인 특정 한국어 단어나 문장을 듣고 질문에 답하는 웹 게임을 만든다. 쉬운 단계부터 소음 대비 키 문장의 dB 차이를 늘려 난이도를 높인다. 객관식과 주관식을 모두 지원한다. + + ## 핵심 시나리오 + + 1. S 캐릭터가 “으하하하~~ 나는 에스다. 널 위한 행운의 단어를 불러주마, 잘 찾아보거라.”라고 안내한다. + 2. 웃음과 안내 음성이 배경 소음 속으로 점차 묻힌다. + 3. “오늘의 내 행운은 ‘사과’에 있다.” 같은 키 문장을 재생한다. + 4. 재생이 끝난 뒤 질문과 네 개의 선택지를 표시한다. + 5. 선택지는 정답 1개와 사전에 존재하는 유사어 오답 3개로 구성한다. + 6. 오답은 자음·모음 변형 규칙의 서로 다른 난이도를 반영하고, 데이터에 변형 종류를 명시한다. + + ## 기능 요구사항 + + - 반응형 SPA, 데스크톱·모바일 키보드/터치 지원 + - 시작 화면, 게임 화면, 결과 화면, 다시 듣기, 다음 문제, 전체 진행률 + - 사용자가 브라우저 정책상 필요한 “소리 시작” 조작을 직접 누르도록 설계 + - Web Audio API로 보이스와 배경을 믹싱하고 목표 음성 gain을 dB 기준으로 적용 + - 최소 5단계 난이도. 단계가 높아질수록 목표 문장과 배경의 상대 dB가 낮아짐 + - 최소 10개 문제 유형. 객관식·주관식을 포함하고 데이터 조합으로 실제 문제가 더 늘어남 + - 최소 8개 배경 환경: 길거리, 쇼핑몰, 극장, 백화점, 시장, 지하철, 카페, 비 오는 거리 + - 기본 패턴 문장, 시험 문장, 질문, 답, 오답 후보, 배경 프리셋을 저장된 데이터로 분리 + - 사전 단어만 사용하는 4지선다 데이터와 각 오답의 변형 규칙 메타데이터 + - 점수, 정답률, 연속 정답, 난이도별 결과 + - 접근성: aria, 명확한 focus, 키보드 조작, reduced motion, 색 대비 + - 오디오가 없거나 로드 실패해도 사용자에게 명확한 오류와 재시도 제공 + - 개인 데이터 수집·로그인·추적기 없음 + - 버전 0.1을 UI와 package metadata에 표시 + + ## 오디오 요구사항 + + - 라이선스 불명확한 외부 자산을 사용하지 않는다. + - 직접 생성한 배경 소음·효과음을 저장하거나, 브라우저에서 결정론적으로 합성하는 프리셋을 사용한다. + - 한국어 목표 음성은 가능하면 직접 생성한 오디오 파일로 저장한다. 네트워크 TTS를 사용했다면 생성 스크립트·출처·생성 절차를 문서화하고 앱은 런타임에 외부 TTS를 호출하지 않는다. + - 모든 자산의 provenance를 `docs/AUDIO_PROVENANCE.md`에 기록한다. + - 오디오 파일을 커밋할 경우 크기를 합리적으로 제한하고 build 결과에 포함되는지 테스트한다. + + ## 개발 규칙 + + - strict TDD: 각 동작은 실패하는 테스트를 먼저 작성하고 실제 RED→GREEN 근거를 `.agent/RESULT.md`에 기록한다. + - TypeScript strict mode를 사용한다. + - 모든 직접 작성 소스 코드에 상세한 한국어 주석을 단다. 자명한 문법 줄마다 반복 주석을 붙이지 말고 알고리즘, 상태, 오디오, 난이도, 접근성, 보안 의도를 설명한다. + - generated lockfile과 기계 생성 바이너리는 주석 대상이 아니다. + - 비밀값, 서버 자격증명, API 키를 저장하지 않는다. + - 서버 배포는 하지 않는다. + - Git 커밋은 하지 않는다. Neo가 검증 후 커밋한다. + + ## 권장 기술 + + - Vite + React + TypeScript + - Vitest + Testing Library + - 순수 CSS 또는 가벼운 CSS 구조 + - 정적 빌드(`dist/`)로 Nginx 배포 가능 + + ## 산출물 + + - 동작하는 v0.1 소스 + - 최소 10개 문제 유형과 조합 가능한 데이터 + - 오디오 생성·재생 구조 + - unit/component 테스트 + - `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`, `docs/DEPLOYMENT.md` + - `.agent/RESULT.md`: 변경 파일, RED/GREEN 명령과 실제 결과, 전체 검증 결과, 미해결 위험 + ❯ tests/release.test.ts:11:53 9| const credit = "Designed by OZ at Heat-T Medical Inc."; 10| for (const path of ["README.md", ".agent/TASK.md", ".agent/ACCEPTA… 11| expect.soft(readFileSync(path, "utf8"), path).toContain(credit); | ^ 12| } 13| for (const path of ["README.md", ...["docs", ".agent"].flatMap((di… ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/ACCEPTANCE.md: expected '# ACCEPTANCE — Find-Word v0.1\n\n## 제…' to contain 'Designed by OZ at Heat-T Medical Inc.' - Expected + Received - Designed by OZ at Heat-T Medical Inc. + # ACCEPTANCE — Find-Word v0.1 + + ## 제품 + + - [ ] H1 또는 브랜드 영역에 `단어를 맞춰라` 표시 + - [ ] `Designed by OZ at HearT-M Medical Inc.` 표시 + - [ ] v0.1 표시 + - [ ] 게임 시작부터 결과·재시작까지 완결된 흐름 + - [ ] 객관식과 주관식 모두 실제 플레이 가능 + - [ ] 최소 10개 문제 유형이 코드·데이터·테스트로 확인됨 + - [ ] 최소 8개 배경 환경 프리셋 + - [ ] 최소 5개 dB 기반 난이도 + - [ ] 문제 조합이 고정 10문제를 넘어 확장 가능 + - [ ] 사전 단어 기반 오답과 변형 규칙 metadata + + ## 오디오 + + - [ ] Web Audio API로 배경과 목표 음성 gain을 분리 + - [ ] 난이도별 상대 dB가 수학적으로 검증됨 + - [ ] 첫 재생은 사용자 gesture 이후 실행 + - [ ] 재생 중 중복 시작·다음 문제 전환 race 방지 + - [ ] 오디오 실패 UI와 재시도 + - [ ] provenance 문서와 생성 절차 + - [ ] 런타임 외부 TTS/API 호출 없음 + + ## 품질 + + - [ ] strict TypeScript + - [ ] 직접 작성한 소스에 상세한 한국어 주석 + - [ ] unit/component 테스트에서 문제 유형 수, 선택지 무결성, 정답 판정, 주관식 정규화, 난이도 dB, 점수, 오디오 상태 검증 + - [ ] lint 통과 + - [ ] test 통과 + - [ ] production build 통과 + - [ ] 접근성 이름, 키보드 focus, reduced motion + - [ ] 360px 모바일과 1280px 데스크톱 레이아웃 + - [ ] secret scan 무검출 + + ## 문서 + + - [ ] README에 개발·테스트·빌드 방법 + - [ ] ARCHITECTURE에 데이터·상태·오디오 설계 + - [ ] AUDIO_PROVENANCE에 생성·라이선스·재생 방식 + - [ ] DEPLOYMENT에 Nginx static 배포와 SPA fallback + - [ ] `.agent/RESULT.md`에 실제 RED/GREEN 및 최종 검증 기록 + ❯ tests/release.test.ts:11:53 9| const credit = "Designed by OZ at Heat-T Medical Inc."; 10| for (const path of ["README.md", ".agent/TASK.md", ".agent/ACCEPTA… 11| expect.soft(readFileSync(path, "utf8"), path).toContain(credit); | ^ 12| } 13| for (const path of ["README.md", ...["docs", ".agent"].flatMap((di… ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/CONTEXT.md: expected '# CONTEXT — Find-Word v0.1\n\n## 저장소와…' to contain 'Designed by OZ at Heat-T Medical Inc.' - Expected + Received - Designed by OZ at Heat-T Medical Inc. + # CONTEXT — Find-Word v0.1 + + ## 저장소와 책임 경계 + + - canonical repository: `https://source.ibit.kr/OZ-Group/Find-Word` + - 작업 브랜치: `feat/v0.1` + - 현재 worktree: `/home/hermes/worktrees/find-word-v0.1` + - Codex는 이 worktree만 수정한다. + - Neo가 최종 diff 검토, 테스트, source push, Heart 서버 배포, Nginx/TLS/DNS 검증을 담당한다. + + ## 배포 예정 환경 + + - Ubuntu 24.04 + - Nginx 1.24 + - Node.js 22는 설치되어 있으나 프로덕션은 정적 `dist/`를 Nginx가 직접 제공하는 구성을 선호한다. + - 예정 도메인 `findword.heart-m.com`은 현재 Heart 서버 IP로 DNS 해석된다. + - 배포 파일·서비스·포트는 아직 존재하지 않는다. + + ## 제품 해석 + + - “4개의 단어”를 우선한다. 정답 1개와 변형 규칙이 서로 다른 사전 단어 오답 3개를 표시한다. + - 사용자 문구의 `Heat-T Medical Inc.`는 Heart 계열 명칭 문맥과 요청 표제를 함께 고려해 UI에는 정확히 `Designed by OZ at HearT-M Medical Inc.`로 표기한다. + - 전체 판매/의료 판단 서비스가 아니라 청각적 주의·식별 게임이다. 의료 효과·치료·진단을 주장하지 않는다. + - 오디오 난이도는 디지털 gain의 상대 dB로 계산하고 UI에 난이도와 상대 dB를 표시한다. + + ## 보안·라이선스 + + - 외부 CDN·분석·런타임 API 의존을 두지 않는다. + - 외부 음원을 다운로드할 경우 명시적 라이선스와 원본 URL이 필요하다. 불확실하면 직접 합성한다. + - 앱은 사용자 입력을 HTML로 삽입하지 않는다. + - 결과는 localStorage에 선택적으로 저장할 수 있으나 개인정보는 저장하지 않는다. + ❯ tests/release.test.ts:11:53 9| const credit = "Designed by OZ at Heat-T Medical Inc."; 10| for (const path of ["README.md", ".agent/TASK.md", ".agent/ACCEPTA… 11| expect.soft(readFileSync(path, "utf8"), path).toContain(credit); | ^ 12| } 13| for (const path of ["README.md", ...["docs", ".agent"].flatMap((di… ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: README.md: expected '# 단어를 맞춰라 · Find-Word v0.1\n\n소음 속에 숨…' not to contain 'HearT-M' - Expected + Received - HearT-M + # 단어를 맞춰라 · Find-Word v0.1 + + 소음 속에 숨은 한국어 단어를 듣고 찾는 반응형 웹 게임입니다. + + **Designed by OZ at HearT-M Medical Inc.** + + React + TypeScript strict + Vite로 만든 정적 SPA입니다. 로그인, 개인정보 저장, + 분석 도구, 외부 CDN, 런타임 TTS/API 요청이 없습니다. 청각적 주의·식별 게임이며 + 의료적 진단·치료나 효과를 주장하지 않습니다. + + ## 실행 + + Node.js **22.12 이상**의 지원되는 LTS 버전을 권장합니다. `package-lock.json`을 + 포함하므로 새 환경에서는 `npm ci`를 사용합니다. 이번 검증 환경은 Node 26.7.0입니다. + + ```bash + npm ci + npm run dev + ``` + + 표시되는 `http://127.0.0.1:5173`에서 사용합니다. 소리는 사용자가 **게임 시작 · 소리 듣기**를 + 누른 뒤에만 재생합니다. 낮은 음량부터 조절하고 헤드폰이나 스피커를 사용할 수 있습니다. + + ## 플레이 + + - 골고루 풀기, 보기에서 선택, 직접 입력 중 선택합니다. + - 8개 환경을 순환하거나 하나를 지정합니다. + - 에스의 안내가 배경에 묻힌 뒤 목표 문장이 나옵니다. 재생 후 질문에 답합니다. + - 10문제 동안 2문제마다 난이도가 올라갑니다: **SNR +6, +3, 0, −3, −6 dB**. + 양수는 음성이 배경보다 크고, 0은 같고, 음수는 더 작다는 뜻입니다. + - 다시 듣기는 감점하지 않습니다. 정답은 `단계 × 100점`, 연속 정답은 최대 40점의 보너스를 받습니다. + - 결과에서 점수·정답률·최고 연속 정답·단계별 결과를 확인하고 다시 시작할 수 있습니다. + - 새로고침하면 기록이 사라집니다. 결과를 브라우저나 서버에 저장하지 않습니다. + + 10개 유형 × 10개 단어의 100개 목표 문장을 저장했습니다. 각 세션은 유형과 + 단어를 섞어 출제하며 정답 위치도 섞습니다. 사전 표제어와 변형 규칙은 + [WORDS.md](docs/WORDS.md)에 설명했습니다. + + ## 검증과 정적 빌드 + + ```bash + npm run lint + npm test + npm run build + npm run preview + ``` + + `lint`는 ESLint와 TypeScript 검사, `test`는 unit/component/자산/빌드 검증기 + 테스트를 실행합니다. `build`는 `dist/`를 만들고 음성 101개가 원본 해시 그대로 + 포함되었는지 확인합니다. 빌드에는 음성 합성 도구가 필요하지 않습니다. + + 실제 오디오·360px/1280px 화면·키보드·접근성·오류 재시도 검증: + + ```bash + # Chromium이 없는 개발 환경에서 한 번만 실행합니다. + npx playwright install chromium + npm run test:browser + # 이미 설치된 Chrome을 사용할 때의 대안입니다. + CHROME_PATH=/usr/bin/google-chrome npm run test:browser + ``` + + 브라우저 테스트는 기존 `dist/`를 사용하므로 소스 변경 후 먼저 빌드하세요. + 실제 음성 재생이 끝나기를 기다려 약 1–2분이 걸립니다. 로컬 4173 포트를 + 테스트 중에만 열고 종료합니다. 스크린샷은 `.agent/evidence/`에 남습니다. + + ## 음성과 문서 + + 한국어 WAV는 직접 쓴 문장을 로컬 eSpeak NG로 합성했습니다. 환경음은 + 브라우저에서 고정 seed로 합성합니다. 외부 음원이나 신경망 음성 모델을 사용하지 않습니다. + 생성 방법, 도구 라이선스, 합성 음질의 한계는 [AUDIO_PROVENANCE.md](docs/AUDIO_PROVENANCE.md)를 + 참고하세요. 실제 음량은 출력 장치에 따라 달라지며 표기 dB는 디지털 상대값입니다. + + - [설계와 상태 전이](docs/ARCHITECTURE.md) + - [Neo를 위한 정적 배포 안내](docs/DEPLOYMENT.md) + - [RED/GREEN 및 실제 검증 결과](.agent/RESULT.md) + + 이번 작업에서는 서버 배포와 Git commit/push를 실행하지 않습니다. + ❯ tests/release.test.ts:17:57 15| )]) { 16| // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. 17| expect.soft(readFileSync(path, "utf8"), path).not.toContain(["He… | ^ 18| } 19| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/ACCEPTANCE.md: expected '# ACCEPTANCE — Find-Word v0.1\n\n## 제…' not to contain 'HearT-M' - Expected + Received - HearT-M + # ACCEPTANCE — Find-Word v0.1 + + ## 제품 + + - [ ] H1 또는 브랜드 영역에 `단어를 맞춰라` 표시 + - [ ] `Designed by OZ at HearT-M Medical Inc.` 표시 + - [ ] v0.1 표시 + - [ ] 게임 시작부터 결과·재시작까지 완결된 흐름 + - [ ] 객관식과 주관식 모두 실제 플레이 가능 + - [ ] 최소 10개 문제 유형이 코드·데이터·테스트로 확인됨 + - [ ] 최소 8개 배경 환경 프리셋 + - [ ] 최소 5개 dB 기반 난이도 + - [ ] 문제 조합이 고정 10문제를 넘어 확장 가능 + - [ ] 사전 단어 기반 오답과 변형 규칙 metadata + + ## 오디오 + + - [ ] Web Audio API로 배경과 목표 음성 gain을 분리 + - [ ] 난이도별 상대 dB가 수학적으로 검증됨 + - [ ] 첫 재생은 사용자 gesture 이후 실행 + - [ ] 재생 중 중복 시작·다음 문제 전환 race 방지 + - [ ] 오디오 실패 UI와 재시도 + - [ ] provenance 문서와 생성 절차 + - [ ] 런타임 외부 TTS/API 호출 없음 + + ## 품질 + + - [ ] strict TypeScript + - [ ] 직접 작성한 소스에 상세한 한국어 주석 + - [ ] unit/component 테스트에서 문제 유형 수, 선택지 무결성, 정답 판정, 주관식 정규화, 난이도 dB, 점수, 오디오 상태 검증 + - [ ] lint 통과 + - [ ] test 통과 + - [ ] production build 통과 + - [ ] 접근성 이름, 키보드 focus, reduced motion + - [ ] 360px 모바일과 1280px 데스크톱 레이아웃 + - [ ] secret scan 무검출 + + ## 문서 + + - [ ] README에 개발·테스트·빌드 방법 + - [ ] ARCHITECTURE에 데이터·상태·오디오 설계 + - [ ] AUDIO_PROVENANCE에 생성·라이선스·재생 방식 + - [ ] DEPLOYMENT에 Nginx static 배포와 SPA fallback + - [ ] `.agent/RESULT.md`에 실제 RED/GREEN 및 최종 검증 기록 + ❯ tests/release.test.ts:17:57 15| )]) { 16| // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. 17| expect.soft(readFileSync(path, "utf8"), path).not.toContain(["He… | ^ 18| } 19| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/CONTEXT.md: expected '# CONTEXT — Find-Word v0.1\n\n## 저장소와…' not to contain 'HearT-M' - Expected + Received - HearT-M + # CONTEXT — Find-Word v0.1 + + ## 저장소와 책임 경계 + + - canonical repository: `https://source.ibit.kr/OZ-Group/Find-Word` + - 작업 브랜치: `feat/v0.1` + - 현재 worktree: `/home/hermes/worktrees/find-word-v0.1` + - Codex는 이 worktree만 수정한다. + - Neo가 최종 diff 검토, 테스트, source push, Heart 서버 배포, Nginx/TLS/DNS 검증을 담당한다. + + ## 배포 예정 환경 + + - Ubuntu 24.04 + - Nginx 1.24 + - Node.js 22는 설치되어 있으나 프로덕션은 정적 `dist/`를 Nginx가 직접 제공하는 구성을 선호한다. + - 예정 도메인 `findword.heart-m.com`은 현재 Heart 서버 IP로 DNS 해석된다. + - 배포 파일·서비스·포트는 아직 존재하지 않는다. + + ## 제품 해석 + + - “4개의 단어”를 우선한다. 정답 1개와 변형 규칙이 서로 다른 사전 단어 오답 3개를 표시한다. + - 사용자 문구의 `Heat-T Medical Inc.`는 Heart 계열 명칭 문맥과 요청 표제를 함께 고려해 UI에는 정확히 `Designed by OZ at HearT-M Medical Inc.`로 표기한다. + - 전체 판매/의료 판단 서비스가 아니라 청각적 주의·식별 게임이다. 의료 효과·치료·진단을 주장하지 않는다. + - 오디오 난이도는 디지털 gain의 상대 dB로 계산하고 UI에 난이도와 상대 dB를 표시한다. + + ## 보안·라이선스 + + - 외부 CDN·분석·런타임 API 의존을 두지 않는다. + - 외부 음원을 다운로드할 경우 명시적 라이선스와 원본 URL이 필요하다. 불확실하면 직접 합성한다. + - 앱은 사용자 입력을 HTML로 삽입하지 않는다. + - 결과는 localStorage에 선택적으로 저장할 수 있으나 개인정보는 저장하지 않는다. + ❯ tests/release.test.ts:17:57 15| )]) { 16| // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. 17| expect.soft(readFileSync(path, "utf8"), path).not.toContain(["He… | ^ 18| } 19| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/RESULT.md: expected '# Find-Word v0.1 작업 결과\n\n작성일: 2026-1…' not to contain 'HearT-M' - Expected + Received - HearT-M + # Find-Word v0.1 작업 결과 + + 작성일: 2026-10-14 UTC. 작업 위치: `/home/hermes/worktrees/find-word-v0.1`. + 입력 문서 `.agent/CONTEXT.md`, `TASK.md`, `ACCEPTANCE.md`를 모두 읽고 적용했다. + 기존 입력 문서는 수정하지 않았다. + + ## 구현 결과 + + - `단어를 맞춰라`, `Designed by OZ at HearT-M Medical Inc.`, `v0.1` 표시. + - 반응형 시작·게임·결과 화면, 10문제 완주, 재시작, 다시 듣기, 중단, 진행률. + - 객관식·주관식·혼합 모드, 10유형 × 10단어 = 100개 저장 문장. + - 4지선다의 고유한 정답 1개·사전 단어 오답 3개, 변형 규칙과 예상 혼동 난이도. + - 8개 합성 배경 환경, +6/+3/0/−3/−6 dB의 5단계 상대 음량. + - 사용자 gesture에서 AudioContext 시작, 음성·배경 gain 분리, 안내 fade, + 재생 중 경합 방지, 취소된 로드의 늦은 완료 무시, 오류와 재시도. + - 점수·정답률·연속 정답·단계별 결과, 한글 NFC/공백/문장부호 정규화, + 한글 조합 중 Enter·재시작·재청취의 입력 상태 보호. + - 키보드/터치, 질문·피드백·결과 focus, ARIA, reduced motion, 색 대비. + - TypeScript strict, 직접 작성 소스 22개 파일의 한국어 설계 설명. + - 직접 합성 WAV 101개(10,864,582바이트), 결정론적 배경 합성, + 생성 도구 실측 버전·원문·파일 해시 manifest. + - 런타임 외부 CDN/TTS/API/분석 요청 없음. 개인정보 저장·로그인·추적 없음. + + 서버·DNS·인증정보에 접근하지 않았으며 **배포, Git commit, Git push를 실행하지 않았다.** + Playwright의 localhost preview는 개발 검증용으로만 실행했다. + + ## 실제 strict TDD 기록 + + 먼저 테스트와 타입 계약/비동작 stub을 작성하고 RED 명령의 종료 코드 **1**을 + 확인한 뒤 최소 동작을 구현했다. 각 GREEN은 종료 코드 **0**이다. 아래 원문 + 로그는 `evidence/`에 보관했다. 테스트를 건너뛰거나 실패 조건을 지워 GREEN으로 + 만들지 않았다. 초기 stub의 throw는 구현 전 상태를 표현한다. + + | 순서·핵심 동작 | 실제 RED 명령·결과 | 최소 구현 뒤 GREEN 명령·결과 | 원문 로그 | + | --- | --- | --- | --- | + | 01 데이터: 10유형·단어 무결성·8환경·5단계 | `npm test -- tests/content.test.ts`: **3 failed**, 빈 목록의 개수/환경 불일치 | 같은 명령: **3 passed** | [RED](evidence/01-content-red.log), [GREEN](evidence/01-content-green.log) | + | 02 출제·판정·게임 전이·점수 | `npm test -- tests/game.test.ts`: **6 failed**, 출제 미구현·정규화 불일치 | 같은 명령: **6 passed** | [RED](evidence/02-game-red.log), [GREEN](evidence/02-game-green.log) | + | 03 dB·RMS·결정론적 환경 합성 | `npm test -- tests/dsp.test.ts`: **3 failed**, gain=1·잘못된 RMS·무음 | 같은 명령: **3 passed** | [RED](evidence/03-dsp-red.log), [GREEN](evidence/03-dsp-green.log) | + | 04 오디오 gesture·gain·fade·중복 시작·취소·실패·음량 | `npm test -- tests/audio.test.ts`: **5 failed**, 오디오 미구현 | 같은 명령: **5 passed** | [RED](evidence/04-audio-red.log), [GREEN](evidence/04-audio-green.log) | + | 05 실제 저장 음성·원문·해시·포맷·크기 | `npm test -- tests/assets.test.ts`: **1 failed**, manifest 없음(ENOENT) | `npm run audio:generate` 후 `npm test -- tests/App.test.tsx tests/assets.test.ts`: **2 files / 7 passed** (음성 1 + UI 6) | [RED](evidence/05-assets-red.log), [GREEN](evidence/06-ui-05-assets-green.log) | + | 06 시작→재생→답변→결과→재시작·오류·포커스·보안 출력 | `npm test -- tests/App.test.tsx`: **6 failed**, 빈 화면에서 브랜드/시작/입력 역할 없음 | 위 결합 명령: **7 passed** 중 UI **6 passed** | [RED](evidence/06-ui-red.log), [GREEN](evidence/06-ui-05-assets-green.log) | + | 07 production 음성 검증기 | `npm test -- tests/build.test.ts`: **1 failed**, 자산 수 0 ≠ 101 | 같은 명령: **1 passed**, 정상 통과 및 파일 변조·누락 시 실제 실패 검증 | [RED](evidence/07-build-red.log), [GREEN](evidence/07-build-green.log) | + | 08 오답 변형별 혼동 난이도 | `npm test -- tests/content.test.ts`: **1 failed / 3 passed**, 순위 undefined | 같은 명령: **4 passed** | [RED](evidence/08-rule-red.log), [GREEN](evidence/08-rule-green.log) | + | 09 한글 조합 중 제출 방지 | `npm test -- tests/App.test.tsx`: **1 failed / 6 passed**, 조합 중 정답 피드백이 나타남 | 같은 명령: **7 passed** | [RED](evidence/09-ime-red.log), [GREEN](evidence/09-ime-green.log) | + | 10 한글 조합 도중 세션 재시작 | `npm test -- tests/App.test.tsx -t '한글 조합 도중'`: **1 failed / 7 skipped**, 새 세션 제출이 막힘 | 같은 명령: **1 passed / 7 skipped** | [RED](evidence/10-ime-reset-red.log), [GREEN](evidence/10-ime-reset-green.log) | + | 11 실측 음성 도구 버전 provenance | `npm test -- tests/assets.test.ts -t '도구 버전'`: **1 failed / 1 skipped**, 버전 정보 undefined | 생성기 수정·재생성 후 `npm test -- tests/assets.test.ts`: **2 passed** | [RED](evidence/11-provenance-red.log), [GREEN](evidence/11-provenance-green.log) | + | 12 한글 조합 도중 다시 듣기 | 위 10의 테스트에 재청취 전환을 추가한 동일 명령: **1 failed / 7 skipped**, 제출이 막힘 | 같은 명령: **1 passed / 7 skipped** | [RED](evidence/12-ime-replay-red.log), [GREEN](evidence/12-ime-replay-green.log) | + + `-t`의 skipped는 해당 회귀만 고른 실행 결과이며 최종 전체 실행에는 **skip 없음**. + + 중간 실패도 남겼다. + + - DSP의 첫 구현에서 음수 0(`-0`)이 경계 샘플로 남아 **1 failed / 2 passed**였다. + 경계값을 명시적으로 0으로 써 수정했다. [중간 로그](evidence/03-dsp-intermediate.log) + - UI 첫 구현에서 라디오의 보조 설명에 ‘직접 입력’이 중복되어 **1 failed / 6 passed**였다. + 각 라디오의 접근성 이름을 명확히 했다. [중간 로그](evidence/06-ui-intermediate.log) + - 오디오 최초 RED의 미구현 throw로 미처리 rejection 3개도 보고되었다. + 실제 엔진의 Promise 종료·정리 구현 뒤 GREEN과 최종 실행에서는 0개다. + - 첫 TypeScript 검사에서 TypedArray의 ArrayBuffer 타입과 테스트 mock의 순환 + 추론 오류를 발견했다. 반환 타입·mock 타입을 명시하여 strict 검사를 통과시켰다. + + ## 브라우저 통합 검증과 접근성 수정 + + 실제 Chrome, production `dist/`, 실제 WAV/Web Audio로 검증했다. 모의 재생으로 + 시간을 단축하지 않고 `ended`까지 기다렸다. 모든 접근성 검사는 axe 규칙을 + 비활성화하지 않고 실행했다. + + 명령: `CHROME_PATH=/usr/bin/google-chrome npm run test:browser` + + | 실행 | 실제 결과 | 발견·조치 | + | --- | --- | --- | + | [첫 실행](evidence/browser-first.log) | 2 passed / 2 failed | 시작 화면 글자 대비 3.75, 4.07 < 4.5. 두 색을 어둡게 조정 | + | [두 번째](evidence/browser-second.log) | 2 passed / 2 failed | 게임 화면 글자 대비 4.08, 4.41 < 4.5. 두 색을 조정 | + | [세 번째](evidence/browser-third.log) | 2 passed / 2 failed | 두 크기 모두 10문제 완주·100% 도달. 결과 설명 대비 4.38 < 4.5 수정 | + | [수정 후](evidence/browser-final.log) | **4 passed (1.2m)** | 시작·게임·결과 axe 위반 0, 두 크기 완주·재시작·404 후 재시도 통과 | + + 최종 빌드에서도 **4 passed (1.2m), 종료 코드 0**을 확인했다. reduced motion의 + 실제 계산된 `animation-name: none`까지 검증했다. + [최종 브라우저 로그](evidence/final-browser.log)에 실제 결과를 보관했다. + + 스크린샷: + + - [1280px 시작](evidence/desktop-home.png), [게임](evidence/desktop-game.png), [결과](evidence/desktop-results.png) + - [360px 시작](evidence/mobile-home.png), [게임](evidence/mobile-game.png), [결과](evidence/mobile-results.png) + + ## 최종 검증 + + 환경: Node.js **26.7.0**, npm **11.19.0**, Vite **8.3.4**, Vitest **5.0.4**. + 권장 최소 Node는 22.12이며 실제 Node 22에서의 별도 실행은 이번 작업에서 하지 않았다. + + | 명령/검증 | 실제 결과 | + | --- | --- | + | `npm run lint` | **PASS**, ESLint + `tsc --noEmit` 오류 0 · [로그](evidence/final-lint.log) | + | `npm test` | **7 files / 29 tests passed**, skip 0 · [로그](evidence/final-test.log) | + | `npm run build` | **PASS**, `dist/` 생성 및 음성 101개 원본 SHA-256 대조 · [로그](evidence/final-build.log) | + | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser` | **4 passed (1.2m)**, 360px/1280px 완주·오류 복구·접근성 위반 0·외부 요청 0·JS 예외 0 · [로그](evidence/final-browser.log) | + | `npm run audio:generate` 후 이전 manifest `files` 비교 | **101개 원문·바이트 수·SHA-256 전부 동일** · [로그](evidence/audio-reproduce.log) | + | `npm audit --audit-level=high` | **found 0 vulnerabilities** · [로그](evidence/final-audit.log) | + | 비밀값 정적 패턴 검사 | **34개 텍스트 파일, 0 matches** · [로그](evidence/final-security.log) | + | 직접 작성 소스의 한글 설명 | **22개 파일, 누락 0**; Python 생성기 AST 파싱 PASS | + | `git diff --check` | 종료 0. 초기 저장소가 비어 새 파일들은 아직 untracked 상태 | + + production 주요 번들: JS **244.26 kB**(gzip 76.95 kB), CSS **14.87 kB**(gzip 4.29 kB), + HTML **0.89 kB**. WAV는 별도로 10.86 MB이고 문항 단위로 로드한다. + + 비밀값 검사는 소스·테스트·스크립트·문서·루트 metadata·입력 `.agent/*.md`에 대해 + AWS access-key 형식, GitHub/OpenAI 토큰 형식, private-key 블록, 긴 password/secret/API-key + 문자열 할당을 Python 정규식으로 검사했다. 일치 값은 출력하지 않는다. + 의존성 디렉터리와 PCM 바이너리는 대상에서 제외했다. 이는 특정 형식의 정적 검사다. + + ## 변경 파일 + + 초기 저장소에는 입력 문서만 존재했다. 다음 파일을 새로 작성했다. 파일 전체 + 목록은 [changed-files.txt](evidence/changed-files.txt)에 남긴다(입력 문서는 보존 표시). + + - 앱: `src/App.tsx`, `src/main.tsx`, `src/styles.css`, `src/content.ts`, `src/game.ts`, + `src/audio/dsp.ts`, `src/audio/engine.ts`, `index.html`. + - 설정: `package.json`, `package-lock.json`, `tsconfig.json`, `vite.config.ts`, + `eslint.config.js`, `playwright.config.ts`, `.gitignore`. + - 생성/검증: `scripts/generate_audio.py`, `scripts/check-build.mjs`. + - 테스트: `tests/setup.ts`, `tests/content.test.ts`, `tests/game.test.ts`, + `tests/dsp.test.ts`, `tests/audio.test.ts`, `tests/assets.test.ts`, + `tests/App.test.tsx`, `tests/build.test.ts`, `tests/browser/game.spec.ts`. + - 자산: `public/audio/intro.wav`, 100개의 `-.wav`, + `public/audio/manifest.json`. + - 문서: `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`, + `docs/DEPLOYMENT.md`, `docs/WORDS.md`, `.agent/RESULT.md` 및 `evidence/` 로그·화면. + - 생성된 `dist/`, `node_modules/`, `.cache/`와 Playwright 임시 보고서는 Git 제외. + + ## 남은 위험·인수 사항 + + 1. 한국어 포먼트 합성은 자연스러운 사람의 발화보다 기계적이다. 자동 검증은 + 오디오의 존재·디코딩·전력·흐름을 확인했고, 실제 사용자의 발음 이해도와 청감 + 난이도를 평가하지 않았다. Neo의 직접 청취 및 사용자 평가가 필요하다. + 2. 환경음은 장소를 추상화한 합성 잡음이다. 실제 시장·카페의 사람 대화 녹음이 + 아니다. 상대 dB와 오답 혼동 순위는 의료·청력 측정치가 아니다. + 3. 모바일은 Chrome의 360px 터치 에뮬레이션으로 검증했다. 실물 iOS Safari, + Android 장치, Bluetooth 출력, 보조공학 장치의 전체 조합은 미검증이다. + 4. 사용자 기록은 메모리에만 있어 새로고침 시 사라진다. 오프라인 PWA는 아니며 + 음성의 첫 로드에는 정적 호스트 연결이 필요하다. 전체 오디오 용량은 약 10.9MB다. + 5. 정답은 정적 번들에 포함된다. 개발자 도구로 답을 알아낼 수 있어 보안 시험이나 + 순위 경쟁 서비스 용도가 아니다. + 6. 서버 배포·Nginx/TLS/DNS는 요청 범위 밖이며 실행하지 않았다. Neo가 별도 + 검토·커밋·배포하고 실제 도메인에서 MIME·캐시·오디오 404·HTTPS를 확인해야 한다. + + ## Neo 스크린샷 검수 후 출시 전 UI 수정 — 2026-10-14 UTC + + 앞의 기록과 기존 스크린샷은 보존하고 이번 작업의 RED/GREEN을 이어서 기록한다. + 기록 위치는 `.agent/TASK.md`와 `.agent/ACCEPTANCE.md`의 `.agent/RESULT.md` 지정을 따랐다. + + ### 변경 결과 + + - 한국어 `h1`·`h2`·`h3`에 `word-break: keep-all`을 적용했다. 실제 Chrome의 + 360px computed style이 `normal`인 RED부터 확인했고, 열 가지 질문 모두 + 360px/1280px에서 `keep-all`과 제목 내부 가로 넘침 여부를 검사한다. + - 게임과 결과 난이도를 `SNR +6 dB`, `SNR +3 dB`, `SNR 0 dB`, `SNR -3 dB`, + `SNR -6 dB`로 표시한다. 게임에서는 “음성이 배경보다 6 dB 커요.”, + “음성과 배경의 크기가 같아요.”, “음성이 배경보다 3 dB 작아요.”처럼 + 크기 관계를 풀어 설명한다. 모바일에서도 설명을 숨기지 않는다. + - 전체 음량은 시작 화면의 설정 카드 안에서 CTA보다 앞에, 게임에서는 환경 + 패널 안에 배치했다. `전체 음량` 접근성 이름, label 연결, 엔진 전달, + 화면 전환 시 값 유지와 결과 화면의 기존 음량 조절을 보존했다. + - 사운드 룸의 `01 / 08`은 실제 환경 수를 이용한 `8가지 환경`으로 바꿨다. + `플레이 방법`의 `#howto` 앵커는 유지하고 장식 화살표를 `↓`로 바꿨다. + CTA의 표시와 접근성 이름은 `게임 시작 · 소리 듣기`로 통일했다. + - 기존 색상·캐릭터·게임 흐름을 유지했다. README의 CTA와 SNR 설명도 맞췄다. + 출제·채점·음원·오디오 gain 구현은 수정하지 않았다. + + ### 추가 strict TDD 기록 + + 각 RED는 해당 구현을 바꾸기 **전에** 실행해 종료 코드 **1**을 확인했다. + 각 GREEN은 같은 테스트 계약으로 종료 코드 **0**을 확인했다. 브라우저는 + 각 단계의 소스를 먼저 빌드한 뒤 localhost production preview로 검사했다. + + | 순서·계약 | 실제 RED | 구현 후 GREEN | 증거 | + | --- | --- | --- | --- | + | 13 한국어 제목의 어절 보존 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --project=mobile`: **1 failed**. 시작·질문 heading의 computed `wordBreak`가 `normal` | 같은 명령: **1 passed**, `keep-all` 및 가로 넘침 없음 | [RED](evidence/13-heading-red.log), [GREEN](evidence/13-heading-green.log), [변경 전 빌드](evidence/13-heading-build-before.log), [변경 후 빌드](evidence/13-heading-build-after.log) | + | 14 다섯 SNR·크기 설명·결과 표시 | `npm test -- tests/App.test.tsx -t '다섯 난이도의 SNR'`: **1 failed / 8 skipped**. 열 문제의 SNR·크기 설명과 결과 표기 누락 | 같은 명령: **1 passed / 8 skipped** | [RED](evidence/14-snr-red.log), [GREEN](evidence/14-snr-green.log) | + | 14 모바일 난이도 설명 가시성 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --project=mobile --grep '난이도 설명'`: **1 failed**. `.db-note`가 hidden이고 새 설명·배지 없음 | 같은 명령: **1 passed** | [RED](evidence/14-snr-browser-red.log), [GREEN](evidence/14-snr-browser-green.log), [빌드](evidence/14-snr-build.log) | + | 15 음량의 소속·문서 순서·이름·값·엔진 | `npm test -- tests/App.test.tsx -t '전체 음량을 시작 CTA'`: **1 failed / 9 skipped**. 설정 카드·환경 패널 바깥이고 CTA 뒤에 있음 | 같은 명령: **1 passed / 9 skipped**. 이름·단일 컨트롤·20→60% 전환 및 엔진 전달 확인 | [RED](evidence/15-volume-red.log), [GREEN](evidence/15-volume-green.log) | + | 15 실제 음량 위치·키보드 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --grep '전체 음량'`: **2 failed**. 두 화면 크기에서 컨트롤이 카드 경계와 CTA 아래로 벗어남 | 같은 명령: **2 passed**, 카드 내부·CTA 이전 배치와 방향키·값 유지 확인 | [RED](evidence/15-volume-browser-red.log), [GREEN](evidence/15-volume-browser-green.log), [빌드](evidence/15-volume-build.log) | + | 16 환경 수·앵커 화살표·명확한 CTA | `npm test -- tests/App.test.tsx -t '시작 안내'`: **3 failed / 10 skipped**. `8가지 환경`·`↓`·새 CTA 없음 | 같은 명령: **3 passed / 10 skipped**, CTA 클릭에서 재생 1회 확인 | [RED](evidence/16-copy-red.log), [GREEN](evidence/16-copy-green.log) | + | 17 음량 이동 후 게임 카드·바닥글 여백 | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/ui.spec.ts --grep '전체 음량'`: **2 failed**. 스크린샷 검토에서 발견한 두 화면 크기의 카드·바닥글 간격 **0px < 24px** | 게임 하단에 32px 여백 적용 후 같은 명령: **2 passed** | [RED](evidence/17-spacing-red.log), [GREEN](evidence/17-spacing-green.log) | + + 필터 실행의 skipped는 해당 회귀만 선택한 결과다. 기존 테스트의 시작 버튼 + 선택자도 새 접근성 이름에 맞췄으며, 이전 이름까지 허용하는 정규식으로 + 검증을 완화하지 않았다. 최종 브라우저 테스트에는 음량에서 Tab을 누르면 + 시작 CTA로 이동하는 검사와 열 가지 질문의 computed style 검사도 포함한다. + + 전체 검증의 첫 타입 검사에서 새 Testing Library `getByRole` 호출에 지원되지 + 않는 `exact` 옵션을 발견했다. 문자열 `name` 자체가 정확 일치를 하므로 불필요한 + 옵션만 제거했다. [중간 lint](evidence/neo-intermediate-lint.log)와 + [중간 build](evidence/neo-intermediate-build.log)를 보존했다. + + 여백 회귀는 첫 전체 브라우저 실행 **10 passed (1.5m)** 뒤 스크린샷 검토에서 + 추가로 발견했다. [당시 로그](evidence/neo-before-spacing-browser.log)를 보존하고 + 17의 측정 계약을 먼저 실패시킨 뒤 수정했다. 전체 캡처 전에는 스크롤을 + 맨 위로 맞춰 화면 밖 fixed 건너뛰기 링크가 Chrome의 full-page 이미지에 + 들어오는 캡처 아티팩트를 방지했다. 앱의 초점 처리와 링크는 바꾸지 않았다. + + ### 이번 수정의 최종 전체 검증 + + 17의 여백 수정까지 포함한 최종 소스로 아래 명령을 모두 실행했다. 종료 코드는 + 전부 **0**이며 unit/component와 브라우저 전체 실행 모두 **skip 없음**이다. + + | 명령 | 최종 결과 | 로그 | + | --- | --- | --- | + | `npm run lint` | **PASS**, ESLint와 TypeScript 오류 0 | [lint](evidence/neo-final-lint.log) | + | `npm test` | **7 files / 34 tests passed** | [test](evidence/neo-final-test.log) | + | `npm run build` | **PASS**, production 빌드·음성 101개 무결성 확인 | [build](evidence/neo-final-build.log) | + | `CHROME_PATH=/usr/bin/google-chrome npm run test:browser` | **10 passed (1.4m)**, 실제 Chrome 360px/1280px | [browser](evidence/neo-final-browser.log) | + + 브라우저에서는 실제 WAV 재생을 기다려 혼합 모드 열 문제를 완주하고, + 재청취·100% 결과·재시작·404 후 재시도·초점 복귀를 검증했다. 두 화면 크기에서 + 시작·게임·결과 axe 위반 **0**, 외부 요청 **0**, JavaScript 예외 **0**이며, + reduced motion과 새 줄바꿈·SNR 설명·음량 위치·키보드 계약도 모두 통과했다. + 스크린샷을 직접 열어 시작 CTA 앞 음량, 게임 환경 패널의 SNR·음량, 어절을 + 보존한 질문, 게임 카드와 바닥글의 여백, 결과 표기를 확인했다. + + 최종 스크린샷(기존 파일과 구별되는 `neo-` 접두사): + + - 1280px: [시작](evidence/neo-desktop-home.png), [게임](evidence/neo-desktop-game.png), [결과](evidence/neo-desktop-results.png), [고정 질문](evidence/neo-desktop-heading.png) + - 360px: [시작](evidence/neo-mobile-home.png), [게임](evidence/neo-mobile-game.png), [결과](evidence/neo-mobile-results.png), [고정 질문](evidence/neo-mobile-heading.png) + + 이번 변경 파일은 `src/App.tsx`, `src/styles.css`, `tests/App.test.tsx`, + `tests/browser/game.spec.ts`, 신규 `tests/browser/ui.spec.ts`, `README.md`, + 이 `.agent/RESULT.md` 및 추가 evidence 로그·스크린샷이다. 의존성과 음원은 + 변경하지 않았다. `git diff --check`는 종료 0이며 저장소의 기존 untracked 상태는 + 유지했다. 변경 텍스트 7개 파일의 후행 공백 0과 RESULT의 evidence 링크 존재도 + 별도로 확인했다. + + 요청된 UI 수정에서 남은 실패는 없다. 실물 모바일·Safari 및 청감 평가의 기존 + 검증 범위 한계는 그대로다. 이번에도 로컬 preview 검증만 수행했으며, + **서버 배포, Git commit, Git push는 실행하지 않았다.** + ❯ tests/release.test.ts:17:57 15| )]) { 16| // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. 17| expect.soft(readFileSync(path, "utf8"), path).not.toContain(["He… | ^ 18| } 19| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/10]⎯ FAIL tests/release.test.ts > 릴리스 문서 계약 > 브랜드 문구를 사용자 원문 그대로 문서에도 보존한다 AssertionError: .agent/TASK.md: expected '# TASK — Find-Word v0.1\n\n## 역할\n\n당…' not to contain 'HearT-M' - Expected + Received - HearT-M + # TASK — Find-Word v0.1 + + ## 역할 + + 당신은 구현 담당 Codex다. 이 저장소에서만 작업하고 프로덕션 서버·DNS·인증정보에는 접근하지 않는다. Neo가 코드 리뷰·배포를 별도로 수행한다. + + ## 제품 + + - 서비스명: **단어를 맞춰라** + - 표기: **Designed by OZ at HearT-M Medical Inc.** + - 버전: **0.1** + - 배포 예정 도메인: `findword.heart-m.com` + - 언어: 사용자 UI와 상세 소스 주석은 한국어 + + ## 목표 + + 여러 환경 소음 속에 작은 음량으로 섞인 특정 한국어 단어나 문장을 듣고 질문에 답하는 웹 게임을 만든다. 쉬운 단계부터 소음 대비 키 문장의 dB 차이를 늘려 난이도를 높인다. 객관식과 주관식을 모두 지원한다. + + ## 핵심 시나리오 + + 1. S 캐릭터가 “으하하하~~ 나는 에스다. 널 위한 행운의 단어를 불러주마, 잘 찾아보거라.”라고 안내한다. + 2. 웃음과 안내 음성이 배경 소음 속으로 점차 묻힌다. + 3. “오늘의 내 행운은 ‘사과’에 있다.” 같은 키 문장을 재생한다. + 4. 재생이 끝난 뒤 질문과 네 개의 선택지를 표시한다. + 5. 선택지는 정답 1개와 사전에 존재하는 유사어 오답 3개로 구성한다. + 6. 오답은 자음·모음 변형 규칙의 서로 다른 난이도를 반영하고, 데이터에 변형 종류를 명시한다. + + ## 기능 요구사항 + + - 반응형 SPA, 데스크톱·모바일 키보드/터치 지원 + - 시작 화면, 게임 화면, 결과 화면, 다시 듣기, 다음 문제, 전체 진행률 + - 사용자가 브라우저 정책상 필요한 “소리 시작” 조작을 직접 누르도록 설계 + - Web Audio API로 보이스와 배경을 믹싱하고 목표 음성 gain을 dB 기준으로 적용 + - 최소 5단계 난이도. 단계가 높아질수록 목표 문장과 배경의 상대 dB가 낮아짐 + - 최소 10개 문제 유형. 객관식·주관식을 포함하고 데이터 조합으로 실제 문제가 더 늘어남 + - 최소 8개 배경 환경: 길거리, 쇼핑몰, 극장, 백화점, 시장, 지하철, 카페, 비 오는 거리 + - 기본 패턴 문장, 시험 문장, 질문, 답, 오답 후보, 배경 프리셋을 저장된 데이터로 분리 + - 사전 단어만 사용하는 4지선다 데이터와 각 오답의 변형 규칙 메타데이터 + - 점수, 정답률, 연속 정답, 난이도별 결과 + - 접근성: aria, 명확한 focus, 키보드 조작, reduced motion, 색 대비 + - 오디오가 없거나 로드 실패해도 사용자에게 명확한 오류와 재시도 제공 + - 개인 데이터 수집·로그인·추적기 없음 + - 버전 0.1을 UI와 package metadata에 표시 + + ## 오디오 요구사항 + + - 라이선스 불명확한 외부 자산을 사용하지 않는다. + - 직접 생성한 배경 소음·효과음을 저장하거나, 브라우저에서 결정론적으로 합성하는 프리셋을 사용한다. + - 한국어 목표 음성은 가능하면 직접 생성한 오디오 파일로 저장한다. 네트워크 TTS를 사용했다면 생성 스크립트·출처·생성 절차를 문서화하고 앱은 런타임에 외부 TTS를 호출하지 않는다. + - 모든 자산의 provenance를 `docs/AUDIO_PROVENANCE.md`에 기록한다. + - 오디오 파일을 커밋할 경우 크기를 합리적으로 제한하고 build 결과에 포함되는지 테스트한다. + + ## 개발 규칙 + + - strict TDD: 각 동작은 실패하는 테스트를 먼저 작성하고 실제 RED→GREEN 근거를 `.agent/RESULT.md`에 기록한다. + - TypeScript strict mode를 사용한다. + - 모든 직접 작성 소스 코드에 상세한 한국어 주석을 단다. 자명한 문법 줄마다 반복 주석을 붙이지 말고 알고리즘, 상태, 오디오, 난이도, 접근성, 보안 의도를 설명한다. + - generated lockfile과 기계 생성 바이너리는 주석 대상이 아니다. + - 비밀값, 서버 자격증명, API 키를 저장하지 않는다. + - 서버 배포는 하지 않는다. + - Git 커밋은 하지 않는다. Neo가 검증 후 커밋한다. + + ## 권장 기술 + + - Vite + React + TypeScript + - Vitest + Testing Library + - 순수 CSS 또는 가벼운 CSS 구조 + - 정적 빌드(`dist/`)로 Nginx 배포 가능 + + ## 산출물 + + - 동작하는 v0.1 소스 + - 최소 10개 문제 유형과 조합 가능한 데이터 + - 오디오 생성·재생 구조 + - unit/component 테스트 + - `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`, `docs/DEPLOYMENT.md` + - `.agent/RESULT.md`: 변경 파일, RED/GREEN 명령과 실제 결과, 전체 검증 결과, 미해결 위험 + ❯ tests/release.test.ts:17:57 15| )]) { 16| // 잘못된 표기를 다시 권장하는 문서가 남아 있으면 다음 편집 때 회귀한다. 17| expect.soft(readFileSync(path, "utf8"), path).not.toContain(["He… | ^ 18| } 19| }); ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/10]⎯ Test Files 2 failed (2) Tests 2 failed | 12 skipped (14) Start at 05:50:05 Duration 1.18s (environment 50%, tests 22%, setup 17%, transform 6%, import 4%, worker 1%)