Commit Graph

26 Commits

Author SHA1 Message Date
Trey T 179400b90d Course — Review Course Material row with bundled weekly PDFs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:40:02 -05:00
Trey T 696eafa64f Noun & adjective practice — Multiple Choice, Review Learned, Review
Mirror the four-entry Vocabulary section for nouns and adjectives, so each
POS gets the same set of practice modes the verb flow already had:

- Noun/Adjective Flashcards (existing) — English → Spanish reveal with
  article for nouns. Now accepts `kind:` to share the view with the
  Review-Learned cram pass.
- Noun/Adjective Multiple Choice — English prompt, 4 Spanish options
  drawn from the current session pool (1 correct + 3 random distractors).
  Same SRS rating writes as Flashcards.
- Review Learned — `NounFlashcardPracticeView(kind: .reviewLearned)` and
  the adjective equivalent. Cycles through already-studied lexemes with
  no schedule changes; mirrors `VocabFlashcardPracticeView`'s
  reviewLearned kind.
- Noun/Adjective Review — fetches due `LexemeReviewCard` rows by POS,
  Spanish-front / English-reveal flashcards rated directly against the
  SRS schedule. Each exposes a static `dueCount(context:)` used by the
  practice-row badge.

Wiring:
- New `LexemeSessionKind` enum (standard / reviewLearned) in
  LexemeSessionQueue.swift, mirroring `VocabSessionKind`.
- Noun + Adjective Flashcard views branch load/persist/answer on `kind`
  so Review Learned doesn't touch the persisted study group or reschedule
  cross-session SRS.
- Practice screen gets dedicated "Nouns" and "Adjectives" sections
  (between Vocabulary and Reading), each with 4 NavigationLinks shaped
  exactly like the Vocabulary section. The previous single-link Noun and
  Adjective entries in the Reading section are removed.
- PracticeView caches `nounDueCount` / `adjectiveDueCount` in @State and
  refreshes on appear + after sessions end, so the badge doesn't trigger
  LexemeReviewCard fetchCount on every body re-evaluation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:59:42 -05:00
Trey T 7da98d786c Vocab study — noun & adjective flashcards with CEFR level toggles
Add SRS-driven noun and adjective flashcards modeled on the existing verb
flashcard flow:

- SharedModels/Lexeme — catalog of non-verb vocab, frequency-ranked, with
  gender for nouns and optional example sentences. Seeded from a bundled
  vocab_lexemes.json built by Scripts/vocab/build_lexemes.py, which joins
  frequency.csv + es-en.data from a pinned doozan/spanish_data commit
  (CC-BY-SA: hermitdave/FrequencyWords + Wiktionary). 1,449 nouns and 600
  adjectives, each with Wiktionary-sourced gender and (where available)
  an example sentence with English translation.
- LexemeReviewCard + LexemeReviewStore — cloud-synced SM-2 SRS, keyed by
  partOfSpeech + lexemeId + drillMode so future drill modes can coexist.
- LexemeSessionQueue + LexemePool — parallel to VocabSessionQueue; fresh
  cards sort by frequency rank.
- LexemeStudyGroup — cloud-synced resumable session per
  (partOfSpeech, drillMode).
- NounFlashcardPracticeView + AdjectiveFlashcardPracticeView — same flow
  as VocabFlashcardPracticeView: English prompt → tap to reveal Spanish
  → Again/Hard/Good/Easy. Nouns reveal with their article (la taza, el
  problema) so gender is taught alongside meaning, not as a separate
  quiz. Example sentence shown when present.

CEFR-style level toggles:
- LexemeLevel enum (A1/A2/B1/B2/C1+) derived from frequencyRank with
  standard Spanish-frequency-dictionary cutoffs (250/500/1000/2000).
- UserProgress.selectedLexemeLevels — cloud-synced multi-select, defaults
  to A1+A2 on first launch.
