fix: keep agent history lists on screen while they reload#3590
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Warning Review limit reached
Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughAgent history and report lists now retain settled content during provider reloads and refreshes instead of showing loading spinners. Widget tests cover the behavior, and release metadata plus the app build number were updated. ChangesAgent history refresh stability
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3590 +/- ##
=======================================
Coverage 99.15% 99.15%
=======================================
Files 1783 1783
Lines 130939 130939
=======================================
Hits 129833 129833
Misses 1106 1106
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aae445b854
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three history sections rendered a centred CircularProgressIndicator whenever their provider reloaded, replacing settled content mid-read. Each sits in a scrolling detail page, so the swap collapses the section's height and shifts everything below it — the same class of jump as the task-detail work in #3585, from the opposite direction. The reloads are routine, not rare: templateRecentReports, soulVersionHistory and soulEvolutionSessionHistory all watch agentUpdateStreamProvider, so any agent update re-runs them. Note the distinction that decides this. AsyncValue.when already defaults skipLoadingOnRefresh to true, so an explicit invalidate was never the risky path; skipLoadingOnReload defaults to false, and a reload is what a watched dependency changing produces. Only the second flag changes behaviour here — the first is written alongside it for symmetry. The fourth site, templateVersionHistory, is guarded defensively rather than fixed: it watches only a service, so nothing reloads it today. Its soul-side twin does watch the update stream, which puts this section one line away from the same bug, so the guard goes in now and says so. Tests drive the update stream rather than calling invalidate, which is what makes them fail without the fix. An earlier version used invalidate and passed either way, as did a version that asserted after a single pump — the first pump lets the reload propagate, the second lets the widget rebuild, and asserting in between just reads the previous frame. An empty-but-resolved list is enough to prove the point, since the empty-state text would be replaced by the spinner just the same.
Addresses three Codex findings on #3590, one of which invalidates the original premise of this PR. The lists were not just flashing on reload — they were not reloading at all. agentUpdateStream(id) filters the notification stream down to sets *containing* id, and almost nothing puts a template or soul id in one: persistedStateChangedNotifier emits {agentId, agentNotification}, and the sync handler adds a template id only for WakeTokenUsageEntity. A soul id never appears anywhere. So all four sections watched an id that never arrives and stayed stale until the page was reopened. Each now also watches agentUpdateStreamProvider(agentNotification), the shared topic every producer includes — the idiom allEvolutionSessionsProvider, ritualReviewProviders and agentPendingWakeProviders already use. That makes the reload path live, which is what the render guards were written for: skipLoadingOnReload keeps the list through a reload, and skipError (added here) keeps it through one that throws, since a failed reload yields AsyncError carrying the previous value and the default would swap the list for the error widget. Initial load and initial failure still show their shells, having no previous value to keep. Removing either flag on its own was checked: without skipLoadingOnReload three tests fail, without skipError one does. The tests now emit the notification shape production actually sends, and the stream override mirrors agentUpdateStream's own filter so a test emitting the wrong shape fails instead of quietly passing. makeTestableWidgetWithContainer gains a retry hook — Riverpod schedules an automatic retry for a failed provider, and the pending timer fails the test at teardown. It is spelled as a function type rather than Riverpod's Retry typedef, which collides with package:test_api's. Documents the refresh contract in the agents README, and rewrites the CHANGELOG entry, which claimed a flash users could not have seen while missing the staleness they could.
aae445b to
b972b5b
Compare
|
@coderabbitai full review |
✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 22 minutes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b972b5b445
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses five further Codex findings on #3590, three of which are my own errors from the previous revision. Reverts the provider subscription changes entirely. Three reasons, in order of severity: The premise was wrong. I claimed a soul id never appears in a notification set. It does — soul entities carry `agentId: soulId` (soul_version_ops.dart, soul_evolution_workflow.dart), so both persistedStateChangedNotifier and the sync handler already land the soul's own id in the set. The two soul sections were correctly wired all along, and their guards were live, not defensive. The template edit landed in the wrong function. It matched the first occurrence of the surrounding pattern, which is templateTokenUsage- Records, not templateRecentReports — so the reports tab was never actually resubscribed, and the token-usage tables gained a global subscription nobody asked for. The tests did not catch it because their overrides injected the dependency by hand, which is exactly the kind of test that proves a wiring production does not have. They now watch the id the real provider watches. And the approach had blast radius I had not mapped: the same providers back SoulEvolutionReviewPage and TemplateTokenUsageSection, whose `when` calls still take the defaults, so a shared-topic subscription would collapse those surfaces on unrelated agent activity — trading one jump for two. What remains is what this PR was scoped for: skipLoadingOnReload and skipError on the four history sections. Both are still independently load-bearing with the providers back at main — removing the first fails three tests, the second one. Also restores three CHANGELOG entries and four metainfo paragraphs that a bad conflict resolution during the previous rebase dropped, and two unrelated `skipLoadingOnReload` lines that a directory-wide sed deleted from ai_summary_card and change_set_summary_card. The README now documents the refresh reality rather than an aspiration: soul sections reload on every soul write, the reports tab only on template-scoped events, and template version history not at all. The latter two are recorded as known gaps with the reason closing them is not a one-liner.
Follow-up to #3585, from the opposite direction: that PR stopped the task detail view from jumping when content above the reading position resized. This one stops three agent history sections from jumping because they replace themselves with a spinner.
The bug
An agent template's reports tab, and a soul's version history and evolution history, each rendered a centred
CircularProgressIndicatorwhenever their provider reloaded — swapping settled content for a spinner and back. Each sits in a scrolling detail page, so the swap collapses the section's height and shifts everything below it.The reloads are routine rather than rare:
templateRecentReports,soulVersionHistoryandsoulEvolutionSessionHistoryallref.watch(agentUpdateStreamProvider(...)), so any agent update — a run finishing, a sync arriving — re-runs them while the user is reading.The distinction that decides this
AsyncValue.whenalready defaultsskipLoadingOnRefreshtotrue, so an explicitinvalidate/refreshwas never the risky path.skipLoadingOnReloaddefaults tofalse, and a reload is what a watched dependency changing produces — which is exactly what these three do. SoskipLoadingOnReloadis the flag that changes behaviour here;skipLoadingOnRefreshis written alongside it for symmetry and is a no-op.Scope
templateRecentReports(reports tab)soulVersionHistorysoulEvolutionSessionHistorytemplateVersionHistoryThe fourth is honest about itself in a code comment: nothing reloads it today, so the guard cannot fire. Its soul-side twin does watch the update stream, which puts this section one line away from the same bug, so the guard goes in now rather than after.
agent_creation_modal.dartwas checked and left alone — it already readsprofilesAsync.value ?? []and guards onhasValuebefore the spinner, so that one only fires on initial load.Tests
New:
test/features/agents/ui/agent_template_detail_sections_test.dart,test/features/agents/ui/agent_soul_detail_info_tab_test.dart. Both drive the update stream to force a genuine reload, and both were confirmed to fail without the fix (3 failures) and pass with it.Two earlier versions of these tests passed either way, which is worth recording since both are easy to write by accident:
invalidateinstead of the stream. That is a refresh, which.whenalready skips loading for — the test asserted nothing.pump(). The first pump lets the reload propagate, the second lets the widget rebuild; asserting in between reads the previous frame and passes whatever the widget would have done.An empty-but-resolved list is enough to prove the behaviour — the empty-state text gets replaced by the spinner just the same as a populated list would — which keeps the fixtures small and avoids constructing version/session entities.
5090 tests pass across
test/features/agents/; analyzer clean.Summary by CodeRabbit
Bug Fixes
Tests