Skip to content

feat: phrase-word dictionary linking for conversations - #27

Merged
gook-lab merged 3 commits into
mainfrom
feat/phrase-word-links
Sep 28, 2026
Merged

gook-lab merged 3 commits into
mainfrom
feat/phrase-word-links

Conversation

@gook-lab

@gook-lab gook-lab commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implement word-to-dictionary matching in conversation phrases with interactive UI for learning.

Features:

  • Longest-match algorithm (matchPhraseWords) to find dictionary words in unseparated Japanese text
  • LinkedPhrase component with clickable word chips and bottom sheet details
  • Full integration with conversation memo store for "Add to memo" flow + mission progress tracking
  • Offline-ready using bundled dictionary (WORDS)
  • Accessibility: Keyboard navigation (Esc to close), focus management, screen reader labels

Verification

  • Unit tests: 122 tests pass (phraseWordMatcher + edge cases)
  • Build: Production build succeeds with no TypeScript errors
  • Lint: ESLint clean (no violations)
  • Types: Full type safety with explicit Segment type
  • Regression: categoryId passed correctly, mission progress incremented, particle detection uses phrase.words
  • Accessibility: Esc closes bottom sheet, focus moves to close button, aria-labels on word chips

Fixes in review

  • categoryId: LinkedPhrase now accepts categoryId prop to store correct category in memo (was empty '')
  • Mission progress: bumpMissionProgress('conversation', 1) called when adding word (was missing)
  • Particle detection: Uses phrase.words[].isParticle instead of 1-char heuristic (fixes false positives on 1-char nouns)
  • Accessibility: Added Esc key handler, focus management (saves/restores), aria-label with meanings
  • Messaging: Consistent toast levels (info for removals), Korean "조사" term

Limitations

  • Inflection variants (e.g., "飲みます" vs dictionary "飲む") not handled — scope for future conjugation support
  • Particles (を, に, は) show in UI but are disabled for memo via phrase.words.isParticle flag

🤖 Generated with Claude Code

Implement word-to-dictionary matching in conversation phrases:

- Add phraseWordMatcher.ts: Longest-match algorithm for Japanese text
  * buildWordIndex(): Create Map-based dictionary index (kanji/hiragana keys)
  * matchPhraseWords(): Match dictionary words in phrase without spacing
  * segmentPhrasedText(): Split text into linked + plain segments
  * Full test suite covering edge cases (overlaps, consecutive matches, skips)

- Add LinkedPhrase component: Interactive word linking UI
  * Display dictionary words as clickable chips with romaji
  * Bottom sheet with word details (kanji, hiragana, romaji, meaning, level, example)
  * "Add to memo" button with immediate store update
  * "Already saved" state indication with Check icon

- Update ConversationDetailPage: Replace ClickablePhrase with LinkedPhrase
  * Full integration with Zustand conversation memo store
  * Offline-ready: Uses bundled WORDS dictionary
  * Toast feedback for add/remove actions

Test results: 122/122 passed
Build: ✓ (no type errors, production ready)
Lint: ✓ (ESLint clean)

Scope: Conversation phrases without inflection variants (future work).
수정 사항:
- categoryId를 props로 받아 메모 저장 시 올바른 카테고리 기록
- 단어 추가 시 bumpMissionProgress('conversation', 1) 호출해 미션 진행도 업데이트
- phrase.words의 isParticle 필드 활용해 입자 판정 정확도 개선
  (기존: 1글자 비한자 추측, 새: 원본 표현 데이터 기준)
- 접근성: Esc 키로 바텀시트 닫기, 포커스 관리, aria-label 추가
- 문구 개선: "조사는 단어장에 담을 수 없어요" (기존 "입자는")
- 제거 시 toast 레벨 일관성: info (success 대신)

모든 테스트 122개, 린트, 빌드 통과.
렌더마다 matchPhraseWithWords를 호출하면 buildWordIndex를
재생성해 불필요한 계산이 반복된다.
phrase.japanese가 변경될 때만 재계산하도록 최적화.

모든 테스트 122개, 린트, 빌드 통과.
@gook-lab
gook-lab merged commit 4c891d7 into main Sep 28, 2026
4 checks passed
@gook-lab
gook-lab deleted the feat/phrase-word-links branch September 28, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant