Skip to content

fix(quests): resolve Mission star rollups per course, not across joined courses#7772

Merged
wcjord merged 2 commits into
mainfrom
fix/per-course-mission-rollup
Jul 20, 2026
Merged

fix(quests): resolve Mission star rollups per course, not across joined courses#7772
wcjord merged 2 commits into
mainfrom
fix/per-course-mission-rollup

Conversation

@wcjord

@wcjord wcjord commented Jul 20, 2026

Copy link
Copy Markdown

What

Resolve the per-Mission star rollup per course outline instead of unioning every joined course's outline into one global rollup.

Why

Closes #7771

A Mission pinned to a single 4-star activity was displaying 0/8 on the course panel. Missions are a shared catalog reused across quests, so a second joined course carrying the same Mission with different activities was being counted into this course's clamp and star total — and unpinned activities re-entered through the union, defeating the pin from #7748.

Pre-existing; pinning made it visible. Design and rationale: quests.instructions.md ("One shared resolver", "Star display on the course panel") — updated in this PR to record the per-course scoping rule agreed with Will.

What changed

  • CourseLoOutline carries a courseId; QuestProgress carries its own rollup. No cross-quest rollup exists anymore.
  • ProgressionResolution.forCourse(id) is how a per-course surface reads its own numbers; questStars takes a courseId.
  • missionGradient judges "satisfied" against each quest's own rollup, so a Mission finished in one course no longer silences it in another. Cross-quest accumulation for the map's band is unchanged.
  • The course panel (objective list + header progress bar) scopes through the loader's course.
  • Two behavior notes: an activity two courses genuinely share still counts in each (each outline lists it, so nothing needs merging); and the old min-merge of disagreeing earnableStars across outlines is gone — each course now clamps against its own outline's data. Outlines carry the same plan, so they shouldn't disagree.
  • In-path: moved missionGradient's doc comment off questStars, where it had been misattached.

Riding along: unbreak flutter analyze on main

Second commit, unrelated to the quests change, folded in at Will's direction.

code_tests was already failing on main: flutter analyze exits non-zero on any issue including infos, and test/pangea/courses/course_catalog_parsing_test.dart:76 tripped use_null_aware_elements. Confirmed by running analyze on a clean origin/main worktree at the pinned SDK — same single issue, exit 1. It arrived with #7770, which merged with code_tests already failing, so every branch cut since then inherited a red gate.

Fix is one line: if (courseId != null) 'course_id': courseId'course_id': ?courseId. Identical semantics — the entry is omitted when the value is null either way.

Testing

Verified on the pinned SDK (Flutter 3.41.4 via fvm, matching CI):

  • fvm flutter test — full suite, 1304 passed.
  • fvm dart format lib/ test/ --set-exit-if-changed — clean.
  • fvm flutter analyzeNo issues found, exit 0 (was exit 1 on main before the ride-along fix).
  • pubspec.lock unchanged.
  • course_catalog_parsing_test.dart run on its own — 12 passed, so the null-aware rewrite preserved behavior.
  • New test/pangea/quests/per_course_mission_rollup_test.dart covers the scoping rules: own-activity clamp, no cross-course star credit, shared-activity double-count, per-quest anchors, scoped questStars, and that the map's cross-quest band accumulation still works.
  • Updated quest_progression_resolver_test, quest_star_summary_test, quest_outline_restriction_test, world_map_ranking_test, joined_objective_cache_test for the scoped API.
  • Not verified in a running client — staging smoke after deploy, per the issue's TO TEST.

Deploy Notes

None.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Quest progression now stores mission rollups per course, scopes star summaries and course-panel progress to the active course, and preserves cross-quest gradient accumulation. Course identity is added to outlines, with expanded resolver, loader, UI, documentation, and test coverage.

Changes

Per-course quest progression

Layer / File(s) Summary
Course-aware resolver contract and scoring
lib/features/quests/lo_progression.dart, lib/features/quests/quest_progression_resolver.dart, lib/features/quests/repo/quest_repo.dart
Course outlines and resolved quests now carry courseId; mission rollups, star summaries, and satisfaction checks are resolved per course.
Course-panel progress integration
lib/features/quests/quest_objectives_loader.dart, lib/routes/courses/course_objectives/course_objectives_view.dart, lib/routes/world/joined_objective_cache.dart
The loader exposes course-scoped progress APIs, and the course panel uses them for progress availability and mission display.
Progression regression coverage
test/pangea/quest_progression_resolver_test.dart, test/pangea/quest_star_summary_test.dart, test/pangea/quests/quest_outline_restriction_test.dart, test/pangea/world_map_ranking_test.dart, test/pangea/joined_objective_cache_test.dart
Existing tests now construct course-aware outlines and validate scoped rollups, thresholds, anchors, summaries, restrictions, and gradients.
Dedicated per-course behavior coverage
test/pangea/quests/per_course_mission_rollup_test.dart
New tests cover isolated course crediting, thresholds, satisfaction, unresolved courses, star summaries, and cross-quest gradients.
Progression documentation updates
.github/instructions/quests.instructions.md
Instructions now describe per-course totals, course-scoped star display, and course-scoped activity pinning.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CourseOutline
  participant ProgressionResolver
  participant ObjectivesLoader
  participant CourseObjectivesView
  CourseOutline->>ProgressionResolver: provide courseId and mission outline
  ProgressionResolver->>ProgressionResolver: build per-course mission rollup
  ObjectivesLoader->>ProgressionResolver: request course-scoped stars and mission progress
  ProgressionResolver-->>ObjectivesLoader: return resolved course data
  ObjectivesLoader-->>CourseObjectivesView: expose progress for the active course
Loading

Possibly related issues

Possibly related PRs

  • pangeachat/client#7638 — Introduces the loader and course-objectives integration extended here with course-scoped resolution.
  • pangeachat/client#7671 — Modifies resolver rollups and threshold computation that this change scopes by course.
  • pangeachat/client#7750 — Wires course-specific activity pinning through the same loader and outline-resolution path.

Suggested reviewers: ggurdin

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving Mission star rollups to per-course resolution instead of combining joined courses.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/per-course-mission-rollup

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: 1

🤖 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/quests.instructions.md:
- Line 17: Update the sentence describing consumer behavior in the quest anchor
documentation to replace “consumers preference” with the grammatically correct
wording “consumers prefer still-unsatisfied Missions,” without changing the
surrounding meaning.
🪄 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: 946cdc0e-b9b2-418b-8c0c-dc82cf62c49c

📥 Commits

Reviewing files that changed from the base of the PR and between 65202d6 and 0546ec8.

📒 Files selected for processing (13)
  • .github/instructions/quests.instructions.md
  • lib/features/quests/lo_progression.dart
  • lib/features/quests/quest_objectives_loader.dart
  • lib/features/quests/quest_progression_resolver.dart
  • lib/features/quests/repo/quest_repo.dart
  • lib/routes/courses/course_objectives/course_objectives_view.dart
  • lib/routes/world/joined_objective_cache.dart
  • test/pangea/joined_objective_cache_test.dart
  • test/pangea/quest_progression_resolver_test.dart
  • test/pangea/quest_star_summary_test.dart
  • test/pangea/quests/per_course_mission_rollup_test.dart
  • test/pangea/quests/quest_outline_restriction_test.dart
  • test/pangea/world_map_ranking_test.dart

- **The per-Mission star rollup** — a **star** is one orchestrator-awarded activity goal, read from awarded-goal state on the learner's own session rooms. Per activity, the learner's stars are their **best single session** (the most goals awarded to them in any one session of it — repeat sessions do not accumulate); the Mission total sums those per-activity bests across the Mission's distinct activities. Both rules are the org doc's satisfaction model. No server-side progression endpoint is needed: every session that earned a star is a room the client can read. (Same collectible pattern as vocab/grammar — see [analytics-system.instructions.md](analytics-system.instructions.md).)

From those, the resolver finds each quest's **anchor (next) Mission**: the **first Mission in quest order whose star total is below the satisfaction threshold**; once every Mission is satisfied, the anchor falls back to the **lowest-star Mission**, so a completed quest keeps pointing at the learner's weakest area instead of going silent. When several quests are in scope it yields an anchor **per quest** plus the global per-Mission star totals; consumers preference still-unsatisfied Missions and **accumulate** across quests (so an activity advancing several quests' unfinished Missions ranks higher) — the resolver just supplies the anchors and totals, the weighting lives in the consumer (see the [world map](world-map.instructions.md) Priority matrix).
From those, the resolver finds each quest's **anchor (next) Mission**: the **first Mission in quest order whose star total is below the satisfaction threshold**; once every Mission is satisfied, the anchor falls back to the **lowest-star Mission**, so a completed quest keeps pointing at the learner's weakest area instead of going silent. When several quests are in scope it yields, **per quest**, an anchor and that quest's own per-Mission star totals; consumers preference still-unsatisfied Missions and **accumulate** across quests (so an activity advancing several quests' unfinished Missions ranks higher) — the resolver just supplies the anchors and totals, the weighting lives in the consumer (see the [world map](world-map.instructions.md) Priority matrix).

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

Fix the grammatical error in “consumers preference.”

Use “consumers prefer still-unsatisfied Missions” to make the sentence grammatically correct.

🧰 Tools
🪛 LanguageTool

[style] ~17-~17: To elevate your writing, try using more formal phrasing here.
Context: ...st-star Mission**, so a completed quest keeps pointing at the learner's weakest area instead o...

(CONTINUE_TO_VB)

🤖 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/quests.instructions.md at line 17, Update the sentence
describing consumer behavior in the quest anchor documentation to replace
“consumers preference” with the grammatically correct wording “consumers prefer
still-unsatisfied Missions,” without changing the surrounding meaning.

Source: Linters/SAST tools

@wcjord
wcjord merged commit fed3183 into main Jul 20, 2026
6 checks passed
@wcjord
wcjord deleted the fix/per-course-mission-rollup branch July 20, 2026 01:25
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.

Fix per-Mission star totals blending across joined courses

1 participant