#6850·flet

Windows: window auto-minimizes shortly after losing focus (regression, works fine on 0.85.3)

Author: nuts-kinocoCreated Sep 15, 2026Updated Sep 16, 2026
Labelsbug

Description

On Windows, right after the app window loses focus (e.g. the user clicks another window), a spurious WindowEventType.MINIMIZE event fires even though the user did not minimize the window. The window is then actually minimized at the OS level (WM_SIZE / SIZE_MINIMIZED is really sent by Windows), so any app logic that reacts to MINIMIZE (e.g. "minimize to tray") triggers unexpectedly.

This does not reproduce on Flet 0.85.3 with the same app and the same repro steps.

Steps to reproduce

  1. Start a Flet desktop app on Windows that registers page.window.on_event and logs e.type.
  2. Let the window gain focus (app is frontmost, maximized).
  3. Cause the window to lose focus (click another window, or programmatically shift OS focus away).
  4. Observe the event log.

Expected behavior

Only blur/focus events fire on a plain focus change; minimize should fire only when the user (or the app) actually minimizes the window.

Actual behavior

Shortly after blur (roughly 0-4 seconds later in our repro), a minimize event fires and the window is actually minimized (WM_SIZE/SIZE_MINIMIZED observed at the OS level via a native probe), even though nothing requested it.

Example log from our app (times are wall clock, HH:MM:SS,mmm):

17:34:53,705 BLUR
17:34:57,286 MINIMIZE   <- fires ~3.5s after BLUR, no user action

After this first spurious minimize, the issue does not repeat on the same running instance (subsequent focus loss does not trigger another minimize).

Investigation so far

  • We checked window_manager (the underlying Flutter plugin) on Windows (window_manager_plugin.cpp). Its handling of WM_NCACTIVATE (blur/focus) and WM_SIZE/SIZE_MINIMIZED (minimize) are independent — there is no code path that turns a blur into a synthetic minimize. This means the OS is genuinely being asked to minimize the window.

  • We suspect the trigger is upstream of that, in packages/flet/lib/src/services/window.dart's WindowService._updateWindow(). It tracks a nullable bool? _minimized cache and calls minimizeWindow() whenever the incoming minimized value differs from the cache:

    dart
    bool? _minimized; // initial value is null
    ...
    if (minimized != _minimized) {
      if (minimized == true) {
        await minimizeWindow();
      } else if (minimized == false && maximized != true) {
        await restoreWindow();
      }
      _minimized = minimized;
    }

    If the Window control's minimized property is (re)sent as false for the first time around a focus-loss-triggered update, false != null is true, so this branch takes the minimized == false path — but if the actual value being reconciled at that moment is momentarily true (or the property update ordering differs from what we assumed), the same "first sync after null" pattern could trigger the true branch. We have not confirmed the exact value/timing on the Python side (flet.controls.core.window.Window) that produces this — this is a hypothesis based on reading the Dart source only.

Environment

  • Flet: 1.0.0 (release, 2026-09-14) / flet-desktop 1.0.0
  • OS: Windows 11
  • Does not reproduce on Flet 0.85.3 with identical app code and repro steps

Additional context

We deliberately avoided patching site-packages locally (not maintainable across upgrades) and are reporting this upstream instead. Happy to provide more logs / a minimal repro app if useful — our current repro is embedded in a larger desktop app.