Skip to content

Defer audio example resolution out of practice selection (#7702)#7708

Merged
wcjord merged 1 commit into
mainfrom
7702-practice-loading-doc-and-stagger
Jul 15, 2026
Merged

Defer audio example resolution out of practice selection (#7702)#7708
wcjord merged 1 commit into
mainfrom
7702-practice-loading-doc-and-stagger

Conversation

@wcjord

@wcjord wcjord commented Jul 15, 2026

Copy link
Copy Markdown

Closes #7702.

Problem

Standalone practice loaded slowly and inconsistently, even on fresh accounts. Root cause: VocabAudioTargetGenerator resolved an example message per candidate lemma — a Matrix event fetch that can hit the server — serially, during target selection, before the first exercise could render. How many candidates it walked before finding enough audio-capable examples varied, hence the inconsistency. Exercise content was already eager-prefetched; only selection blocked.

Fix

  • Selection is now cheap: audio candidates are picked by a local check that the lemma has a message-bearing use (eventId + roomId); no example resolution, no network. The ~50/50 audio/meaning mix is still decided here, by count.
  • Resolution moved to generation (VocabAudioPracticeExerciseGenerator), inside the existing eager background queue — every exercise is still proactively loaded, it just no longer gates first paint.
  • Meaning fallback: an audio target whose example can't resolve at generation becomes a lemmaMeaning exercise for the same lemma, so a deferred failure never leaves a gap.

Docs

practice-exercises.instructions.md was stale (all lib/pangea/... paths dead from a move) and silent on the load sequence. Fixed the paths, added a Loading & Generation Sequencing section, and split the "never blocks on network" contract into message vs standalone. The standalone contract now states: selection stays cheap and resolves no example messages/audio/events; the first exercise gates the UI, the rest are prefetched eagerly in the background.

Testing

  • New unit test (vocab_audio_target_selection_test.dart) proves selection needs no MatrixState (the front-load is gone) and skips lemmas with no message-bearing use.
  • Practice scorer suite green.
  • Verified locally: Analytics -> Practice Vocab reaches the first exercise without the long/variable wait.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved vocabulary audio practice generation by resolving audio examples when needed.
    • Automatically falls back to meaning-based practice when audio content cannot be generated.
    • Practice target selection now loads faster by deferring audio content resolution until generation.
    • Added concurrent exercise generation with loading placeholders for smoother practice sessions.
  • Bug Fixes

    • Prevented missing audio content from creating incomplete exercises.
    • Ensured generated exercises retain associated message, room, and event details.
  • Documentation

    • Updated practice exercise documentation to reflect current routes, session flow, loading behavior, and fallback handling.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Practice documentation and audio practice generation now use local target selection followed by deferred content generation. Audio examples are resolved during generation, with lemma-meaning fallback when resolution fails, while selection tests cover metadata eligibility and unresolved audio targets.

Changes

Audio Practice Flow

Layer / File(s) Summary
Practice route and lifecycle contracts
.github/instructions/practice-exercises.instructions.md
Documentation maps current practice routes, session ownership, request routing, two-phase loading, and audio-to-meaning fallback behavior.
Local audio target selection
lib/routes/analytics/construct_analytics/practice/vocab_audio_target_generator.dart, test/pangea/analytics_practice/vocab_audio_target_selection_test.dart
Target selection checks local eventId and roomId metadata, deduplicates by lemma, defers audio resolution, and tests eligible and ineligible constructs.
Deferred audio exercise generation
lib/routes/analytics/construct_analytics/practice/vocab_audio_practice_exercise_generator.dart
Generation resolves audio examples, derives tokens and answer data from the resolved message, and converts unresolved audio targets into meaning requests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PracticeSessionController
  participant VocabAudioTargetGenerator
  participant VocabAudioPracticeExerciseGenerator
  participant MatrixState
  participant ExampleMessageUtil

  PracticeSessionController->>VocabAudioTargetGenerator: select local audio targets
  VocabAudioTargetGenerator-->>PracticeSessionController: return lemma targets
  PracticeSessionController->>VocabAudioPracticeExerciseGenerator: generate exercise
  VocabAudioPracticeExerciseGenerator->>MatrixState: fetch construct uses
  MatrixState-->>VocabAudioPracticeExerciseGenerator: return analytics uses
  VocabAudioPracticeExerciseGenerator->>ExampleMessageUtil: resolve audio example
  ExampleMessageUtil-->>VocabAudioPracticeExerciseGenerator: return audio example or failure
  alt audio example unavailable
    VocabAudioPracticeExerciseGenerator-->>PracticeSessionController: generate lemma meaning exercise
  else audio example available
    VocabAudioPracticeExerciseGenerator-->>PracticeSessionController: generate audio exercise
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the repository's required template sections and checklist. Add the template items: contributing guidelines, platform testing checkboxes, and the accessibility section, plus any required testing notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: deferring audio example resolution out of practice selection.
Linked Issues check ✅ Passed The changes directly address #7702 by removing selection-time network work and making loading faster and more consistent.
Out of Scope Changes check ✅ Passed The code, docs, and tests all support the stated loading-performance fix and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 7702-practice-loading-doc-and-stagger

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/pangea/analytics_practice/vocab_audio_target_selection_test.dart (1)

62-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover uses with only one metadata identifier.

Add cases for eventId-only and roomId-only uses. The current all-null case would still pass if eligibility accidentally changed from && to ||.

As per path instructions, tests should “verify the explicit selection/loading contract.”

Proposed coverage
     test('skips a lemma whose uses point at no message (no eventId/roomId), so '
         'an audio example could never resolve', () async {
-      final constructs = [
-        _construct('perro', [_use(lemma: 'perro')]),
-      ];
-
-      final targets = await VocabAudioTargetGenerator.get(constructs);
-      expect(targets, isEmpty);
+      for (final use in [
+        _use(lemma: 'perro'),
+        _use(lemma: 'perro', eventId: r'$evt1'),
+        _use(lemma: 'perro', roomId: '!room1'),
+      ]) {
+        final targets = await VocabAudioTargetGenerator.get([
+          _construct('perro', [use]),
+        ]);
+        expect(targets, isEmpty);
+      }
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/pangea/analytics_practice/vocab_audio_target_selection_test.dart` around
lines 62 - 70, Add test cases alongside the existing no-metadata test for uses
containing only an eventId and only a roomId, and assert each produces no audio
targets. Use the existing construct/use helpers and keep the assertions focused
on the explicit selection/loading eligibility contract, ensuring both metadata
identifiers are required.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/instructions/practice-exercises.instructions.md:
- Around line 138-139: Update the Activity Generation section around the
remaining old references to use the new lib/routes paths and the
MessagePracticeExerciseRequest and PracticeExerciseModel contract, matching the
flow documented in the numbered steps. Remove all references to the obsolete
lib/pangea paths, MessageActivityRequest, and PracticeActivityModel so the
section is consistent.

In
`@lib/routes/analytics/construct_analytics/practice/vocab_audio_practice_exercise_generator.dart`:
- Around line 23-26: Update the audio-example resolution flow in get so expected
failures from getConstructUses or getAudioExampleMessage are caught, logged, and
converted to null before the existing meaning fallback runs. Ensure
_asMeaningRequest is invoked for both null results and resolver exceptions,
while preserving successful audio generation; add coverage for a throwing
resolver.

---

Nitpick comments:
In `@test/pangea/analytics_practice/vocab_audio_target_selection_test.dart`:
- Around line 62-70: Add test cases alongside the existing no-metadata test for
uses containing only an eventId and only a roomId, and assert each produces no
audio targets. Use the existing construct/use helpers and keep the assertions
focused on the explicit selection/loading eligibility contract, ensuring both
metadata identifiers are required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d8f62e8-c7e8-4979-98d4-b83a2d7a8d1f

📥 Commits

Reviewing files that changed from the base of the PR and between 604a7f2 and 2d8dc4b.

📒 Files selected for processing (4)
  • .github/instructions/practice-exercises.instructions.md
  • lib/routes/analytics/construct_analytics/practice/vocab_audio_practice_exercise_generator.dart
  • lib/routes/analytics/construct_analytics/practice/vocab_audio_target_generator.dart
  • test/pangea/analytics_practice/vocab_audio_target_selection_test.dart

Comment on lines +138 to +139
3. For each target, a [`MessagePracticeExerciseRequest`](../../lib/routes/chat/toolbar/practice_exercises/message_practice_exercise_request.dart) is routed by [`PracticeRepo`](../../lib/routes/chat/toolbar/practice_exercises/practice_generation_repo.dart) to the appropriate generator.
4. The generator returns a [`PracticeExerciseModel`](../../lib/routes/chat/toolbar/practice_exercises/practice_exercise_model.dart) subclass with choices and answers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Finish updating the Activity Generation section to the new contract.

These lines document the new routes and MessagePracticeExerciseRequest/PracticeExerciseModel, but Lines 197-210 still reference the old lib/pangea/ paths, MessageActivityRequest, and PracticeActivityModel. Update that section to avoid conflicting flow documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/instructions/practice-exercises.instructions.md around lines 138 -
139, Update the Activity Generation section around the remaining old references
to use the new lib/routes paths and the MessagePracticeExerciseRequest and
PracticeExerciseModel contract, matching the flow documented in the numbered
steps. Remove all references to the obsolete lib/pangea paths,
MessageActivityRequest, and PracticeActivityModel so the section is consistent.

Comment on lines +23 to +26
final audioExample =
req.audioExampleMessage ?? await _resolveAudioExample(req);
if (audioExample == null) {
return VocabMeaningPracticeExerciseGenerator.get(_asMeaningRequest(req));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make resolver errors trigger the meaning fallback.

The fallback handles only null. An exception from getConstructUses or getAudioExampleMessage escapes get, aborting generation despite the documented “never leaves a gap” contract. Convert expected lookup/event-resolution failures to null with logging, then invoke _asMeaningRequest; cover a throwing resolver in tests.

Also applies to: 102-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/routes/analytics/construct_analytics/practice/vocab_audio_practice_exercise_generator.dart`
around lines 23 - 26, Update the audio-example resolution flow in get so
expected failures from getConstructUses or getAudioExampleMessage are caught,
logged, and converted to null before the existing meaning fallback runs. Ensure
_asMeaningRequest is invoked for both null results and resolver exceptions,
while preserving successful audio generation; add coverage for a throwing
resolver.

@wcjord wcjord merged commit 11ef508 into main Jul 15, 2026
6 checks passed
@wcjord wcjord deleted the 7702-practice-loading-doc-and-stagger branch July 15, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent Practice Loading Times

1 participant