feat: release Find-Word v0.1
This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
# 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 Heat-T 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.22.2 이상**으로 바로잡았다.
|
||||
실제 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개의 `<pattern>-<word>.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는 실행하지 않았다.**
|
||||
|
||||
|
||||
## 독립 릴리스 리뷰·Neo literal-preservation 재검토 반영 — 2026-10-14 UTC
|
||||
|
||||
이번 사용자 지시를 이전 입력 문서의 브랜드 해석보다 우선 적용했다.
|
||||
정확한 표기는 **Designed by OZ at Heat-T Medical Inc.**다. UI, README,
|
||||
`.agent/CONTEXT.md`·`TASK.md`·`ACCEPTANCE.md`의 요구사항과 이 문서의 표기도
|
||||
일치시켰다. 이전 실행의 raw 로그와 스크린샷은 당시 상태의 증거이므로 보존한다.
|
||||
아래 검증이 현재 릴리스 상태이며 앞의 결과·바이트 수·테스트 수는 당시 기록이다.
|
||||
|
||||
### 변경 내용
|
||||
|
||||
- 브랜드 원문을 고정한 컴포넌트·문서·실제 브라우저 회귀 검증을 추가했다.
|
||||
회사명을 문맥으로 추측해 교정하던 입력 문서 규칙도 제거했다.
|
||||
- 프로젝트 자체는 `private: true`, `license: UNLICENSED`, root `LICENSE`의
|
||||
private / all-rights-reserved로 명시했다. package repository·homepage와
|
||||
README·배포 문서의 canonical URL은 `https://source.ibit.kr/OZ-Group/Find-Word`다.
|
||||
- root와 public의 `THIRD_PARTY_NOTICES.md`에 잠긴 React 19.3.0, ReactDOM 19.3.0,
|
||||
scheduler 0.28.0의 MIT 전문을 포함했다. 빌드 검증기는 전이 의존성을 포함한
|
||||
런타임 목록을 lockfile에서 찾고, 설치된 LICENSE 전문·버전과 root/public/dist의
|
||||
고지 사본을 대조한다. notice의 누락·축약을 실제 실패시키는 테스트도 추가했다.
|
||||
- `Pattern.mechanic`에 10개 고유 연산을 지정했다. 직접 목표 식별, 암호 구별,
|
||||
잊고 기억하기, 첫째·마지막·가운데, 반복, 정정, 신호 조건, 오른쪽 표지를 각각
|
||||
다르게 듣고 판단하게 했다. `renderSentence`가 실제 보기의 오답도 문장에
|
||||
삽입하며 UI와 음성 생성기가 같은 원문을 사용한다. 문장 계약 검증은 100개
|
||||
조합에서 순서·반복 횟수·취소·조건·방향 단서와 질문 의도를 확인한다.
|
||||
- 101개 WAV와 manifest를 재생성했다. 합계 **20,497,222바이트**, 16kHz mono
|
||||
PCM이며 기존 20MiB 제한을 유지했다. 방향은 말로 전달하는 의미 단서다.
|
||||
- package와 lockfile의 Node 최소 버전은 `>=22.22.2`로 맞췄다. README·배포
|
||||
문서에 잠긴 jsdom 계열의 24.x 최소 24.15.0 요구도 함께 설명했다.
|
||||
- Playwright는 `chromiumSandbox: true`가 기본이다. 미설정·빈 값·`0`·`false`는
|
||||
sandbox를 유지하며, `PLAYWRIGHT_NO_SANDBOX=1`일 때만 끈다. Chrome 경로
|
||||
지정은 sandbox와 독립적이다. 새 브라우저 스크린샷은 `release-` 접두사로 저장한다.
|
||||
|
||||
### 실제 strict TDD
|
||||
|
||||
아래 RED는 구현을 바꾸기 전에 실제 실행했고 모두 종료 코드 **1**이었다.
|
||||
GREEN은 같은 계약으로 모두 종료 코드 **0**이었다. 브라우저 전달 계약은
|
||||
기존 dist에서 먼저 실패시킨 후 새 소스를 빌드했다. 테스트 실패를 제거하거나
|
||||
허용치를 완화하지 않았다. `-t`로 지정한 실행의 skip은 선택 실행에 한정된다.
|
||||
|
||||
| 순서·계약 | RED 명령·관찰 | GREEN 명령·관찰 | 원문 로그 |
|
||||
| --- | --- | --- | --- |
|
||||
| 18 literal 보존 | `npm test -- tests/App.test.tsx tests/release.test.ts -t '브랜드'`: **2 failed**, UI·문서의 기존 표기 불일치 | 같은 명령: **2 passed / 12 skipped** | [RED](evidence/18-literal-red.log), [GREEN](evidence/18-literal-green.log) |
|
||||
| 19 프로젝트 metadata·MIT 고지·배포 검증기 | `npm test -- tests/release.test.ts tests/build.test.ts`: **4 failed / 2 passed**, metadata·LICENSE·고지 누락 및 검증기의 누락/축약 미검출 | 같은 명령: **6 passed** | [RED](evidence/19-notices-red.log), [GREEN](evidence/19-notices-green.log) |
|
||||
| 20 고유 청취 연산·문장 계약·출제 | `npm test -- tests/content.test.ts tests/game.test.ts`: **12 failed / 10 passed**, mechanic 없음·경쟁 단어/순서/의미 계약 미충족 | 같은 명령: **22 passed** | [RED](evidence/20-mechanics-red.log), [GREEN](evidence/20-mechanics-green.log) |
|
||||
| 20 변경 원문과 WAV 대응 | `npm test -- tests/assets.test.ts`: **1 failed / 1 passed**, 기존 manifest 원문이 새 문장과 다름 | `npm run audio:generate` 후 `npm test -- tests/content.test.ts tests/game.test.ts tests/assets.test.ts`: **24 passed**, 문장·해시·PCM·비무음·크기·피크 검증 | [RED](evidence/20-audio-red.log), [생성](evidence/20-audio-generate.log), [GREEN](evidence/20-audio-green.log) |
|
||||
| 21 Node 최소 버전 | `npm test -- tests/release.test.ts -t 'Node 최소'`: **1 failed / 3 skipped**, package/lock/docs 값이 잠금 의존성 요구와 다름 | 같은 명령: **1 passed / 3 skipped** | [RED](evidence/21-node-red.log), [GREEN](evidence/21-node-green.log) |
|
||||
| 22 sandbox 기본값과 명시적 opt-in | `npm test -- tests/playwright-config.test.ts`: **5 failed**, 기본 sandbox 비활성화·무조건 적용된 비활성화 플래그 | 같은 명령: **5 passed**, 각 환경변수 값을 별도 프로세스로 검증 | [RED](evidence/22-sandbox-red.log), [GREEN](evidence/22-sandbox-green.log) |
|
||||
| 23 실제 HTTP 배포 응답 | `env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome npm run test:browser -- tests/browser/release.spec.ts --project=mobile`: **1 failed**, 이전 dist의 크레딧 불일치와 notice 대신 SPA HTML 응답 | 새 build 후 전체 브라우저 실행: **12 passed (2.2m)**, 두 화면 크기의 전달 계약 포함 | [RED](evidence/23-delivery-browser-red.log), [전체 GREEN](evidence/release-final-browser.log) |
|
||||
|
||||
중간 lint에서 새 sandbox 테스트의 env 객체 타입이 좁게 추론되어 TypeScript
|
||||
오류 2개가 났다. `NodeJS.ProcessEnv`를 명시하여 수정했다.
|
||||
[실패 원문](evidence/release-intermediate-lint.log)을 보존했다.
|
||||
|
||||
|
||||
### 최종 검증 결과
|
||||
|
||||
환경: Node.js **26.7.0**, npm **11.19.0**, 시스템 Chrome
|
||||
`/usr/bin/google-chrome`. 최소 Node 22.22.2에서 별도로 실행한 결과는 아니다.
|
||||
새 소스의 production build를 완료한 뒤 다음 명령으로 브라우저 전체를 실행했다.
|
||||
|
||||
```bash
|
||||
env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome DEBUG=pw:browser npm run test:browser
|
||||
```
|
||||
|
||||
| 검증 | 실제 결과·증거 |
|
||||
| --- | --- |
|
||||
| `npm run lint` | **PASS**, ESLint + TypeScript strict 오류 0 · [로그](evidence/release-final-lint.log) |
|
||||
| `npm test` | **9 files / 57 tests passed**, skip 0 · [로그](evidence/release-final-test.log) |
|
||||
| `npm run build` | **PASS**, WAV 101개 SHA-256 및 root/public/dist 런타임 MIT notice 전문 대조 · [로그](evidence/release-final-build.log) |
|
||||
| 위 Chrome 전체 명령 | **12 passed (2.2m)**, 360px/1280px, 실제 재생·10문제 완주·재청취·404 복구·키보드·SNR·레이아웃·axe·크레딧·MIT HTTP 응답 · [로그](evidence/release-final-browser.log) |
|
||||
| sandbox 실제 실행 인자 | Chrome 4회 실행 모두 `--no-sandbox` 없음. opt-in 환경변수를 지운 상태로 실행 · [검증](evidence/release-integrity.log) |
|
||||
| `npm audit --audit-level=high` | **found 0 vulnerabilities**, 개발 의존성 포함 · [로그](evidence/release-final-audit.log) |
|
||||
| `npm ls --omit=dev` | React / ReactDOM 19.3.0, 해석 오류 없음 |
|
||||
| 브랜드·기록·정적 산출물 최종 확인 | 현재 UI·테스트·문서에 대체 브랜드 표기 없음, evidence 링크 존재, 101 WAV/notice 검증 통과 · [로그](evidence/release-integrity.log) |
|
||||
| `git diff --check` | 종료 코드 0. 시작 시 이미 소스 전체가 untracked였으므로 변경 텍스트의 후행 공백도 별도 검사 |
|
||||
|
||||
브라우저의 페이지 JS 예외·외부 페이지 요청·axe 위반은 모두 0이었다.
|
||||
Chrome 자체 진단 출력과 앱 페이지의 오류·네트워크 검증은 구별했다.
|
||||
실제 360px 게임 스크린샷에서도 올바른 브랜드, 질문 줄바꿈, 보기·음량·SNR 표시를 확인했다.
|
||||
|
||||
- [360px 시작](evidence/release-mobile-home.png), [게임](evidence/release-mobile-game.png), [결과](evidence/release-mobile-results.png)
|
||||
- [1280px 시작](evidence/release-desktop-home.png), [게임](evidence/release-desktop-game.png), [결과](evidence/release-desktop-results.png)
|
||||
|
||||
### 이번 변경 파일과 범위
|
||||
|
||||
- 앱·출제: `src/App.tsx`, `src/content.ts`, `src/game.ts`.
|
||||
- 라이선스·metadata·브라우저 설정: `LICENSE`, `THIRD_PARTY_NOTICES.md`,
|
||||
`public/THIRD_PARTY_NOTICES.md`, `package.json`, `package-lock.json`, `playwright.config.ts`.
|
||||
의존성 버전과 integrity는 바꾸지 않았다.
|
||||
- 생성·빌드: `scripts/generate_audio.py`, `scripts/check-build.mjs`,
|
||||
`public/audio/`의 101 WAV와 manifest. 안내 WAV도 다시 생성했으며 원문은 유지했다.
|
||||
- 회귀 검증: `tests/App.test.tsx`, `tests/content.test.ts`, `tests/game.test.ts`,
|
||||
`tests/assets.test.ts`, `tests/build.test.ts`, 새 `tests/release.test.ts`,
|
||||
`tests/playwright-config.test.ts`, `tests/browser/release.spec.ts`.
|
||||
기존 `tests/browser/game.spec.ts`·`ui.spec.ts`는 새 스크린샷 이름을 사용한다.
|
||||
- 문서·증거: `README.md`, `docs/ARCHITECTURE.md`, `docs/AUDIO_PROVENANCE.md`,
|
||||
`docs/DEPLOYMENT.md`, `.agent/CONTEXT.md`, `TASK.md`, `ACCEPTANCE.md`,
|
||||
이 `RESULT.md` 및 새 evidence 파일.
|
||||
|
||||
요청된 수정·자동 검증에 남은 실패는 없다. 한국어 합성 발음의 청감과 실물
|
||||
모바일 Safari는 기존과 같이 별도 인수 대상이다. WAV 용량은 약 20.5MB로 늘었고
|
||||
질문별로 로드한다. **서버 배포, Git commit, Git push는 실행하지 않았다.**
|
||||
|
||||
## Neo canonical 오디오 재시도 assertion 회귀 수정 — 2026-10-14 UTC
|
||||
|
||||
### RED 근거와 재현 조건
|
||||
|
||||
- **Neo가 보고한 기존 canonical 실패를 이번 RED 근거로 사용한다.**
|
||||
전체 `npm run test:browser`(12 tests, workers=2)에서 desktop의
|
||||
`오디오 HTTP 실패를 화면에 표시하고 재시도한다`가 재시도 후
|
||||
`getByRole("group", { name: "답 선택" })`를 global expect timeout
|
||||
**20,000ms** 안에 보지 못해 1회 실패했다.
|
||||
- 같은 수정 전 테스트를 desktop 단독 `--workers=1 --repeat-each=3`으로
|
||||
실행하면 **16.3~17.3초에 3회 모두 통과**했다는 Neo의 보고다.
|
||||
실패는 병렬 전체 실행에서 간헐적으로 발생했다. 이는 이번 로컬 실행의
|
||||
원문 로그가 아니며, 새로 실패를 재현했다고 주장하지 않는다.
|
||||
- 사용자 메시지에 첨부된 **12 passed (2.2m)** 출력은 성공 실행 기록이다.
|
||||
이를 실패 로그로 취급하지 않으며 기존 evidence도 보존한다.
|
||||
|
||||
### 원인 추적과 최소 수정 범위
|
||||
|
||||
`systematic-debugging` 절차로 실패한 assertion에서 상태 전이를 역추적했다.
|
||||
404가 난 최초 재생은 `src/App.tsx`의 `introDone`을 설정하지 않는다.
|
||||
재시도는 안내와 목표 WAV를 다시 로드·디코드하고, `src/audio/engine.ts`의
|
||||
목표 소스 `onended` → `play()` 완료 → `ENDED` → `answer` 상태가 된 뒤에야
|
||||
답 선택 그룹을 표시한다. 출제 seed는 `Date.now()`여서 실행별 목표 길이도
|
||||
달라진다. 정상 단독 실행과 병렬 실패의 차이는 이 대기 경로의 시간 여유다.
|
||||
|
||||
현재 101개 WAV를 Python `wave`의 프레임 수 / sample rate로 실측했다.
|
||||
안내는 **8.1205초**, 최장 목표 `message-market.wav`는 **10.1725초**다.
|
||||
엔진의 시작 여유 **0.05초**와 안내→목표 간격 **0.35초**를 더한 최대
|
||||
재생 일정은 **18.693초**다. 기존 20초 assertion에는 HTTP 로드·디코드·배경
|
||||
합성·브라우저 스케줄링·DOM 반영·assertion 관찰을 위한 여유가 **1.307초**만
|
||||
남는다. Neo의 병렬 실패와 단독 성공은 이 좁은 assertion 예산에 부합한다.
|
||||
당시 trace가 없어 실패 1회의 세부 지연 분포는 확인할 수 없다.
|
||||
[실측과 제어 흐름 기록](evidence/24-retry-audio-budget.log)
|
||||
|
||||
수정은 `tests/browser/game.spec.ts`의 **재시도 완료 `toBeVisible` 하나에만
|
||||
30,000ms 상한**을 지정한다. 최대 재생 일정 이후 **11.307초**의 여유를 두며
|
||||
조건이 충족되면 즉시 통과한다. 제품의 준비 제한 15초·재생 종료 후 5초
|
||||
watchdog, global expect 20초, test timeout 120초, workers=2는 유지한다.
|
||||
sleep, 자동 재시도, 오디오 mock, 테스트 skip, 제품 코드 변경은 추가하지 않는다.
|
||||
|
||||
### 수정 후 검증
|
||||
|
||||
환경: Node.js **26.7.0**, npm **11.19.0**, 시스템 Chrome
|
||||
`/usr/bin/google-chrome`. 새 production build에서 실제 WAV를 사용했다.
|
||||
브라우저 실행은 `env -u PLAYWRIGHT_NO_SANDBOX CHROME_PATH=/usr/bin/google-chrome`
|
||||
접두사로 기본 sandbox를 유지했다. 아래 명령은 모두 종료 코드 **0**이다.
|
||||
|
||||
| 검증 명령 | 실제 GREEN 결과 | 로그 |
|
||||
| --- | --- | --- |
|
||||
| `npm run lint` | **PASS**, ESLint + TypeScript 오류 0 | [lint](evidence/24-retry-final-lint.log) |
|
||||
| `npm test` | **9 files / 57 tests passed**, skip 0 | [test](evidence/24-retry-final-test.log) |
|
||||
| `npm run build` | **PASS**, WAV 101개와 런타임 notice 전문 확인 | [build](evidence/24-retry-final-build.log) |
|
||||
| `npm run test:browser -- tests/browser/game.spec.ts --project=desktop --workers=1 --repeat-each=3 --grep '오디오 HTTP 실패'` | **3 passed (52.9s)**, 각 **17.7s / 15.5s / 16.8s** | [targeted](evidence/24-retry-targeted-green.log) |
|
||||
| `npm run test:browser` | **12 passed (2.2m)**, desktop/mobile, workers=2, skip 0 | [browser](evidence/24-retry-final-browser.log) |
|
||||
|
||||
전체 병렬 실행의 재시도 테스트는 desktop **19.9초**, mobile **14.8초**에
|
||||
통과했다. 이 수치는 Playwright가 보고한 **테스트 전체 시간**이며 assertion
|
||||
자체의 대기 시간으로 해석하지 않는다. 실제 404 오류 표시→재시도→답 선택→
|
||||
처음으로 이동 후 초점 복귀 계약을 유지한 채 통과했다. 기존의 실제 오디오
|
||||
10문제 완주, 레이아웃·접근성·외부 요청 없음, 릴리스·UI 검사도 모두 통과했다.
|
||||
|
||||
이번 변경은 `tests/browser/game.spec.ts`의 assertion 한 곳과 이 기록,
|
||||
`evidence/24-retry-*.log`에 한정한다. 시작 시 소스 전체가 untracked였으므로
|
||||
작업 전 SHA-256 snapshot과 비교해 `src/`, 음원, 의존성, Playwright 설정 및
|
||||
기존 evidence가 그대로임을 확인했다. 전체 브라우저 실행이 다시 생성한
|
||||
기존 `release-*.png`는 작업 전 사본으로 복원해 과거 검증 증거를 보존했다.
|
||||
`git diff --check`, 수정 텍스트의 후행 공백 검사와 evidence 링크 확인도
|
||||
통과했다. [변경 범위와 무결성 확인](evidence/24-retry-integrity.log)
|
||||
|
||||
요청한 반복·전체 검증은 모두 GREEN이다. **Git commit, push, 배포는 실행하지 않았다.**
|
||||
Reference in New Issue
Block a user