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>
This commit is contained in:
Trey t
2026-04-22 09:57:32 -05:00
parent 5d3accb2c0
commit 4093b5a7f3
8 changed files with 387 additions and 272 deletions

View File

@@ -8,12 +8,15 @@
/* Begin PBXBuildFile section */
00BEC0BDBB49198022D9852E /* WordOfDayWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E9BCDBB9BC24F5C8117767E /* WordOfDayWidget.swift */; };
04C74D9E0ED84BF785A8331C /* ClozeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D232CDA43CC9218D748BA121 /* ClozeView.swift */; };
0A89DCC82BE11605CB866DEF /* TenseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC3247457109FC6BF00D85B /* TenseInfo.swift */; };
12D2C9311D5C4764B48B1754 /* StoryQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */; };
0AD63CAED7C568590A16E879 /* StoryQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */; };
0D0D3B5CC128D1A1D1252282 /* PronunciationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */; };
13F29AD5745FB532709FA28A /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E972AA745F44586EF0B1B0C8 /* OnboardingView.swift */; };
14242FD1F500D296D41E927C /* FeatureReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */; };
1A230C01A045F0C095BFBD35 /* PracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */; };
1B0B3B2C771AD72E25B3493C /* StemChangeToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */; };
1C2636790E70B6BC7FFCC904 /* DailyLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313D24F96E6A0039C34341F /* DailyLog.swift */; };
20B71403A8D305C29C73ADA2 /* StemChangeConjugationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */; };
218E982FC4267949F82AABAD /* SharedModels in Frameworks */ = {isa = PBXBuildFile; productRef = 4A4D7B02884EBA9ACD93F0FD /* SharedModels */; };
261E582449BED6EF41881B04 /* AdaptiveContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B16FF4C52457CD8CD703532 /* AdaptiveContainer.swift */; };
27BA7FA9356467846A07697D /* TypingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10C16AA6022E4742898745CE /* TypingView.swift */; };
@@ -22,69 +25,65 @@
2C7ABAB4D88E3E3B0EAD1EF7 /* PracticeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */; };
33E885EB38C3BB0CB058871A /* HandwritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F842EB5E566C74658D918BB /* HandwritingView.swift */; };
352A5BAA6E406AA5850653A4 /* PracticeSessionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */; };
354631F309E625046A3A436B /* TextbookExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */; };
35A0F6E7124D989312721F7D /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AC3C548BDB9EF8701BE64C /* DashboardView.swift */; };
35D6404C60C249D5995AD895 /* ConversationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10603F454E54341AA4B9931 /* ConversationService.swift */; };
36F92EBAEB0E5F2B010401EF /* StreakCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EF2362D9FFF9B07A45CE6D /* StreakCalendarView.swift */; };
377C4AA000CE9A0D8CC43DA9 /* GrammarNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */; };
39D0666B293DC265CF87B9DD /* SentenceBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */; };
3EC2A2F4B9C24B029DA49C40 /* VocabReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3698CE7ACF148318615293E /* VocabReviewView.swift */; };
3F4F0C07BE61512CBFBBB203 /* HandwritingCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D974250C396589656B8443 /* HandwritingCanvas.swift */; };
4005E258FDF03C8B3A0D53BD /* VocabFlashcardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931634BEB33B93429CE254F /* VocabFlashcardView.swift */; };
46943ACFABF329DE1CBFC471 /* TensePill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 102F0E136CDFF8CED710210F /* TensePill.swift */; };
4C2649215B81470195F38ED0 /* StoryLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */; };
48967E05C65E32F7082716CD /* AnswerChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */; };
4C3484403FD96E37DA4BEA66 /* NewWordIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72CB5F95DF256DF7CD73269D /* NewWordIntent.swift */; };
4DCC5CC233DE4701A12FD7EB /* ListeningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B2179562E54E148C98219D /* ListeningView.swift */; };
4C577CF6B137D0A32759A169 /* VerbExampleGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */; };
50E0095A23E119D1AB561232 /* VerbDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DBE662F89F02A0282F5BEE /* VerbDetailView.swift */; };
519E68D2DF4C80AB96058C0D /* LyricsConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA01795655C444795577A22 /* LyricsConfirmationView.swift */; };
51D072AF30F4B12CD3E8F918 /* SRSEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0E6EAFC0D24928BA956FA5 /* SRSEngine.swift */; };
53908E41767B438C8BD229CD /* ClozeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A649B04B8B3C49419AD9219C /* ClozeView.swift */; };
53A0AC57EAC44B676C997374 /* QuizType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626873572466403C0288090D /* QuizType.swift */; };
5A3246026E68AB6483126D0B /* WeekProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1980E8E439EB76ED7330A90D /* WeekProgressWidget.swift */; };
5EA915FFA906C5C2938FCADA /* ConjugaWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */; };
5EE41911F3D17224CAB359ED /* StudyTimerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */; };
60E86BABE2735E2052B99DF3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCC95A95581458E068E0484 /* SettingsView.swift */; };
61328552866DE185B15011A9 /* StoryLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15AC27B1E3D332709657F20B /* StoryLibraryView.swift */; };
615D3128ED6E84EF59BB5AA3 /* LyricsReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58394296923991E56BAC2B02 /* LyricsReaderView.swift */; };
65ABC39F35804C619DAB3200 /* GrammarExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */; };
6BB4B0A655E6CB6F82D81B5A /* WeekTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */; };
6CCC8D51F5524688A4BC5AF8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */; };
6D4A29280FDD99B8E18AF264 /* WidgetDataReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2889F2F81673AFF3A58A07A8 /* WidgetDataReader.swift */; };
6ED2AC2CAA54688161D4B920 /* SyncStatusMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */; };
728702D9AA7A8BDABBA62513 /* ReviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */; };
760628EFE1CF191CE2FC07DC /* GuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */; };
78FE99C5D511737B6877EDD5 /* VocabReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D95887B18216FCA71643D6 /* VocabReviewView.swift */; };
7A13757EA40E81E55640D0FC /* LyricsSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70960F0FD7509310B3F61C48 /* LyricsSearchView.swift */; };
7A1B2C3D4E5F60718293A4B5 /* textbook_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */; };
7A1B2C3D4E5F60718293A4B6 /* textbook_vocab.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */; };
7A1B2C3D4E5F60718293AA01 /* TextbookChapterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */; };
7A1B2C3D4E5F60718293AA02 /* TextbookChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */; };
7A1B2C3D4E5F60718293AA03 /* TextbookExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */; };
7A1B2C3D4E5F60718293AA04 /* AnswerChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */; };
81E4DB9F64F3FF3AB8BCB03A /* TextbookChapterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */; };
81FA7EBCF18F0AAE0BF385C3 /* VerbListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63061BBC8998DF33E3DCA2B /* VerbListView.swift */; };
82F6079BE3F31AC3FB2D1013 /* MultipleChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */; };
84CCBAE22A9E0DA27AE28723 /* DeckStudyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631DC0A942DD57C81DECE083 /* DeckStudyView.swift */; };
8510085D78E248D885181E80 /* FeatureReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */; };
8C1E4E7F36D64EFF8D092AC8 /* StoryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327659ABFD524514B6D2D505 /* StoryGenerator.swift */; };
8B516215E0842189DEA0DBB1 /* GrammarExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */; };
8BD4B5A2DDDD4BE4B4A94962 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79576893566932D2BE207528 /* ChatView.swift */; };
8C43F09F52EA9B537EA27E43 /* CourseReviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */; };
8D7CA0F4496B44C28CD5EBD5 /* DictionaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */; };
8E3D8E8254CF4213B9D9FAD3 /* StoryReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B6081226847E0A0A174BC /* StoryReaderView.swift */; };
943728CD3E65FE6CCADB05EE /* StemChangeConjugationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */; };
8DC1CB93333F94C5297D33BF /* GrammarExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */; };
90B76E34F195223580F7CCCF /* DictionaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BE2A08EC694FF784ED5575 /* DictionaryService.swift */; };
943A94A8C71919F3EFC0E8FA /* UserProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E536AD1180FE10576EAC884A /* UserProgress.swift */; };
968D626462B0ADEC8D7D56AA /* CheckpointExamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */; };
96A3E5FA8EC63123D97365E1 /* TextbookFlowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */; };
97EFCF6724CE59DC4F0274FD /* AchievementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */; };
9D9FD3853C5C969C62AE9999 /* StartupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */; };
9F0ACDC1F4ACB1E0D331283D /* CheckpointExamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */; };
A7DF435F99E66E067F2B33E1 /* ListeningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D1904DF07E0A6816134CF3 /* ListeningView.swift */; };
A9959AE6C87B4AD21554E401 /* FullTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 711CB7539EF5887F6F7B8B82 /* FullTableView.swift */; };
AAC6F85A1C3B6C1186E1656A /* TenseEndingTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D98E1564C6538056D81200 /* TenseEndingTable.swift */; };
ABBE5080E254D1D3E3465E40 /* ConversationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96C065B8787DEC6818E497 /* ConversationService.swift */; };
ACE9D8B3116757B5D6F0F766 /* StoryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713F23A9C2935408B136C7C7 /* StoryGenerator.swift */; };
B10A324C06F0957DDE2233F8 /* TextbookChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39908548430FDF01D76201FB /* TextbookChapterView.swift */; };
B4603AA6EFB134794AA39BF4 /* LyricsLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2B1F646394D7C03493F1BF /* LyricsLibraryView.swift */; };
B73F6EED00304B718C6FEFFA /* GrammarExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */; };
B48C0015BE53279B0631C2D7 /* ChatLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648436F8326CF95777E2FA58 /* ChatLibraryView.swift */; };
BA3DE2DA319AA3B572C19E11 /* VerbExampleCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */; };
BB48230C3B26EA6E84D2D823 /* DailyProgressRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180F9D59828C36B44A5E384F /* DailyProgressRing.swift */; };
BC662C36AC503E00A977CEC1 /* VocabGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */; };
BF0832865857EFDA1D1CDEAD /* SharedModels in Frameworks */ = {isa = PBXBuildFile; productRef = BCCBABD74CADDB118179D8E9 /* SharedModels */; };
C0BAEF49A6270D8F64CF13D6 /* PracticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C359C051FB157EF447561405 /* PracticeViewModel.swift */; };
C1F84182F12EB5CFF32768B6 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5983A534E4836F30B5281ACB /* MainTabView.swift */; };
C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */; };
C3851F960C1162239DC2F935 /* CourseQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 143D06606AE10DCA30A140C2 /* CourseQuizView.swift */; };
C8AF0931F7FD458C80B6EC0D /* ChatLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5667AA04211A449A9150BD28 /* ChatLibraryView.swift */; };
C8C3880535008764B7117049 /* DataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADCA82DDD34DF36D59BB283 /* DataLoader.swift */; };
CAC69045B74249F121643E88 /* AnswerReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */; };
CC886125F8ECE72D1AAD4861 /* StoryReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A48474D969CEF5F573DF09B /* StoryReaderView.swift */; };
CF9E48ADF0501FB79F3DDB7B /* conjuga_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C2D88FF9A3B0590B22C7837 /* conjuga_data.json */; };
D3FFE73A5AD27F1759F50727 /* SpeechService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E3AD244327CBF24B7A2752 /* SpeechService.swift */; };
D40B4E919DE379C50265CA9F /* SyncToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4B5204F6B8647C816814F0 /* SyncToast.swift */; };
@@ -92,18 +91,14 @@
D6B67523714E0B3618391956 /* CombinedWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43345D6C7EAA4017E3A45935 /* CombinedWidget.swift */; };
D7456B289D135CEB3A15122B /* TestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFE27F29412021AEC57E728 /* TestResult.swift */; };
DB73836F751BB2751439E826 /* LyricsSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */; };
DDF58F3899FC4B92BF6587D2 /* StudyTimerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */; };
DF06034A4B2C11BA0C0A84CB /* ConjugaWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
DF82C2579F9889DDB06362CC /* ReferenceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777C696A841803D5B775B678 /* ReferenceStore.swift */; };
E7BFEE9A90E1300EFF5B1F32 /* HandwritingRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */; };
E814A9CF1067313F74B509C6 /* StoreInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */; };
E82C743EB1FDF6B67ED22EAD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6153A5C7241C1AB0373AA17 /* Foundation.framework */; };
E99473B7DF9BCAE150E9D1E1 /* WidgetDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D570252DA3DCDD9217C71863 /* WidgetDataService.swift */; };
EA07DB964C8940F69C14DE2C /* PronunciationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535EF6988A24B47B70209A2 /* PronunciationService.swift */; };
ED0401D05A7C2B4C55057A88 /* DailyProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195DA9CDA703DDFAD1B3CD5A /* DailyProgressWidget.swift */; };
F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 833516C5D57F164C8660A479 /* CourseView.swift */; };
F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A014EEC3EE08E945FBBA5335 /* Assets.xcassets */; };
F7E459C46F25A8A45D7E0DFB /* AllChaptersScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */; };
F84706B47A2156B2138FB8D5 /* GrammarNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1A6221A35699BD8065D064 /* GrammarNotesView.swift */; };
FC7873F97017532C215DAD34 /* ReviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */; };
/* End PBXBuildFile section */
@@ -116,13 +111,6 @@
remoteGlobalIDString = F73909B4044081DB8F6272AF;
remoteInfo = ConjugaWidgetExtension;
};
6E1F966015DA38BD4E3CE8AF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = AB7396D9C3E14B65B5238368 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 96127FACA68AE541F5C0F8BC;
remoteInfo = Conjuga;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -140,33 +128,34 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
02B2179562E54E148C98219D /* ListeningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListeningView.swift; sourceTree = "<group>"; };
02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbExampleGenerator.swift; sourceTree = "<group>"; };
0313D24F96E6A0039C34341F /* DailyLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyLog.swift; sourceTree = "<group>"; };
07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExerciseView.swift; sourceTree = "<group>"; };
0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewCard.swift; sourceTree = "<group>"; };
102F0E136CDFF8CED710210F /* TensePill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TensePill.swift; sourceTree = "<group>"; };
10C16AA6022E4742898745CE /* TypingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingView.swift; sourceTree = "<group>"; };
12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureReferenceView.swift; sourceTree = "<group>"; };
143D06606AE10DCA30A140C2 /* CourseQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseQuizView.swift; sourceTree = "<group>"; };
15AC27B1E3D332709657F20B /* StoryLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryLibraryView.swift; sourceTree = "<group>"; };
16C1F74196C3C5628953BE3F /* Conjuga.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Conjuga.app; sourceTree = BUILT_PRODUCTS_DIR; };
17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExercise.swift; sourceTree = "<group>"; };
180F9D59828C36B44A5E384F /* DailyProgressRing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyProgressRing.swift; sourceTree = "<group>"; };
18AC3C548BDB9EF8701BE64C /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = "<group>"; };
18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusMonitor.swift; sourceTree = "<group>"; };
195DA9CDA703DDFAD1B3CD5A /* DailyProgressWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyProgressWidget.swift; sourceTree = "<group>"; };
1980E8E439EB76ED7330A90D /* WeekProgressWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekProgressWidget.swift; sourceTree = "<group>"; };
1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureReferenceView.swift; sourceTree = "<group>"; };
1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementService.swift; sourceTree = "<group>"; };
1C4B5204F6B8647C816814F0 /* SyncToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncToast.swift; sourceTree = "<group>"; };
1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeView.swift; sourceTree = "<group>"; };
1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugaApp.swift; sourceTree = "<group>"; };
1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExerciseView.swift; sourceTree = "<group>"; };
1F842EB5E566C74658D918BB /* HandwritingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingView.swift; sourceTree = "<group>"; };
27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConjugaUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
20D1904DF07E0A6816134CF3 /* ListeningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListeningView.swift; sourceTree = "<group>"; };
2889F2F81673AFF3A58A07A8 /* WidgetDataReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataReader.swift; sourceTree = "<group>"; };
2931634BEB33B93429CE254F /* VocabFlashcardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabFlashcardView.swift; sourceTree = "<group>"; };
2A8B6081226847E0A0A174BC /* StoryReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryReaderView.swift; sourceTree = "<group>"; };
30EF2362D9FFF9B07A45CE6D /* StreakCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreakCalendarView.swift; sourceTree = "<group>"; };
327659ABFD524514B6D2D505 /* StoryGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGenerator.swift; sourceTree = "<group>"; };
34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckpointExamView.swift; sourceTree = "<group>"; };
3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingRecognizer.swift; sourceTree = "<group>"; };
39908548430FDF01D76201FB /* TextbookChapterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterView.swift; sourceTree = "<group>"; };
3A96C065B8787DEC6818E497 /* ConversationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationService.swift; sourceTree = "<group>"; };
3B16FF4C52457CD8CD703532 /* AdaptiveContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveContainer.swift; sourceTree = "<group>"; };
3BC3247457109FC6BF00D85B /* TenseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenseInfo.swift; sourceTree = "<group>"; };
3CC1AD23158CBABBB753FA1E /* ConjugaWidget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ConjugaWidget.entitlements; sourceTree = "<group>"; };
@@ -175,9 +164,11 @@
42ADC600530309A9B147A663 /* IrregularHighlightText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IrregularHighlightText.swift; sourceTree = "<group>"; };
43345D6C7EAA4017E3A45935 /* CombinedWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedWidget.swift; sourceTree = "<group>"; };
43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsSearchService.swift; sourceTree = "<group>"; };
496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterListView.swift; sourceTree = "<group>"; };
49E3AD244327CBF24B7A2752 /* SpeechService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechService.swift; sourceTree = "<group>"; };
4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PronunciationService.swift; sourceTree = "<group>"; };
4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarNote.swift; sourceTree = "<group>"; };
5667AA04211A449A9150BD28 /* ChatLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLibraryView.swift; sourceTree = "<group>"; };
4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTimerService.swift; sourceTree = "<group>"; };
58394296923991E56BAC2B02 /* LyricsReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsReaderView.swift; sourceTree = "<group>"; };
5983A534E4836F30B5281ACB /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeHeaderView.swift; sourceTree = "<group>"; };
@@ -185,64 +176,55 @@
5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekTestView.swift; sourceTree = "<group>"; };
626873572466403C0288090D /* QuizType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizType.swift; sourceTree = "<group>"; };
631DC0A942DD57C81DECE083 /* DeckStudyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeckStudyView.swift; sourceTree = "<group>"; };
6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VocabGridTests.swift; sourceTree = "<group>"; };
648436F8326CF95777E2FA58 /* ChatLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLibraryView.swift; sourceTree = "<group>"; };
69D98E1564C6538056D81200 /* TenseEndingTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenseEndingTable.swift; sourceTree = "<group>"; };
6A48474D969CEF5F573DF09B /* StoryReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryReaderView.swift; sourceTree = "<group>"; };
6B9A9F2AB21895E06989A4D5 /* FlashcardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashcardView.swift; sourceTree = "<group>"; };
70960F0FD7509310B3F61C48 /* LyricsSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsSearchView.swift; sourceTree = "<group>"; };
711CB7539EF5887F6F7B8B82 /* FullTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullTableView.swift; sourceTree = "<group>"; };
713F23A9C2935408B136C7C7 /* StoryGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGenerator.swift; sourceTree = "<group>"; };
72CB5F95DF256DF7CD73269D /* NewWordIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWordIntent.swift; sourceTree = "<group>"; };
731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentenceBuilderView.swift; sourceTree = "<group>"; };
76BE2A08EC694FF784ED5575 /* DictionaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryService.swift; sourceTree = "<group>"; };
777C696A841803D5B775B678 /* ReferenceStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferenceStore.swift; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = textbook_data.json; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = textbook_vocab.json; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterListView.swift; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterView.swift; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookExerciseView.swift; sourceTree = "<group>"; };
7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerChecker.swift; sourceTree = "<group>"; };
79576893566932D2BE207528 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
7E6AF62A3A949630E067DC22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
80D974250C396589656B8443 /* HandwritingCanvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingCanvas.swift; sourceTree = "<group>"; };
833516C5D57F164C8660A479 /* CourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseView.swift; sourceTree = "<group>"; };
83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerReviewView.swift; sourceTree = "<group>"; };
842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeSessionService.swift; sourceTree = "<group>"; };
8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AllChaptersScreenshotTests.swift; sourceTree = "<group>"; };
854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookExerciseView.swift; sourceTree = "<group>"; };
8C2D88FF9A3B0590B22C7837 /* conjuga_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = conjuga_data.json; sourceTree = "<group>"; };
8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideView.swift; sourceTree = "<group>"; };
8E9BCDBB9BC24F5C8117767E /* WordOfDayWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordOfDayWidget.swift; sourceTree = "<group>"; };
8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StemChangeToggleTests.swift; sourceTree = "<group>"; };
950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryLibraryView.swift; sourceTree = "<group>"; };
9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = ConjugaWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTimerService.swift; sourceTree = "<group>"; };
9E1FB35614B709E6B1D1D017 /* Conjuga.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Conjuga.entitlements; sourceTree = "<group>"; };
A014EEC3EE08E945FBBA5335 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryService.swift; sourceTree = "<group>"; };
A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupCoordinator.swift; sourceTree = "<group>"; };
A6153A5C7241C1AB0373AA17 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
A63061BBC8998DF33E3DCA2B /* VerbListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbListView.swift; sourceTree = "<group>"; };
A649B04B8B3C49419AD9219C /* ClozeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClozeView.swift; sourceTree = "<group>"; };
A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryQuizView.swift; sourceTree = "<group>"; };
AC34396050805693AA4AC582 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerChecker.swift; sourceTree = "<group>"; };
BC273716CD14A99EFF8206CA /* course_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = course_data.json; sourceTree = "<group>"; };
BCCC95A95581458E068E0484 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
C359C051FB157EF447561405 /* PracticeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeViewModel.swift; sourceTree = "<group>"; };
CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewStore.swift; sourceTree = "<group>"; };
CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextbookFlowUITests.swift; sourceTree = "<group>"; };
CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StemChangeConjugationView.swift; sourceTree = "<group>"; };
CF6D58AEE2F0DFE0F1829A73 /* SharedModels */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SharedModels; path = SharedModels; sourceTree = SOURCE_ROOT; };
D3698CE7ACF148318615293E /* VocabReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabReviewView.swift; sourceTree = "<group>"; };
D535EF6988A24B47B70209A2 /* PronunciationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PronunciationService.swift; sourceTree = "<group>"; };
D232CDA43CC9218D748BA121 /* ClozeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClozeView.swift; sourceTree = "<group>"; };
D570252DA3DCDD9217C71863 /* WidgetDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataService.swift; sourceTree = "<group>"; };
DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleChoiceView.swift; sourceTree = "<group>"; };
DADCA82DDD34DF36D59BB283 /* DataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLoader.swift; sourceTree = "<group>"; };
DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseReviewStore.swift; sourceTree = "<group>"; };
DAFE27F29412021AEC57E728 /* TestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResult.swift; sourceTree = "<group>"; };
E10603F454E54341AA4B9931 /* ConversationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationService.swift; sourceTree = "<group>"; };
E1DBE662F89F02A0282F5BEE /* VerbDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbDetailView.swift; sourceTree = "<group>"; };
E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryQuizView.swift; sourceTree = "<group>"; };
E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugaWidgetBundle.swift; sourceTree = "<group>"; };
E536AD1180FE10576EAC884A /* UserProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProgress.swift; sourceTree = "<group>"; };
E8D95887B18216FCA71643D6 /* VocabReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabReviewView.swift; sourceTree = "<group>"; };
E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreInspector.swift; sourceTree = "<group>"; };
E972AA745F44586EF0B1B0C8 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckpointExamView.swift; sourceTree = "<group>"; };
FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbExampleCache.swift; sourceTree = "<group>"; };
F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExercise.swift; sourceTree = "<group>"; };
F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StemChangeConjugationView.swift; sourceTree = "<group>"; };
FC2B1F646394D7C03493F1BF /* LyricsLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsLibraryView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -263,14 +245,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
C5C1BB325D49EE6ED3AC3D5F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E82C743EB1FDF6B67ED22EAD /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -282,8 +256,6 @@
9E1FB35614B709E6B1D1D017 /* Conjuga.entitlements */,
1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */,
BC273716CD14A99EFF8206CA /* course_data.json */,
7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */,
7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */,
7E6AF62A3A949630E067DC22 /* Info.plist */,
353C5DE41FD410FA82E3AED7 /* Models */,
1994867BC8E985795A172854 /* Services */,
@@ -297,8 +269,8 @@
0931AEB5B728C3A03F06A1CA /* Settings */ = {
isa = PBXGroup;
children = (
1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */,
BCCC95A95581458E068E0484 /* SettingsView.swift */,
12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */,
);
path = Settings;
sourceTree = "<group>";
@@ -316,24 +288,26 @@
isa = PBXGroup;
children = (
1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */,
B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */,
3A96C065B8787DEC6818E497 /* ConversationService.swift */,
DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */,
DADCA82DDD34DF36D59BB283 /* DataLoader.swift */,
7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */,
76BE2A08EC694FF784ED5575 /* DictionaryService.swift */,
3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */,
43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */,
842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */,
E10603F454E54341AA4B9931 /* ConversationService.swift */,
D535EF6988A24B47B70209A2 /* PronunciationService.swift */,
A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */,
327659ABFD524514B6D2D505 /* StoryGenerator.swift */,
978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */,
4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */,
777C696A841803D5B775B678 /* ReferenceStore.swift */,
CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */,
49E3AD244327CBF24B7A2752 /* SpeechService.swift */,
5C0E6EAFC0D24928BA956FA5 /* SRSEngine.swift */,
A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */,
E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */,
713F23A9C2935408B136C7C7 /* StoryGenerator.swift */,
4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */,
18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */,
EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */,
02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */,
D570252DA3DCDD9217C71863 /* WidgetDataService.swift */,
);
path = Services;
@@ -351,6 +325,7 @@
isa = PBXGroup;
children = (
0313D24F96E6A0039C34341F /* DailyLog.swift */,
F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */,
4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */,
626873572466403C0288090D /* QuizType.swift */,
0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */,
@@ -358,7 +333,6 @@
3BC3247457109FC6BF00D85B /* TenseInfo.swift */,
DAFE27F29412021AEC57E728 /* TestResult.swift */,
E536AD1180FE10576EAC884A /* UserProgress.swift */,
17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -384,6 +358,16 @@
path = ViewModels;
sourceTree = "<group>";
};
43E4D263B0AF47E401A51601 /* Stories */ = {
isa = PBXGroup;
children = (
15AC27B1E3D332709657F20B /* StoryLibraryView.swift */,
A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */,
6A48474D969CEF5F573DF09B /* StoryReaderView.swift */,
);
path = Stories;
sourceTree = "<group>";
};
4B183AB0C56BC2EC302531E7 /* ConjugaWidget */ = {
isa = PBXGroup;
children = (
@@ -404,20 +388,20 @@
isa = PBXGroup;
children = (
83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */,
D232CDA43CC9218D748BA121 /* ClozeView.swift */,
6B9A9F2AB21895E06989A4D5 /* FlashcardView.swift */,
711CB7539EF5887F6F7B8B82 /* FullTableView.swift */,
1F842EB5E566C74658D918BB /* HandwritingView.swift */,
20D1904DF07E0A6816134CF3 /* ListeningView.swift */,
DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */,
5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */,
1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */,
D3698CE7ACF148318615293E /* VocabReviewView.swift */,
731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */,
10C16AA6022E4742898745CE /* TypingView.swift */,
E8D95887B18216FCA71643D6 /* VocabReviewView.swift */,
8FB89F19B33894DDF27C8EC2 /* Chat */,
895E547BEFB5D0FBF676BE33 /* Lyrics */,
8A1DED0596E04DDE9536A9A9 /* Stories */,
DFD75E32A53845A693D98F48 /* Chat */,
02B2179562E54E148C98219D /* ListeningView.swift */,
A649B04B8B3C49419AD9219C /* ClozeView.swift */,
43E4D263B0AF47E401A51601 /* Stories */,
);
path = Practice;
sourceTree = "<group>";
@@ -425,9 +409,9 @@
8102F7FA5BFE6D38B2212AD3 /* Guide */ = {
isa = PBXGroup;
children = (
07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */,
3F1A6221A35699BD8065D064 /* GrammarNotesView.swift */,
8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */,
1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */,
);
path = Guide;
sourceTree = "<group>";
@@ -443,14 +427,13 @@
path = Lyrics;
sourceTree = "<group>";
};
8A1DED0596E04DDE9536A9A9 /* Stories */ = {
8FB89F19B33894DDF27C8EC2 /* Chat */ = {
isa = PBXGroup;
children = (
950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */,
2A8B6081226847E0A0A174BC /* StoryReaderView.swift */,
E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */,
648436F8326CF95777E2FA58 /* ChatLibraryView.swift */,
79576893566932D2BE207528 /* ChatView.swift */,
);
path = Stories;
path = Chat;
sourceTree = "<group>";
};
A591A3B6F1F13D23D68D7A9D = {
@@ -460,8 +443,6 @@
4B183AB0C56BC2EC302531E7 /* ConjugaWidget */,
F7D740BB7D1E23949D4C1AE5 /* Packages */,
F605D24E5EA11065FD18AF7E /* Products */,
B442229C0A26C1D531472C7D /* Frameworks */,
C77B065CF67D1F5128E10CC7 /* ConjugaUITests */,
);
sourceTree = "<group>";
};
@@ -481,14 +462,6 @@
path = Views;
sourceTree = "<group>";
};
B442229C0A26C1D531472C7D /* Frameworks */ = {
isa = PBXGroup;
children = (
E772BA9C3FF67FEA9A034B4B /* iOS */,
);
name = Frameworks;
sourceTree = "<group>";
};
BA34B77A38B698101DBBE241 /* Dashboard */ = {
isa = PBXGroup;
children = (
@@ -501,16 +474,16 @@
BE5A40BAC9DD6884C58A2096 /* Course */ = {
isa = PBXGroup;
children = (
34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */,
143D06606AE10DCA30A140C2 /* CourseQuizView.swift */,
833516C5D57F164C8660A479 /* CourseView.swift */,
631DC0A942DD57C81DECE083 /* DeckStudyView.swift */,
7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */,
7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */,
7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */,
F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */,
496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */,
39908548430FDF01D76201FB /* TextbookChapterView.swift */,
854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */,
2931634BEB33B93429CE254F /* VocabFlashcardView.swift */,
5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */,
EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */,
CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */,
);
path = Course;
sourceTree = "<group>";
@@ -522,41 +495,11 @@
path = Utilities;
sourceTree = "<group>";
};
C77B065CF67D1F5128E10CC7 /* ConjugaUITests */ = {
isa = PBXGroup;
children = (
CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */,
8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */,
8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */,
6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */,
);
name = ConjugaUITests;
path = ConjugaUITests;
sourceTree = "<group>";
};
DFD75E32A53845A693D98F48 /* Chat */ = {
isa = PBXGroup;
children = (
5667AA04211A449A9150BD28 /* ChatLibraryView.swift */,
FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */,
);
path = Chat;
sourceTree = "<group>";
};
E772BA9C3FF67FEA9A034B4B /* iOS */ = {
isa = PBXGroup;
children = (
A6153A5C7241C1AB0373AA17 /* Foundation.framework */,
);
name = iOS;
sourceTree = "<group>";
};
F605D24E5EA11065FD18AF7E /* Products */ = {
isa = PBXGroup;
children = (
16C1F74196C3C5628953BE3F /* Conjuga.app */,
9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */,
27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -594,24 +537,6 @@
productReference = 16C1F74196C3C5628953BE3F /* Conjuga.app */;
productType = "com.apple.product-type.application";
};
C6CC399BFD5A2574CB9956B4 /* ConjugaUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F454EA7279A44C5E151F71BA /* Build configuration list for PBXNativeTarget "ConjugaUITests" */;
buildPhases = (
66589E8F78971725CA2066ED /* Sources */,
C5C1BB325D49EE6ED3AC3D5F /* Frameworks */,
425DC31DA6EF2C4C7A873DAA /* Resources */,
);
buildRules = (
);
dependencies = (
04C7E3C8079DE56024C2154E /* PBXTargetDependency */,
);
name = ConjugaUITests;
productName = ConjugaUITests;
productReference = 27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = EA7E12CF28EB750C2B8BB2F1 /* Build configuration list for PBXNativeTarget "ConjugaWidgetExtension" */;
@@ -664,25 +589,16 @@
548B46ED3C40F5F28A5ADCC6 /* XCLocalSwiftPackageReference "SharedModels" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = F605D24E5EA11065FD18AF7E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
96127FACA68AE541F5C0F8BC /* Conjuga */,
F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */,
C6CC399BFD5A2574CB9956B4 /* ConjugaUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
425DC31DA6EF2C4C7A873DAA /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B74A8384221C70A670B902D8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -690,8 +606,6 @@
F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */,
CF9E48ADF0501FB79F3DDB7B /* conjuga_data.json in Resources */,
2B5B2D63DC9C290F66890A4A /* course_data.json in Resources */,
7A1B2C3D4E5F60718293A4B5 /* textbook_data.json in Resources */,
7A1B2C3D4E5F60718293A4B6 /* textbook_vocab.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -704,22 +618,28 @@
files = (
97EFCF6724CE59DC4F0274FD /* AchievementService.swift in Sources */,
261E582449BED6EF41881B04 /* AdaptiveContainer.swift in Sources */,
48967E05C65E32F7082716CD /* AnswerChecker.swift in Sources */,
CAC69045B74249F121643E88 /* AnswerReviewView.swift in Sources */,
B48C0015BE53279B0631C2D7 /* ChatLibraryView.swift in Sources */,
8BD4B5A2DDDD4BE4B4A94962 /* ChatView.swift in Sources */,
9F0ACDC1F4ACB1E0D331283D /* CheckpointExamView.swift in Sources */,
04C74D9E0ED84BF785A8331C /* ClozeView.swift in Sources */,
C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */,
ABBE5080E254D1D3E3465E40 /* ConversationService.swift in Sources */,
C3851F960C1162239DC2F935 /* CourseQuizView.swift in Sources */,
8C43F09F52EA9B537EA27E43 /* CourseReviewStore.swift in Sources */,
F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */,
7A1B2C3D4E5F60718293AA01 /* TextbookChapterListView.swift in Sources */,
7A1B2C3D4E5F60718293AA02 /* TextbookChapterView.swift in Sources */,
7A1B2C3D4E5F60718293AA03 /* TextbookExerciseView.swift in Sources */,
7A1B2C3D4E5F60718293AA04 /* AnswerChecker.swift in Sources */,
1C2636790E70B6BC7FFCC904 /* DailyLog.swift in Sources */,
BB48230C3B26EA6E84D2D823 /* DailyProgressRing.swift in Sources */,
35A0F6E7124D989312721F7D /* DashboardView.swift in Sources */,
C8C3880535008764B7117049 /* DataLoader.swift in Sources */,
84CCBAE22A9E0DA27AE28723 /* DeckStudyView.swift in Sources */,
90B76E34F195223580F7CCCF /* DictionaryService.swift in Sources */,
14242FD1F500D296D41E927C /* FeatureReferenceView.swift in Sources */,
D4DDE25FB2DAD315370AFB74 /* FlashcardView.swift in Sources */,
A9959AE6C87B4AD21554E401 /* FullTableView.swift in Sources */,
8B516215E0842189DEA0DBB1 /* GrammarExercise.swift in Sources */,
8DC1CB93333F94C5297D33BF /* GrammarExerciseView.swift in Sources */,
377C4AA000CE9A0D8CC43DA9 /* GrammarNote.swift in Sources */,
F84706B47A2156B2138FB8D5 /* GrammarNotesView.swift in Sources */,
760628EFE1CF191CE2FC07DC /* GuideView.swift in Sources */,
@@ -727,6 +647,7 @@
E7BFEE9A90E1300EFF5B1F32 /* HandwritingRecognizer.swift in Sources */,
33E885EB38C3BB0CB058871A /* HandwritingView.swift in Sources */,
28D2F489F1927BCCC2B56086 /* IrregularHighlightText.swift in Sources */,
A7DF435F99E66E067F2B33E1 /* ListeningView.swift in Sources */,
519E68D2DF4C80AB96058C0D /* LyricsConfirmationView.swift in Sources */,
B4603AA6EFB134794AA39BF4 /* LyricsLibraryView.swift in Sources */,
615D3128ED6E84EF59BB5AA3 /* LyricsReaderView.swift in Sources */,
@@ -739,6 +660,7 @@
352A5BAA6E406AA5850653A4 /* PracticeSessionService.swift in Sources */,
1A230C01A045F0C095BFBD35 /* PracticeView.swift in Sources */,
C0BAEF49A6270D8F64CF13D6 /* PracticeViewModel.swift in Sources */,
0D0D3B5CC128D1A1D1252282 /* PronunciationService.swift in Sources */,
53A0AC57EAC44B676C997374 /* QuizType.swift in Sources */,
DF82C2579F9889DDB06362CC /* ReferenceStore.swift in Sources */,
FC7873F97017532C215DAD34 /* ReviewCard.swift in Sources */,
@@ -748,39 +670,33 @@
60E86BABE2735E2052B99DF3 /* SettingsView.swift in Sources */,
D3FFE73A5AD27F1759F50727 /* SpeechService.swift in Sources */,
9D9FD3853C5C969C62AE9999 /* StartupCoordinator.swift in Sources */,
20B71403A8D305C29C73ADA2 /* StemChangeConjugationView.swift in Sources */,
E814A9CF1067313F74B509C6 /* StoreInspector.swift in Sources */,
ACE9D8B3116757B5D6F0F766 /* StoryGenerator.swift in Sources */,
61328552866DE185B15011A9 /* StoryLibraryView.swift in Sources */,
0AD63CAED7C568590A16E879 /* StoryQuizView.swift in Sources */,
CC886125F8ECE72D1AAD4861 /* StoryReaderView.swift in Sources */,
36F92EBAEB0E5F2B010401EF /* StreakCalendarView.swift in Sources */,
5EE41911F3D17224CAB359ED /* StudyTimerService.swift in Sources */,
6ED2AC2CAA54688161D4B920 /* SyncStatusMonitor.swift in Sources */,
D40B4E919DE379C50265CA9F /* SyncToast.swift in Sources */,
AAC6F85A1C3B6C1186E1656A /* TenseEndingTable.swift in Sources */,
0A89DCC82BE11605CB866DEF /* TenseInfo.swift in Sources */,
46943ACFABF329DE1CBFC471 /* TensePill.swift in Sources */,
D7456B289D135CEB3A15122B /* TestResult.swift in Sources */,
81E4DB9F64F3FF3AB8BCB03A /* TextbookChapterListView.swift in Sources */,
B10A324C06F0957DDE2233F8 /* TextbookChapterView.swift in Sources */,
354631F309E625046A3A436B /* TextbookExerciseView.swift in Sources */,
27BA7FA9356467846A07697D /* TypingView.swift in Sources */,
943A94A8C71919F3EFC0E8FA /* UserProgress.swift in Sources */,
50E0095A23E119D1AB561232 /* VerbDetailView.swift in Sources */,
BA3DE2DA319AA3B572C19E11 /* VerbExampleCache.swift in Sources */,
4C577CF6B137D0A32759A169 /* VerbExampleGenerator.swift in Sources */,
81FA7EBCF18F0AAE0BF385C3 /* VerbListView.swift in Sources */,
4005E258FDF03C8B3A0D53BD /* VocabFlashcardView.swift in Sources */,
78FE99C5D511737B6877EDD5 /* VocabReviewView.swift in Sources */,
6BB4B0A655E6CB6F82D81B5A /* WeekTestView.swift in Sources */,
968D626462B0ADEC8D7D56AA /* CheckpointExamView.swift in Sources */,
E99473B7DF9BCAE150E9D1E1 /* WidgetDataService.swift in Sources */,
DDF58F3899FC4B92BF6587D2 /* StudyTimerService.swift in Sources */,
8C1E4E7F36D64EFF8D092AC8 /* StoryGenerator.swift in Sources */,
4C2649215B81470195F38ED0 /* StoryLibraryView.swift in Sources */,
8E3D8E8254CF4213B9D9FAD3 /* StoryReaderView.swift in Sources */,
12D2C9311D5C4764B48B1754 /* StoryQuizView.swift in Sources */,
8D7CA0F4496B44C28CD5EBD5 /* DictionaryService.swift in Sources */,
3EC2A2F4B9C24B029DA49C40 /* VocabReviewView.swift in Sources */,
53908E41767B438C8BD229CD /* ClozeView.swift in Sources */,
65ABC39F35804C619DAB3200 /* GrammarExercise.swift in Sources */,
B73F6EED00304B718C6FEFFA /* GrammarExerciseView.swift in Sources */,
EA07DB964C8940F69C14DE2C /* PronunciationService.swift in Sources */,
4DCC5CC233DE4701A12FD7EB /* ListeningView.swift in Sources */,
35D6404C60C249D5995AD895 /* ConversationService.swift in Sources */,
C8AF0931F7FD458C80B6EC0D /* ChatLibraryView.swift in Sources */,
6CCC8D51F5524688A4BC5AF8 /* ChatView.swift in Sources */,
8510085D78E248D885181E80 /* FeatureReferenceView.swift in Sources */,
943728CD3E65FE6CCADB05EE /* StemChangeConjugationView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -798,26 +714,9 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
66589E8F78971725CA2066ED /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
96A3E5FA8EC63123D97365E1 /* TextbookFlowUITests.swift in Sources */,
F7E459C46F25A8A45D7E0DFB /* AllChaptersScreenshotTests.swift in Sources */,
1B0B3B2C771AD72E25B3493C /* StemChangeToggleTests.swift in Sources */,
BC662C36AC503E00A977CEC1 /* VocabGridTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
04C7E3C8079DE56024C2154E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = Conjuga;
target = 96127FACA68AE541F5C0F8BC /* Conjuga */;
targetProxy = 6E1F966015DA38BD4E3CE8AF /* PBXContainerItemProxy */;
};
0B370CF10B68E386093E5BB2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */;
@@ -966,24 +865,6 @@
};
name = Release;
};
A923186E44A25A8086B27A34 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = V3PF3M6B6U;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
PRODUCT_BUNDLE_IDENTIFIER = com.conjuga.app.uitests;
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Conjuga;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
B9223DC55BB69E9AB81B59AE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1049,23 +930,6 @@
};
name = Debug;
};
DB8C0F513F77A50F2EF2D561 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_WEAK = NO;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = V3PF3M6B6U;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
PRODUCT_BUNDLE_IDENTIFIER = com.conjuga.app.uitests;
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Conjuga;
};
name = Debug;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1096,15 +960,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
F454EA7279A44C5E151F71BA /* Build configuration list for PBXNativeTarget "ConjugaUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A923186E44A25A8086B27A34 /* Release */,
DB8C0F513F77A50F2EF2D561 /* Debug */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */

