Skip to content

fix(performance): the widget tree never reaches an idle frame, so pumpAndSettle times out #40

Description

@koniz-dev

Context

On a real Android emulator, await $.pumpAndSettle() immediately after booting
the app throws:

pumpAndSettle timed out
#0  WidgetTester.pumpAndSettle.<anonymous closure> (widget_tester.dart:717:11)
#3  main.<anonymous closure> (integration_test/app_e2e_test.dart:29:5)

Evidence: run 32975097312.
The test spent 131s before failing, and the app itself boots fine - so this is
not a crash. pumpAndSettle waits for a frame in which nothing is scheduled, and
this tree apparently never produces one.

The E2E tests have been changed to use waitUntilVisible instead (#33), which
polls rather than demanding quiescence. That unblocks E2E but does not answer
why, and the answer matters:

  • Anyone writing a widget test against the full app via
    test/helpers/pump_app.dart will hit the same wall the moment they call
    pumpAndSettle on the real widget tree rather than a single screen.
  • A tree that always has work scheduled means the framework is producing frames
    continuously. On a real device that is battery and jank, not just a test
    problem.

Already ruled out by reading the source

Do not re-check these:

  • No spinner on the initial route. AuthState.isLoading is
    @Default(false), so login_screen.dart:104 renders text, not a
    CircularProgressIndicator.
  • No AnimationController anywhere in lib/.
  • No repeating timer. The only Timer( in lib/core/ is
    utils/debouncer.dart:38, one-shot.
  • No startup network call. authNotifierProvider.build() returns
    const AuthState(); getCurrentUser() is not called on build.
  • .env absence is not it. EnvConfig.load() swallows every exception.

Where to look next

  • flutter_native_splash and whatever keeps the native splash alive after first
    frame.
  • lib/core/performance/ - the no-op performance service and its screen mixin
    (performance_screen_mixin.dart) hook frame callbacks.
  • NavigationLoggingObserver on the router.
  • lib/shared/widgets/optimized_image.dart and the image cache warm-up in
    main().

flutter run --profile plus the DevTools frame chart on a device will show what
is scheduling frames, in a way static reading cannot.

Acceptance criteria

  1. The cause is named concretely: which widget, mixin, or plugin schedules a
    frame every tick. A reference to the specific file and line, not a hypothesis.
  2. Either the continuous scheduling is removed, or it is documented as
    intentional with the reason and the cost.
  3. A widget test that pumps the full app (not a single screen) and calls
    pumpAndSettle() completes without timing out - or, if the scheduling is
    intentional, test/helpers/pump_app.dart gains a doc comment saying
    pumpAndSettle must not be used with it and what to use instead.
  4. If the cause is removed: the E2E tests may return to pumpAndSettle, but only
    with a dispatched E2E run proving it. Do not revert fix(testing): the shipped E2E test cannot pass without a backend, so every fork inherits a red run #33 on reasoning alone.
  5. ./scripts/dev/audit_template.sh exits 0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    epic:design-systemlib/shared: design tokens, theme, shared widgets, accessibilitypriority:P2Normal queue: real but not blockingstatus:blockedCannot proceed: needs a decision, credential, or upstream fixtype:bugSomething that is broken relative to documented behavior

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions