fix(riverpod): a listener that reads the value is not a render #3

Merged
trevon merged 1 commit from fix/riverpod-listen-is-not-a-render into main 2026-09-22 21:19:28 +00:00
Owner

Problem

riverpod-async-gap counted any site that read an AsyncValue as a UI read, including ref.listen. A listener mirrors the value into a side effect and draws nothing, so it cannot leave a blank or stale screen.

Found by dogfooding on word_alive (PR trvon/word-alive#41). internalUserProvider is read only by a ref.listen in main.dart that sets an analytics user property, and it was reported as an info-level gap.

Solution

A gap now needs a ref.watch that reads the value. Listeners still count as places that handle error or loading, as before.

Rule changes

  • riverpod-async-gap: no longer fires for providers consumed only by ref.listen.
  • Precision measured:
    • Fixture contract: new listenOnlyProvider negative case in stuck_loading/lib/riverpod_gaps.dart; the existing unhandledProvider positive case still fires.
    • Real code: word_alive goes from 1 finding to 0, and the removed finding was a verified false positive.

Release Note

  • Internal only
  • User-facing summary: riverpod-async-gap no longer reports providers that are only listened to, not rendered.

Breaking Changes

  • None

Tests

  • Unit: rule contract with a positive and a negative control. The new test failed before the fix, and so did the existing single-finding test.
  • scripts/lint.sh format|analyze|test: clean, 135 passed.
  • Recall manifest: not run (needs the labelled corpus)

Documentation

  • CHANGELOG: generated by release-please
  • README: no change needed

AI usage

  • Written with Claude Code, verified with the local gate and on word_alive.
## Problem riverpod-async-gap counted any site that read an AsyncValue as a UI read, including `ref.listen`. A listener mirrors the value into a side effect and draws nothing, so it cannot leave a blank or stale screen. Found by dogfooding on word_alive (PR trvon/word-alive#41). `internalUserProvider` is read only by a `ref.listen` in `main.dart` that sets an analytics user property, and it was reported as an info-level gap. ## Solution A gap now needs a `ref.watch` that reads the value. Listeners still count as places that handle error or loading, as before. ## Rule changes - [x] riverpod-async-gap: no longer fires for providers consumed only by `ref.listen`. - [x] Precision measured: - Fixture contract: new `listenOnlyProvider` negative case in `stuck_loading/lib/riverpod_gaps.dart`; the existing `unhandledProvider` positive case still fires. - Real code: word_alive goes from 1 finding to 0, and the removed finding was a verified false positive. ## Release Note - [ ] Internal only - User-facing summary: `riverpod-async-gap` no longer reports providers that are only listened to, not rendered. ## Breaking Changes - [x] None ## Tests - [x] Unit: rule contract with a positive and a negative control. The new test failed before the fix, and so did the existing single-finding test. - [x] `scripts/lint.sh format|analyze|test`: clean, 135 passed. - [ ] Recall manifest: not run (needs the labelled corpus) ## Documentation - [ ] CHANGELOG: generated by release-please - [ ] README: no change needed ## AI usage - [x] Written with Claude Code, verified with the local gate and on word_alive.
fix(riverpod): a listener that reads the value is not a render
Some checks failed
Lint Commits / commitlint (pull_request) Successful in 15s
Tests / Static Analysis (pull_request) Has been cancelled
Tests / Tests (pull_request) Has been cancelled
f6411dc23b
riverpod-async-gap treated any site that read an AsyncValue as a UI read,
including ref.listen. A listener mirrors the value into a side effect and
draws nothing, so it cannot leave a blank or stale screen. Dogfooding on
word_alive flagged an analytics user-property listener this way.

A gap now needs a ref.watch that reads the value. Listeners still count as
places that handle error or loading.
trevon merged commit 3c59fe78f3 into main 2026-09-22 21:19:28 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
trevon/pipe!3
No description provided.