View File

@@ -53,16 +53,6 @@
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C77B065CF67D1F5128E10CC7"
BuildableName = "ConjugaUITests.xctest"
BlueprintName = "ConjugaUITests"
ReferencedContainer = "container:Conjuga.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<CommandLineArguments>
</CommandLineArguments>

View File

@@ -40,6 +40,7 @@ struct ConjugaApp: App {
@State private var syncMonitor = SyncStatusMonitor()
@State private var studyTimer = StudyTimerService()
@State private var dictionary = DictionaryService()
@State private var verbExampleCache = VerbExampleCache()
let localContainer: ModelContainer
let cloudContainer: ModelContainer
@@ -113,6 +114,7 @@ struct ConjugaApp: App {
.environment(\.cloudModelContextProvider, { cloudContainer.mainContext })
.environment(studyTimer)
.environment(dictionary)
.environment(verbExampleCache)
.task {
let needsSeed = await DataLoader.needsSeeding(container: localContainer)
if needsSeed {

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.conjuga.app.refresh</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@@ -22,21 +26,13 @@
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.education</string>
<key>UILaunchScreen</key>
<dict/>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Conjuga uses speech recognition to check your Spanish pronunciation and transcribe what you say during listening exercises.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Conjuga needs microphone access to record your voice for pronunciation practice.</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.conjuga.app.refresh</string>
</array>
<key>UILaunchScreen</key>
<dict/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>

View File

@@ -0,0 +1,65 @@
import Foundation
import SharedModels
/// Disk-backed cache for verb example sentences (Issue #27). One JSON file
/// in the Caches directory keyed by verb id; lazy-loaded on first access and
/// write-through on every generation. Matches DictionaryService's disk pattern.
///
/// Cache eviction by the OS is acceptable because contents are regenerable.
@MainActor
@Observable
final class VerbExampleCache {
private var store: [Int: [VerbExample]] = [:]
private var isLoaded = false
init() {}
// MARK: - Public API
/// Look up cached examples for a verb; returns nil on miss.
/// Safe to call before `loadIfNeeded()`; it triggers the disk load itself.
func examples(for verbId: Int) -> [VerbExample]? {
loadIfNeeded()
return store[verbId]
}
/// Store newly generated examples and persist to disk.
func setExamples(_ examples: [VerbExample], for verbId: Int) {
loadIfNeeded()
store[verbId] = examples
save()
}
// MARK: - Disk I/O
private static var cacheURL: URL {
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent("verb_examples.json")
}
private func loadIfNeeded() {
guard !isLoaded else { return }
defer { isLoaded = true }
guard let data = try? Data(contentsOf: Self.cacheURL),
let decoded = try? JSONDecoder().decode([String: [VerbExample]].self, from: data)
else { return }
// Persisted with String keys because JSON object keys are strings;
// convert back to Int for in-memory lookup.
var rebuilt: [Int: [VerbExample]] = [:]
for (key, value) in decoded {
if let id = Int(key) {
rebuilt[id] = value
}
}
store = rebuilt
}
private func save() {
let serialized = Dictionary(uniqueKeysWithValues: store.map { (String($0.key), $0.value) })
guard let data = try? JSONEncoder().encode(serialized) else { return }
try? data.write(to: Self.cacheURL)
}
}

View File

@@ -0,0 +1,78 @@
import Foundation
import FoundationModels
import SharedModels
/// Generates a set of example sentences for a single verb, one per core tense
/// (Issue #27). Mirrors the StoryGenerator pattern: @Generable response types,
/// a static availability flag, and a single generate(...) entry point.
@MainActor
struct VerbExampleGenerator {
// MARK: - Generable Types
@Generable
struct GeneratedExampleSet {
@Guide(
description: "Six example sentences, one per tense in the exact order requested. Each sentence must actually use the target verb conjugated in that tense.",
.count(6)
)
var examples: [GeneratedExample]
}
@Generable
struct GeneratedExample {
@Guide(description: "The tense id this sentence demonstrates. Must match one of the ids provided in the prompt exactly (e.g. ind_presente).")
var tenseId: String
@Guide(description: "A natural Spanish sentence, 6-14 words, that uses the target verb in the specified tense. For imperative tenses use tú or nosotros — never yo.")
var spanish: String
@Guide(description: "An accurate, idiomatic English translation of the Spanish sentence.")
var english: String
}
// MARK: - Generation
/// Generate one example per tense in `tenseIds`. Returns the examples in the
/// same order as `tenseIds`, filling in placeholders for any the model skipped.
static func generate(
verbInfinitive: String,
verbEnglish: String,
tenseIds: [String]
) async throws -> [VerbExample] {
let tenseList = tenseIds
.compactMap { id in TenseInfo.find(id).map { "\(id) (\($0.english))" } }
.joined(separator: ", ")
let session = LanguageModelSession(instructions: """
You are a Spanish language teacher writing short example sentences for a learner.
The learner is studying the verb "\(verbInfinitive)" (to \(verbEnglish)).
Write one sentence per requested tense. Each sentence must:
- Actually conjugate "\(verbInfinitive)" in that tense (not just mention it).
- Be 6-14 words, natural and everyday.
- Use vocabulary appropriate for intermediate learners.
- Vary subjects and contexts across the set; do not reuse the same subject twice.
For imperative tenses, address "" or "nosotros" — never "yo".
""")
let prompt = """
Write example sentences for "\(verbInfinitive)" in these tenses, in this order:
\(tenseList)
Return one GeneratedExample per tense with the matching tenseId, spanish, and english.
"""
let response = try await session.respond(to: prompt, generating: GeneratedExampleSet.self)
// Map by tenseId and return in the caller's requested order so the UI
// renders a predictable sequence even if the model shuffles its output.
let byTense = Dictionary(uniqueKeysWithValues: response.content.examples.map {
($0.tenseId, VerbExample(tenseId: $0.tenseId, spanish: $0.spanish, english: $0.english))
})
return tenseIds.compactMap { byTense[$0] }
}
static var isAvailable: Bool {
SystemLanguageModel.default.availability == .available
}
}

View File

@@ -4,10 +4,31 @@ import SwiftData
struct VerbDetailView: View {
@Environment(\.modelContext) private var modelContext
@Environment(VerbExampleCache.self) private var exampleCache
@State private var speechService = SpeechService()
let verb: Verb
@State private var selectedTense: TenseInfo = TenseInfo.all[0]
@State private var examples: [VerbExample] = []
@State private var examplesState: ExamplesState = .idle
private enum ExamplesState: Equatable {
case idle
case loading
case loaded
case unavailable
case failed(String)
}
private static let exampleTenseIds: [String] = [
TenseID.ind_presente.rawValue,
TenseID.ind_preterito.rawValue,
TenseID.ind_imperfecto.rawValue,
TenseID.ind_futuro.rawValue,
TenseID.subj_presente.rawValue,
TenseID.imp_afirmativo.rawValue,
]
private var formsForTense: [VerbForm] {
ReferenceStore(context: modelContext).fetchForms(verbId: verb.id, tenseId: selectedTense.id)
}
@@ -66,6 +87,8 @@ struct VerbDetailView: View {
} header: {
Text("Conjugation")
}
examplesSection
}
.navigationTitle(verb.infinitive)
.toolbar {
@@ -78,6 +101,94 @@ struct VerbDetailView: View {
.tint(.secondary)
}
}
.task(id: verb.id) {
await loadExamples()
}
}
// MARK: - Examples
@ViewBuilder
private var examplesSection: some View {
Section {
switch examplesState {
case .idle, .loading:
HStack(spacing: 10) {
ProgressView()
Text("Generating examples…")
.font(.caption)
.foregroundStyle(.secondary)
}
case .unavailable:
Label("Examples require Apple Intelligence on this device.", systemImage: "sparkles")
.font(.caption)
.foregroundStyle(.secondary)
case .failed(let message):
Label(message, systemImage: "exclamationmark.triangle")
.font(.caption)
.foregroundStyle(.secondary)
case .loaded:
if examples.isEmpty {
Label("No examples available.", systemImage: "text.quote")
.font(.caption)
.foregroundStyle(.secondary)
} else {
ForEach(Array(examples.enumerated()), id: \.offset) { _, example in
VStack(alignment: .leading, spacing: 4) {
if let info = TenseInfo.find(example.tenseId) {
Text(info.english)
.font(.caption2.weight(.semibold))
.foregroundStyle(.tint)
}
Text(example.spanish)
.font(.body)
.italic()
Text(example.english)
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 2)
}
}
}
} header: {
Text("Examples")
}
}
private func loadExamples() async {
// Reset state when navigating between verbs via NavigationSplitView.
examples = []
examplesState = .idle
if let cached = exampleCache.examples(for: verb.id), !cached.isEmpty {
examples = cached
examplesState = .loaded
return
}
guard VerbExampleGenerator.isAvailable else {
examplesState = .unavailable
return
}
examplesState = .loading
do {
let generated = try await VerbExampleGenerator.generate(
verbInfinitive: verb.infinitive,
verbEnglish: verb.english,
tenseIds: Self.exampleTenseIds
)
guard !generated.isEmpty else {
examplesState = .failed("Could not generate examples.")
return
}
exampleCache.setExamples(generated, for: verb.id)
examples = generated
examplesState = .loaded
} catch {
examplesState = .failed("Could not generate examples.")
}
}
}
@@ -86,4 +197,5 @@ struct VerbDetailView: View {
VerbDetailView(verb: Verb(id: 1, infinitive: "hablar", english: "to speak", rank: 1, ending: "ar", reflexive: 0, level: "basic"))
}
.modelContainer(for: [Verb.self, VerbForm.self], inMemory: true)
.environment(VerbExampleCache())
}

View File

@@ -0,0 +1,17 @@
import Foundation
/// A single Spanish / English example sentence pair for a verb at a specific tense.
/// Used by the Verb detail view (Issue #27). Generated at runtime via Foundation
/// Models and cached to disk; shape is intentionally simple Codable for easy
/// JSON persistence and cross-module sharing.
public struct VerbExample: Codable, Hashable, Sendable {
public let tenseId: String
public let spanish: String
public let english: String
public init(tenseId: String, spanish: String, english: String) {
self.tenseId = tenseId
self.spanish = spanish
self.english = english
}
}