- SettingsView gains a "Vocabulary Levels" section with five toggles; the
  existing "Levels" section is renamed "Verb Levels" for clarity.
- Due SRS cards always surface regardless of toggles. Disabling a level
  only stops new cards from that band entering the pool.

PracticeView gets "Nouns" and "Adjectives" rows under "Books".

DataLoader: new lexemeDataVersion gate that re-seeds the Lexeme table
from vocab_lexemes.json independent of book seeding. project.yml lists
the new JSON resource and the existing book_olly-vol2.json (which the
previous build was silently excluding because xcodegen rewrote the
project from project.yml).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:16:55 -05:00
Trey T c794c013f0 Vocab Flashcards — persist the study group across launches and devices
The session queue was in-memory only: leaving the flashcard screen or
killing the app rebuilt it from scratch. The set of verbs you were
working through wasn't sticky.

VocabStudyGroup (new cloud-synced @Model) — the active standard study
set: the in-session queue (un-graduated verbs + each card's
learning-step state) as a JSON blob, plus the learned count. One active
group at a time, keyed "active-standard". CloudKit-synced, so the same
group follows you from iPad to iPhone.

VocabStudyGroupStore — fetch-or-create / persist / clear. activeGroup()
returns the newest if duplicates exist (two devices racing before sync
settles); clear() removes all duplicates.

VocabSessionQueue — CardState is now String-backed; added
init(entries:learnedCount:) to resume a saved queue and snapshot() to
export it.

VocabFlashcardPracticeView (standard kind):
  - On load, resume the active group if one exists; otherwise build a
    fresh set and persist it immediately.
  - Every answer writes the updated queue to the group.
  - Finishing the set clears the group; the completion button is now
    "Next Set" and builds a fresh group.
Review Learned (reviewLearned kind) is unaffected — it's an ad-hoc cram
and intentionally isn't persisted; its button stays "Study Again".

Registered VocabStudyGroup in the cloud container schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 15:42:17 -05:00
Trey T 5c0fc8ee2d Vocab Practice — proper SRS session queue with in-session learning steps
Vocab Flashcards / Multiple Choice were a flat linear walk through a
shuffled list — rating a card "Hard" or "Again" did nothing to bring it
back. Replaced with a real two-layer SRS, matching how Anki separates
in-session learning steps from the long-term schedule.

VocabSessionQueue (new) — the in-session layer. Position-based learning
steps:
  - Again → card reappears 5–8 cards later (state: learning)
  - Hard  → reappears 7–10 cards later (state: learning)
  - Good  → first time: → review state, reappears 16–24 cards later
            already in review: graduates, leaves the session
  - Easy  → graduates immediately
A card you keep failing keeps cycling until you mark it Good twice or
Easy once. answer() returns a ReviewQuality only on graduation — that's
the single rating handed to the long-term VerbReviewStore, so
intermediate Again/Hard presses no longer thrash the cross-session
SM-2 schedule.

VocabVerbPool.sessionVerbs (rewritten) — due-first ordering + a 20-card
session cap. Overdue verbs (per VerbReviewCard.dueDate) come first,
most-overdue leading; then never-reviewed verbs by frequency rank.
Not-yet-due verbs are intentionally skipped — that's the SRS schedule
doing its job. A single sitting is now bounded instead of a 100+ card
slog.

Study Again — the completion screen gets a "Study Again" button that
rebuilds the queue from the same verb set (re-shuffled), so you can run
the whole set again after finishing.

Progress display switched from "1 of 110" to "N learned · M to go",
which reflects the live queue as cards requeue and graduate.

Both vocab views now share VocabSessionQueue + VocabVerbPool; the queue
struct is pure value-type logic, easy to reason about and test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:44:43 -05:00
Trey T 900a927f95 Fixes #34 — drop AI-generated images from verb vocab practice
Image Playground's .illustration style can't depict abstract verbs —
"dar" (to give) generated a Shiba Inu in a meadow, "saber" a cake.
Verbs are actions, not objects; a static illustration rarely helps and
the irrelevant output was pure noise.

