Previously the chapter reader showed vocab tables as a flat list of OCR
lines — because Vision reads columns top-to-bottom, the Spanish column
appeared as one block followed by the English column, making pairings
illegible.
Now every vocab table renders as a 2-column grid with Spanish on the
left and English on the right. Supporting changes:
- New ocr_all_vocab.swift: bounding-box OCR over all 931 vocab images,
cluster lines into rows by Y-coordinate, split rows by largest X-gap,
detect 2- / 3- / 4-column layouts automatically. ~2800 pairs extracted
this pass vs ~1100 from the old block-alternation heuristic.
- merge_pdf_into_book.py now prefers bounding-box pairs when present,
falls back to the heuristic, embeds the resulting pairs as
vocab_table.cards in book.json.
- DataLoader passes cards through to TextbookBlock on seed.
- TextbookChapterView renders cards via SwiftUI Grid (2 cols).
- fix_vocab.py quarantine rule relaxed — only mis-pairs where both
sides are clearly the same language are removed. "unknown" sides
stay (bbox pipeline already oriented them correctly).
Textbook card count jumps from 1044 → 3118 active pairs.
textbookDataVersion bumped to 9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New features:
- Offline Dictionary: reverse index of 175K verb forms + 200 common
words, cached to disk, powers instant word lookups in Stories
- Vocab SRS Review: spaced repetition for course vocabulary cards
with due count badge and Again/Hard/Good/Easy rating
- Cloze Practice: fill-in-the-blank using SentenceQuizEngine with
distractor generation from vocabulary pool
- Grammar Exercises: interactive quizzes for 5 grammar topics
(ser/estar, por/para, preterite/imperfect, subjunctive, personal a)
with "Practice This" button on grammar note detail
- Listening Practice: listen-and-type + pronunciation check modes
using Speech framework with word-by-word match scoring
- Conversational Practice: AI chat partner via Foundation Models
with 10 scenario types, saved to cloud container
Other changes:
- Add Conversation model to SharedModels and cloud container
- Add Info.plist keys for speech recognition and microphone
- Skip speech auth on simulator to prevent crash
- Fix preparing data screen to only show during seed/migration
- Extract courseDataVersion to static property on DataLoader
- Add "How Features Work" reference page in Settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generate one-paragraph Spanish stories on-device using Foundation Models,
matched to user's level and enabled tenses. Every word is tappable —
pre-annotated words show instantly, others get a quick on-device AI
lookup with caching. English translation hidden by default behind a
toggle. Comprehension quiz with 3 multiple-choice questions. Stories
saved to cloud container for sync and persistence across resets.
Closes#9
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track how long users spend studying by timing foreground sessions.
StudyTimerService starts on app active, stops on background, and
accumulates seconds into DailyLog.studySeconds (CloudKit-synced).
Dashboard shows today/total study time with a 7-day bar chart.
Closes#1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checkpoint exams appear after each week in the course view, testing
all words from week 1 through the current week within the same course.
Users can choose 25, 50, or 100 questions with even distribution
across weeks. Results are tracked separately from weekly tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New EnglishConjugator in SharedModels constructs English translations
by combining the verb's infinitive with person pronouns and tense
auxiliaries (e.g., abatir conditional yo → "I would knock down").
Covers all 20 tense IDs, handles 60+ irregular English verbs,
multi-word verbs, 3rd person rules, gerund and participle formation.
VerbDetailView shows the English below each conjugated form, plus a
legend explaining red = irregular conjugation. 42 tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New feature in the Practice tab that lets users search for Spanish songs
by artist + title, fetch lyrics from LRCLIB (free, no API key), pull
album art from iTunes Search API, auto-translate to English via Apple's
on-device Translation framework, and save for offline reading.
Components:
- SavedSong SwiftData model (local container, no CloudKit sync)
- LyricsSearchService actor (LRCLIB + iTunes Search, concurrent)
- LyricsSearchView (artist/song fields, result list with album art)
- LyricsConfirmationView (lyrics preview, auto-translation, save)
- LyricsLibraryView (saved songs list, swipe to delete)
- LyricsReaderView (Spanish lines with English subtitles)
- Practice tab integration (Lyrics button with NavigationLink)
- localStoreResetVersion bumped to 3 for schema migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: the widget was opening the shared local.store with a 2-entity
schema (VocabCard, CourseDeck), causing SwiftData to destructively migrate
the file and drop the 4 entities the widget didn't know about (Verb,
VerbForm, IrregularSpan, TenseGuide). The main app would then re-seed on
next launch, and the cycle repeated forever.
Fix: move Verb, VerbForm, IrregularSpan, TenseGuide from the app target
into SharedModels so both the main app and the widget use the exact same
types from the same module. Both now declare all 6 local entities in their
ModelContainer, producing identical schema hashes and eliminating the
destructive migration.
Other changes bundled in this commit (accumulated during debugging):
- Split ModelContainer into localContainer + cloudContainer (no more
CloudKit + non-CloudKit configs in one container)
- Add SharedStore.localStoreURL() helper and a global reference for
bypass-environment fetches
- One-time store reset mechanism to wipe stale schema metadata from
previous broken iterations
- Bootstrap/maintenance split so only seeding gates the UI; dedup and
cloud repair run in the background
- Sync status toast that shows "Syncing" while background maintenance
runs (network-aware, auto-dismisses)
- Background app refresh task to keep the widget word-of-day fresh
- Speaker icon on VerbDetailView for TTS
- Grammar notes navigation fix (nested NavigationStack was breaking
detail pane on iPhone)
- Word-of-day widget swaps front/back when the deck is reversed so the
Spanish word always shows in bold
- StoreInspector diagnostic helper for raw SQLite table inspection
- Add Conjuga scheme explicitly to project.yml so xcodegen doesn't drop it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>