Skip to content

fix: keep agent history lists on screen while they reload#3590

Merged
matthiasn merged 3 commits into
mainfrom
fix/no-loading-flash-on-agent-history
Jul 25, 2026
Merged

fix: keep agent history lists on screen while they reload#3590
matthiasn merged 3 commits into
mainfrom
fix/no-loading-flash-on-agent-history

Conversation

@matthiasn

@matthiasn matthiasn commented Jul 25, 2026

Copy link
Copy Markdown
Owner

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 CircularProgressIndicator whenever 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, soulVersionHistory and soulEvolutionSessionHistory all ref.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.when already defaults skipLoadingOnRefresh to true, so an explicit invalidate/refresh was never the risky path. skipLoadingOnReload defaults to false, and a reload is what a watched dependency changing produces — which is exactly what these three do. So skipLoadingOnReload is the flag that changes behaviour here; skipLoadingOnRefresh is written alongside it for symmetry and is a no-op.

Scope

Site Watches an update stream? Status
templateRecentReports (reports tab) yes fixed
soulVersionHistory yes fixed
soulEvolutionSessionHistory yes fixed
templateVersionHistory no — only a service guarded defensively

The 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.dart was checked and left alone — it already reads profilesAsync.value ?? [] and guards on hasValue before 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:

  1. Driving invalidate instead of the stream. That is a refresh, which .when already skips loading for — the test asserted nothing.
  2. Asserting after a single 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

    • Prevented agent version, evolution, and report history lists from blinking during refreshes.
    • Existing list content now remains visible while updated data loads, avoiding disruptive loading spinners, layout shifts, and page collapse.
  • Tests

    • Added coverage to verify stable history and report displays during background reloads.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@matthiasn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 17ed0e39-80fa-4d4e-b9a5-db874d99903e

📥 Commits

Reviewing files that changed from the base of the PR and between aae445b and 9f34498.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • flatpak/com.matthiasn.lotti.metainfo.xml
  • lib/features/agents/README.md
  • lib/features/agents/ui/agent_soul_detail_info_tab.dart
  • lib/features/agents/ui/agent_template_detail_sections.dart
  • pubspec.yaml
  • test/features/agents/ui/agent_soul_detail_info_tab_test.dart
  • test/features/agents/ui/agent_template_detail_sections_test.dart
  • test/widget_test_utils.dart
📝 Walkthrough

Walkthrough

Agent 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.

Changes

Agent history refresh stability

Layer / File(s) Summary
Retain history content during reloads
lib/features/agents/ui/agent_soul_detail_info_tab.dart, lib/features/agents/ui/agent_template_detail_sections.dart
History and report renderers skip loading states during reloads and refreshes, keeping existing lists visible.
Validate and publish the refresh behavior
test/features/agents/ui/*, CHANGELOG.md, flatpak/com.matthiasn.lotti.metainfo.xml, pubspec.yaml
Widget tests verify retained content during stream-driven reloads; release notes document the fix and the app build number increments.
Estimated code review effort: 2 (Simple) ~15 minutes

Suggested labels: bug, UX

🚥 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 matches the main change: keeping agent history lists visible during reloads instead of swapping to a spinner.
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/no-loading-flash-on-agent-history

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.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.15%. Comparing base (bfabc04) to head (9f34498).

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           
Flag Coverage Δ
glados 14.07% <ø> (+<0.01%) ⬆️
standard 98.90% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread lib/features/agents/ui/agent_template_detail_sections.dart
Comment thread test/features/agents/ui/agent_template_detail_sections_test.dart Outdated
Comment thread lib/features/agents/ui/agent_soul_detail_info_tab.dart
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.
@matthiasn
matthiasn force-pushed the fix/no-loading-flash-on-agent-history branch from aae445b to b972b5b Compare July 25, 2026 19:42
@matthiasn

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
✅ Action performed

Full 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.

Repository owner deleted a comment from gemini-code-assist Bot Jul 25, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread lib/features/agents/state/template_query_providers.dart Outdated
Comment thread lib/features/agents/state/template_query_providers.dart Outdated
Comment thread lib/features/agents/state/soul_query_providers.dart Outdated
Comment thread flatpak/com.matthiasn.lotti.metainfo.xml Outdated
Comment thread lib/features/agents/state/template_query_providers.dart Outdated
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.
@matthiasn
matthiasn merged commit ff66a70 into main Jul 25, 2026
32 checks passed
@matthiasn
matthiasn deleted the fix/no-loading-flash-on-agent-history branch July 25, 2026 20:18
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.

1 participant