Removed:
  - VerbIllustration view (was in VocabFlashcardPracticeView).
  - VocabImageService.swift entirely — VerbIllustration was its only
    consumer.
  - vocabImageService from the app environment in ConjugaApp.
  - The illustration row from both vocab session reveal panes.

Vocab card reveal is now: Spanish infinitive + example sentence +
rating buttons. The example sentence already carries the learning
context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:38:01 -05:00
Trey T c890095610 Vocab Practice — verb pool, Settings level filter, per-verb SRS
Vocab Practice used a deck picker over VocabCard rows. That meant it
ignored the Settings level toggles entirely and operated on a totally
separate vocabulary universe than the conjugation modes. Rewired
end-to-end:

Pool source
  Replaced VocabCard with the Verb table. The pool is now
  ReferenceStore.fetchVerbs(selectedLevels: UserProgress.selectedVerbLevels)
  — the same call PracticeSessionService uses for conjugation. Changes
  to level toggles in Settings (or the Verbs tab, which also writes to
  this field) immediately affect Vocab Practice.

Entry flow
  Deleted VocabPracticeEntryView. Practice → Vocabulary now has two
  direct entries:
    • Vocab Flashcards — verb.english → tap → verb.infinitive
    • Vocab Multiple Choice — verb.english → pick from 4 infinitives
  Both pull from the same level-filtered pool, shuffled.

Per-verb SRS
  New VerbReviewCard @Model (cloud-synced, mirrors CourseReviewCard's
  SM-2 fields but keyed by verbId). VerbReviewStore.rate(verbId:quality:)
  applies the existing SRSEngine. Registered in cloud container schema
  in ConjugaApp.swift.

Example sentences
  Lazy-generated via VerbExampleGenerator on first reveal, cached
  through VerbExampleCache (same path VerbDetailView uses). Empty until
  the example arrives — block hides itself if Apple Intelligence isn't
  available.

AI illustration
  VerbIllustration replaces VocabIllustration; same Image Playground
  pipeline. Cache key uses ("verb", infinitive, english) so verbs and
  course-deck vocab never collide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:31:12 -05:00
Trey T 0b7d4a73ad Add Vocab Practice — English-first flashcards + multiple choice, with AI illustrations
Practice tab restructured into three sections:
- Conjugation: the 6 conjugation modes + the Common Tenses / Weak
  Verbs / Irregularity Drills focus buttons.
- Vocabulary: new "Vocab Practice" entry (flashcard or multiple
  choice, deck-picker on entry) + the existing Vocab Review.
- Reading: Stories, Books, Lyrics, Conversation, Listening, Cloze
  (moved here from the flat list).

VocabPracticeEntryView lets the user pick any course/textbook deck
(or "All decks") and a mode (Flashcard / Multiple Choice). Last-used
choice is remembered via @AppStorage.

VocabFlashcardPracticeView:
  Front shows the English meaning. Tap to reveal the Spanish word,
  example sentences from the card, an AI-generated illustration of
  the concept, and Again/Hard/Good/Easy rating buttons. SRS updates
  via the existing CourseReviewStore.rate() path.

VocabMultipleChoicePracticeView:
  English prompt, 4 Spanish options. Distractors come from the same
  deck and prefer matching part-of-speech (via
  DictionaryService.lookup) — falls back to random when POS is
  unknown or the deck has fewer than 4 cards. After answer: reveal
  correct/incorrect, the Spanish word, examples, illustration, and
  the same rating buttons.

VocabImageService wraps Apple Intelligence's ImageCreator
(iOS 18.2+) for on-device illustration generation. Caches PNG
results to disk under Caches/VocabImages keyed by
SHA256(deck+ES+EN). In-flight dedup keeps concurrent requests for
the same key sharing one task. Falls back to a placeholder UI when
Apple Intelligence isn't available (older devices / disabled in
Settings) — detected lazily on the first failed ImageCreator init.

EN-first direction is enforced regardless of the underlying deck's
isReversed flag, so the user sees the English-to-Spanish recall
direction they asked for even when practising a reversed course
deck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:02:02 -05:00
Trey T 9aa4d0836d Guide — bidirectional cross-link chips between tense guides & grammar notes
After the full enrichment pass, both the Tenses and Grammar surfaces of
the Guide tab cover overlapping material — WEIRDO appears in both
"Subjuntivo Presente" and "Subjunctive Triggers", preterite↔imperfect
contrast in three places, etc. Instead of trimming either body and
losing content, add a small chip row at the top of each detail view
linking directly across.

GuideCrossLinks.swift (new) — curated tense→[noteId] map covering 18 of
the 20 tenses. The two without aligned notes (ind_pluscuamperfecto,
ind_preterito_anterior) don't show chips. The reverse map (noteId→
[tenseId]) is derived once at static init and sorted by canonical tense
order so chips appear in conjugation-table order.

GuideDetailView — "Related grammar" indigo chip row directly under the
header. Tap a chip → switch to the Grammar segment with that note
selected.

GrammarNoteDetailView — "Used in tenses" orange chip row directly under
the title. Tap a chip → switch to the Tenses segment with that tense
selected.

The GuideView segment-change handler now only clears the *other* tab's
selection so programmatic jumps keep their destination intact; manual
segment swipes still feel "fresh" like before.

No content is removed. Users get a deeper-dive path one tap away in
either direction, and the redundancy becomes a feature instead of a
maintenance hazard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:59:46 -05:00
Trey T a416233a2d Books — read-aloud mode with active-word highlight and tap-to-define
Adds a TTS read-along to the book reader. Tap the play button in the
toolbar; AVSpeechSynthesizer reads the chapter paragraph-by-paragraph
with the current word highlighted in yellow, auto-scrolling the active
paragraph to centre. Tap any word during read-along to pause and open
the definition sheet; reading resumes when the sheet dismisses.

Behavior per spec:
- Tap-to-define interrupts the synth (pauseSpeaking at: .immediate) and
  resumes on sheet dismiss.
- Voice picker sheet (waveform.circle toolbar button) lists installed
  Spanish voices grouped by Premium / Enhanced / Default quality, with
  a "Download more voices…" row that opens iOS Settings (no public
  deep-link to Accessibility → Spoken Content exists; the footer spells
  out the path).
- Speed picker (Slow / Normal / Fast) drives AVSpeechUtterance.rate.
- Stops at chapter end, no auto-advance to the next chapter.
- Vocabulary lines shaped `palabra = meaning` are skipped — the synth
  would otherwise say "palabra equals meaning" and they're reference
  material, not prose.

Audio session uses .playback + .spokenAudio mode and is properly
deactivated with .notifyOthersOnDeactivation on stop() so music apps
resume cleanly after reading ends.

Voice/rate persisted via @AppStorage; controller picks them up
onAppear and writes back through Bindings the picker mutates.

Word-index space in BookSpeechController.wordRanges(in:) matches
BookReaderView's split(separator: " ") rendering exactly — both split
on ASCII U+0020 only, so willSpeakRange callbacks resolve to the right
visible word.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 21:42:09 -05:00
Trey T 09e49bda2c Add Books — read EPUB-imported books in Practice with tap-to-define
New "Books" row in the Practice tab opens a library of bundled bilingual
books. Each chapter renders Spanish paragraph-by-paragraph; tap any
word for a definition sheet (DictionaryService with on-device AI
fallback), or toggle the toolbar button to swap to the pre-computed
English translation inline.

Local-only Book + BookChapter SwiftData models added to the local
container schema (reset version bumped to 5). DataLoader.seedBooks
walks the bundle for `book_*.json` resources, so future books drop in
without touching app code — just bundle a new JSON and bump
bookDataVersion.

First book: Olly Richards' "Spanish Short Stories For Beginners
Vol 2" — 13 chapters, 2,646 paragraphs, bilingual.

Scripts/books/ is the repeatable pipeline for future EPUBs:
extract_epub.py → translate_chapters.py (per-chapter resumable jobs) →
bundle_book.py. Translation is done by parallel Claude Code subagents
reading per-job input files and writing output files — no API key
required, matching the pattern used for the textbook vocab vision
pass. See Scripts/books/README.md for the full how-to.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:21:44 -05:00
Trey T ade091f108 Add Extra Study — star cards during review, study just the marked ones
Per-week "Extra Study (N)" row appears at the end of each LanGo week
section when at least one card is marked. Cards are marked from inside
VocabFlashcardView via a star next to the speaker on reveal. Marks are
keyed by the same SHA256 hash CourseReviewCard uses, so a mark and its
SRS state describe the same logical card.

ExtraStudyMark is CloudKit-synced (private DB), with uniqueness enforced
by fetch-or-create on id since CloudKit forbids @Attribute(.unique).
Skipped for textbook courses: DeckStudyView nils out the mark context
when the deck's courseName matches a TextbookChapter, and CourseView
hides the row when the active course is a textbook — so there are no
orphan marks the user can't reach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:38:39 -05:00
Trey T 06b47d37cf Fix video action button wrapping + bundle textbook seed JSONs
- VideoActionsView: stack icon over title via a custom label style so
  the Stream/Download/Play row fits iPhone width without wrapping
  mid-word ("Stre am", "Down load").
- project.yml: add textbook_data.json and textbook_vocab.json to the
  Conjuga target's Copy Bundle Resources. They were missing, so
  DataLoader.seedTextbookData early-returned every launch and the
  textbook never appeared in the Book tab. Regenerated Conjuga.xcodeproj.
2026-04-24 17:32:36 -05:00
Trey t 5777a210cd Fixes #21 — Curated YouTube videos per guide + grammar item
Each of the 56 tense guides and grammar notes gets a curated YouTube video
attached (54 with picks, 2 silent nulls on rare / hard-to-find topics).
Users can stream in YouTube/Safari, download via YouTubeKit for offline
viewing, or play the local MP4 full-screen via AVPlayer.

YouTubeVideoStore loads the bundled youtube_videos.json at launch and serves
lookups by tense id or grammar note id. VideoDownloadService resolves the
best progressive MP4 stream off the main actor (YouTubeKit isn't Sendable),
writes to documents/videos/<videoId>.mp4, and records a DownloadedVideo row
in the local SwiftData container so the app knows what's on disk across
launches.

VideoActionsButtonRow is the unified UI for both detail views: three large
buttons — Stream (red, always enabled), Download (blue, disabled while in
flight and after completion, shows progress), Play (green, enabled only
when downloaded). Full-screen cover on tap. Settings gains a Downloaded
Videos list with swipe-delete, total-size summary, and a 500 MB warning.

Local store reset version bumped to 4 for the new DownloadedVideo schema.

Known fragility: YouTubeKit scrapes YouTube's private stream API and will
break when YouTube changes their internal format. Streaming keeps working
regardless.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:51:19 -05:00
Trey t 98badc98ad Fixes #28 — Curated reflexive verb list on detail + practice filter
Bundles the 100 most common reflexive verbs from spanishwithdaniel.com as a
canonical list and wires it through the UI. Compound list entries (recibirse
/ graduarse, equivocarse / confundirse) are split. Trailing prepositions and
set-phrase completions are captured as usageHint (e.g. acordarse "de",
ponerse "de acuerdo").

ReflexiveVerbStore loads the JSON at launch and exposes lookups by base
infinitive, both via @Environment for SwiftUI and a static shared instance
for services. Verbs whose bare infinitive isn't in the list skip the UI
treatment silently.

VerbDetailView shows a new Reflexive section with the reflexive infinitive,
usage hint, and English meaning when there is a match. VerbListView gains a
"Reflexive verbs only" filter alongside the existing Level and Irregularity
filters. Settings adds the same flag so it also constrains the practice
pool; PracticeSessionService applies the reflexive filter in all six pick
paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:56:04 -05:00
Trey t 4093b5a7f3 Fixes #27 — AI-generated example sentences on verb detail
Tapping a verb now shows six example sentences beneath the conjugation table,
one per core tense (Present, Preterite, Imperfect, Future, Present Subjunctive,
Imperative). Each example renders the tense label, Spanish sentence, and
English translation in the DeckStudyView style.

Generation uses Foundation Models with a @Generable schema that pins each
response to the requested tenseId and forces tú/nosotros subjects for
imperatives. Results are cached as JSON in the Caches directory keyed by
verb id (DictionaryService pattern); cache misses regenerate on demand.

Devices without Apple Intelligence see an inline notice instead of the
loading state. No network dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:57:32 -05:00
Trey T 5f90a01314 Render textbook vocab as paired Spanish→English grid
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>
2026-04-19 15:58:41 -05:00
Trey T 63dfc5e41a Add textbook reader, exercise grading, stem-change toggle, extraction pipeline
Major changes:
- Textbook UI: chapter list, reader, and interactive exercise view (keyboard
  + Apple Pencil) surfaced under the Course tab. 30 chapters, 251 exercises.
- Stem-change conjugation toggle on Week 4 flashcard decks (E-IE, E-I, O-UE).
  Uses existing VerbForm + IrregularSpan data to render highlighted present
  tense conjugations inline.
- Deterministic on-device answer grader with partial credit (correct / close
  for accent-stripped or single-char-typo / wrong). 11 unit tests cover it.
- SharedModels: TextbookChapter (local), TextbookExerciseAttempt (cloud-
  synced), AnswerGrader helpers. Bumped schema.
- DataLoader: textbook seeder (version 8) + refresh helpers that preserve
  LanGo course decks when textbook data is re-seeded.
- Local extraction pipeline in Conjuga/Scripts/textbook/ — XHTML chapter
  parser, answer-key parser, macOS Vision image OCR + PDF page OCR, merger,
  NSSpellChecker validator, language-aware auto-fixer, and repair pass that
  re-pairs quarantined vocab rows using bounding-box coordinates.
- UI test target (ConjugaUITests) with three tests: end-to-end textbook
  flow, all-chapters screenshot audit, and stem-change toggle verification.

Generated textbook content (textbook_data.json, textbook_vocab.json) and
third-party source files are gitignored — re-run Scripts/textbook/run_pipeline.sh
locally to regenerate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:12:55 -05:00
Trey t a663bc03cd Add 6 new practice features, offline dictionary, and feature reference
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>
2026-04-13 16:12:36 -05:00
Trey t 451866e988 Add AI-generated short stories with tappable words and comprehension quiz
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>
2026-04-13 11:31:58 -05:00
Trey t 473eb271cc Add background study timer tracking foreground time per day
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>
2026-04-13 09:44:44 -05:00
Trey t d372a5c77f Add checkpoint exams with cumulative vocabulary review per course
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>
2026-04-12 12:45:25 -05:00
Trey t a1dc17bf00 Add per-form English translations to verb conjugation table
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>
2026-04-11 23:44:17 -05:00
Trey t faef20e5b8 Add Lyrics practice: search, translate, and read Spanish song lyrics
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>
2026-04-11 22:44:40 -05:00
Trey t fd5861c48d Move reference-data models to SharedModels to fix widget-triggered data loss
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>
2026-04-10 13:51:02 -05:00
Trey t 4b467ec136 Initial commit: Conjuga Spanish conjugation app
Includes SwiftData dual-store architecture (local reference + CloudKit user data),
JSON-based data seeding, 20 tense guides, 20 grammar notes, SRS review system,
course vocabulary, and widget support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 20:58:33 -05:00