Skip to content

Flet 0.86#6679

Merged
FeodorFitsner merged 41 commits into
mainfrom
flet-0.86
Jul 14, 2026
Merged

Flet 0.86#6679
FeodorFitsner merged 41 commits into
mainfrom
flet-0.86

Conversation

@FeodorFitsner

Copy link
Copy Markdown
Contributor

Merges the Flet 0.86 release branch into main.

Highlights

New features

Build & packaging

Fixes & performance

Docs & CI

FeodorFitsner and others added 30 commits June 11, 2026 14:21
* Remove Canvaskit web build artifacts

Delete generated web/canvaskit build artifacts from sdk/python/templates/build/{{cookiecutter.out_dir}} (canvaskit.js, .symbols, .wasm and chromium/skwasm variants). These are built/WebAssembly output files and have been removed from the template to avoid committing generated binaries and reduce repository/template size.

* feat(build): multi-version Python support (3.12 / 3.13 / 3.14)

Add a `python_versions` registry that maps each supported short Python
version to its CPython-standalone build, Pyodide release, and Emscripten
wheel platform tag. `flet build` and `flet publish` resolve the version
in this order: `--python-version X.Y` (new flag) -> `[project].requires-
python` (highest matching stable; pre-release rows are skipped unless an
explicit specifier like `==3.15.*` opts in) -> default `3.14`.

`build_base.py` threads the resolved version through to serious_python
(via `--python-version` and `SERIOUS_PYTHON_VERSION`) and the Flutter
build env, and exposes it to the cookiecutter template as
`cookiecutter.options.python_version` so the Android `abiFilters` can
drop `armeabi-v7a` for 3.13+ (PEP 738 dropped 32-bit Android in
python-build releases).

Pyodide is no longer pre-baked into the build template. On each web
build/publish, the matching pyodide-core tarball plus the micropip /
packaging wheels are downloaded into the build output and cached under
`~/.flet/pyodide/<version>/`. `client/web/python.js` and the templated
`python.js` drop the hardcoded `defaultPyodideUrl`; `patch_index.py`
injects `flet.pyodideUrl` at build time (CDN for normal builds, local
`pyodide/pyodide.js` when `--no-cdn`).

Other:

* Bump the `serious_python` pin in the build template to 2.0.0
  (multi-version support landed there as a breaking release; pin must
  follow once 2.0.0 is published to pub.dev).
* Add `python_version`-aware section to the publish docs ("Choosing a
  Python version"), Pyodide pairing table on the static-website page,
  and a `--pre` clarification on `flet publish`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* flet --version: list supported Python versions instead of a static Pyodide

With multi-version Python, Pyodide is selected per build from
`SUPPORTED_PYTHON_VERSIONS` (currently 0.27.7 / 0.29.4 / 314.0.0a2 for
Python 3.12 / 3.13 / 3.14). The old single-line `Pyodide: 0.27.7` field
on `flet.version` was both stale and misleading once it stopped tracking
what `flet build` actually bundles.

Replace it with a multi-line listing rendered from the registry:

    Flet: 0.85.3
    Flutter: 3.41.7
    Supported Python versions:
      3.12 (Pyodide 0.27.7)
      3.13 (Pyodide 0.29.4)
      3.14 (Pyodide 314.0.0a2, default)

Pre-release rows render with ", pre-release" appended. `PYODIDE_VERSION`
and the `pyodide_version` export on `flet.version` are removed (the only
external consumer was the CLI version output, now updated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* flet --version: list supported Python versions newest first

Sort SUPPORTED_PYTHON_VERSIONS by `packaging.version.Version(r.short)`
in reverse before rendering, so users see the default (newest stable) at
the top instead of having to scan to the bottom of the list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(build): use Path.as_uri() for dev-package file:// URLs on Windows

`flet build` rewrites local-path dev dependencies as `<pkg> @
file://<path>` so pip installs them in place. On Windows the literal
f"file://{dev_path}" rendered `file://D:\a\...\flet`, which pip parsed
as a UNC path (`\\D:\a\...`) and failed with:

  ERROR: Could not install packages due to an OSError: [Errno 2]
  No such file or directory: '\\\\D:\\a\\flet\\flet\\sdk\\python\\packages\\flet'

`Path.as_uri()` produces the correct three-slash form
(`file:///D:/a/flet/...` on Windows, `file:///Users/...` on POSIX) and
URL-encodes any special characters, so it's safe on every platform.

Surfaces on flet's own apk-windows CI job
(flet-dev/flet#27111512526 job 80010220246).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(flet-build-test): exercise full Python matrix (3.12 / 3.13 / 3.14)

Each of the 14 build targets (linux/macos/windows + Android aab+apk on
all three hosts + ipa + ios-simulator + web on all three hosts) now runs
against every supported bundled CPython, producing 42 jobs total.

Matrix gains a `python_version` scalar axis and promotes `name` from an
`include` row to a base axis so GH Actions takes the cartesian product;
the existing `include` entries are kept as-is and matched by `name` to
supply per-target runner / build_cmd / artifact_path. The build step now
passes `--python-version ${{ matrix.python_version }}` to `flet build`,
and the upload step disambiguates the artifact name with a `-pyX.Y`
suffix so the 42 uploads don't collide.

`fail-fast: false` is unchanged so a single failing combo doesn't kill
the rest of the grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(flet-build-test): group build jobs per Python via reusable workflow

Split the 14-target build matrix into a reusable workflow
(`flet-build-test-matrix.yml`) that takes the bundled Python version as
an input, and call it three times from the entrypoint — once per
supported CPython release. The Actions UI now renders each call as its
own collapsible "Matrix: build" card (`0/14 jobs completed`) instead of
a single flat 42-row list.

Adds a `python_version` choice input to `workflow_dispatch` (`all` |
`3.12` | `3.13` | `3.14`) so manual runs can target a single version.
push and pull_request triggers always fire all three. Each call is
gated by `if: github.event_name != 'workflow_dispatch' || inputs.python_version == 'all' || inputs.python_version == 'X.Y'`.

The pack job stays at the top level (its host-Python PyInstaller bundle
is independent of the bundled CPython that `flet build` ships, so it
doesn't need a per-Python axis). path-filter triggers now reference
both workflow files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Prepare release 0.86.0 (multi-version bundled Python)

Bumps the Dart-side `flet` package to 0.86.0 and adds the user-facing
0.86.0 entry to the top-level changelog: new `--python-version` flag,
per-version Pyodide download/cache, `flet --version` listing change,
the default-3.14 breaking note, removal of `flet.version.pyodide_version`,
and the Windows `file://` dev-package URL fix. The Dart `flet` package
gets the usual italic release-coordination note since this branch
doesn't touch the Dart side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: sort Python version tables newest first

Match the same descending order applied to `flet --version` so users see
the default (3.14) at the top of every supported-Python table instead of
having to scan to the bottom of the list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flet build` (#6590)

* feat(cli): add `flet clean` command to delete the build directory

Adds a new `flet clean` subcommand that deletes the `build` directory of a
Flet app (default: current directory). Removes the Flutter bootstrap project,
cached artifacts, and generated output in one step.

Refs #6233

* feat(cli): deprecate `--clear-cache` flag of `flet build`/`flet debug`

The `--clear-cache` flag (shared by `flet build` and `flet debug`) now emits a
DeprecationWarning and a console notice recommending the new `flet clean`
command. The flag remains functional for now. Help text updated accordingly.

Refs #6233

* test(cli): add tests for `flet clean` command

Covers deleting an existing build directory, the no-op case when no build
directory is present, path-argument targeting, and the error exit when the
app path does not exist.

Refs #6233

* docs(cli): document `flet clean` and update `--clear-cache` references

Add a CLI docs page for `flet clean` (auto-rendered from the parser via
crocodocs), register it in the CLI sidebar, and update the publish guide's
`--clear-cache` mentions to recommend `flet clean` and note the deprecation.

Refs #6233

* docs(changelog): add 0.85.3 entries for `flet clean` + `--clear-cache` deprecation

Refs #6233

* update changelog

* ci: simplify test paths in workflow configuration

* refactor(cli): use flet deprecated_warning for `--clear-cache` with removal version

Switch the `--clear-cache` deprecation from a raw `warnings.warn` to flet's
`deprecated_warning` helper, and state the timeline explicitly: deprecated in
0.86.0, removal in 0.89.0. The visible console notice mirrors the same versions.

Refs #6233

* docs(updates): document `--clear-cache` deprecation in "Stay up to date"

Add a version-prefixed breaking-changes guide
(v0-86-0-deprecated-clear-cache-flag.md) for the deprecated `--clear-cache` flag
of `flet build`/`flet debug`, pointing users to the new `flet clean` command.
Rename the existing 0.85.0 guides to the same `v<version>-` convention, wire
everything through the sidebars.yml source (and regenerated sidebars.js) plus the
breaking-changes index, and add a 0.86.x release-notes entry.

Refs #6233

* build(docs): stop tracking generated website/sidebars.js

sidebars.js is generated by `crocodocs generate` from sidebars.yml and is
regenerated by `yarn start`/`yarn build` (locally and in CI) before Docusaurus
reads it, so the committed copy was never relied upon — and tracking it let a
hand-edit drift from the source. Gitignore it, matching the existing convention
for .crocodocs/ and the generated docs asset mirrors. sidebars.yml stays tracked.

* docs(publish): remove all references to deprecated `--clear-cache` flag

Replace mentions of the `--clear-cache` flag with recommendations for using `flet clean` to clear the build directory, aligning with its deprecation in version 0.86.
* Update pubsub_client.py

By default Pylance handles Callable as (...) -> Unknown. This is a minor fix for avoiding a reportUnknownMemberType error

* Update CHANGELOG.md

---------

Co-authored-by: TheEthicalBoy <98978078+ndonkoHenri@users.noreply.github.com>
* initial commit

* Refactor: Replace `getBool` with `hasEventHandler` and optimize control access methods

* docs: add run/run_async/DropdownM2 API pages; link deprecations via docs_reason

Add API doc pages for `flet.run`, `flet.run_async`, and `flet.DropdownM2`
(registered in sidebars.yml) so the reST cross-references in their docstrings
resolve to real pages instead of leaking into the built HTML, fixing the
docs:check gate.

Use `docs_reason` cross-references in the `app`, `app_async`, and `DropdownM2`
deprecations so the rendered docs link to the replacement APIs while the runtime
DeprecationWarning text stays plain. Also fix `app_async`'s reason to reference
`run_async()` instead of `run()`.

* fix(semantics): trigger `long_press` (not `dismiss`) on long press

`Semantics`'s Dart builder wired `onLongPress` to the `dismiss` event, so
`Semantics.on_long_press` handlers never fired and a long-press emitted a
`dismiss` event instead. Wire `onLongPress` to the `long_press` event.

Pre-existing bug surfaced by Copilot review on #6589.

* docs(scrollable): list `ResponsiveRow` among `ScrollableControl` inheritors

`ResponsiveRow` inherits `ScrollableControl` but was missing from the docstring's
list of example inheritors.

* fix #6585: validate `NavigationRailDestination.icon`

* fix: update example integration test imports
… Flet apps (#6571)

* fix(android): pickFirsts libc++_shared.so to unblock multi-plugin Flet apps

Gradle's mergeNativeLibs task aborts when more than one input
contributes a file at the same path. serious_python_android (the
Python runtime shipped via mobile-forge wheels) bundles
libc++_shared.so because cross-compiled wheels link against it;
many third-party Flutter plugins (ultralytics_yolo, sentry_flutter,
several ML/CV/audio plugins) ship their own copy too. The clash
surfaces as

    > N files found with path 'lib/<abi>/libc++_shared.so' from inputs:
        - .../<plugin_a>/.../libc++_shared.so
        - .../serious_python_android/.../libc++_shared.so

and breaks any Flet app that combines flet-libcpp-shared-backed
wheels with a third-party plugin that also bundles libc++_shared.so
(reported in #6570 for a YOLO + opencv-python app).

libc++_shared.so is the NDK's shared C++ runtime, and the NDK has
held strict ABI compatibility on it since r17 (June 2018). Whichever
copy wins Gradle's input ordering, every consumer that linked
against libc++_shared will work against it -- which makes pickFirsts
the documented, narrowly-scoped escape hatch for this specific
collision. The directive is glob-scoped to **/libc++_shared.so so
any other future duplicate-native-lib clash still fails loudly
rather than getting silently masked.

Resolves the runtime crash / build failure in #6570; verified
working by the reporter via a local one-line edit before this
landed.

* add changelog entry

* add release notes for versions 0.85.2–0.85.4, 0.1.46, and 0.1.53

* update changelog to reference PR #6571 for libc++_shared.so fix

* changelog v0.86.0

---------

Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
…indow_blur` to `pick_files()` (#6573)

* initial commit

* Add `compression_quality` to `FilePicker.pick_files()`

* Improve `FilePicker.save_file()` documentation

* improve `FloatingActionButtonLocation` docs

* update changelog to reference PR #6573

* validate `compression_quality` range in `FilePicker.pick_files()`

* changelog v0.86.0

---------

Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
…-version Python, native-mmap, DataChannel) (#6601)

* fix(controls): preserve concrete value type when constructing ValueKey

`ValueKey(controlKey.value)` produced `ValueKey<Object>(value)` because
`controlKey.value` is statically typed `Object`. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
`ValueKey<String>('foo')` — making `find.byKey(Key('foo'))` /
`find.byKey(ValueKey('foo'))` in flutter_test fail to locate any
Flet-rendered control by user-assigned key.

Switch-dispatch on the runtime type so a String value yields
`ValueKey<String>`, int → `ValueKey<int>`, etc. This matches what
`Key('foo')` (factory for `ValueKey<String>('foo')`) and analogous
test-side constructions produce.

Repro: flet_example in flet-dev/serious-python on the dart-bridge
branch — its integration_test/app_test.dart with
`find.byKey(Key('increment'))` for an IconButton with
`key="increment"` was finding 0 widgets until this fix.

* feat(transport): add dart_bridge in-process transport (alongside socket)

Adds a third transport (`FletDartBridgeServer` + Dart-side channel-builder
injection) that exchanges Flet's MsgPack protocol over the in-process
`dart_bridge` byte channel — the prebuilt-binary FFI bridge consumed by
serious_python plugins via `package:serious_python/bridge.dart`.

Coexists with the existing UDS / TCP socket transport. Activation:
- Python: `FLET_DART_BRIDGE_PORT=<port>` env var + `is_embedded()` true.
- Dart: pass `FletApp(channelBuilder: ...)` — the embedder constructs a
  `FletBackendChannel` impl wrapping a `PythonBridge` and feeds it in.

`flet` package itself stays Python-independent: it does NOT depend on
`serious_python` or know about `PythonBridge`. The whole PythonBridge
wiring lives in the embedder's code (proven by a forthcoming
`flet_ffi_example` in serious-python). What lands here in `flet` is just
the seam.

Python side:
- New `flet/messaging/flet_dart_bridge_server.py` — `FletDartBridgeServer`
  with the same protocol dispatch as `FletSocketServer`, lazy-imported so
  non-embedded runs never load `dart_bridge`. Inbound: `__on_bytes`
  enqueues payloads from the C-callback thread onto an asyncio.Queue
  drained by `__inbound_loop`. Outbound: `send_message` calls
  `dart_bridge.send_bytes(port, packb(...))`.
- `flet/app.py`: `run_async` selection block grows a third arm:
    if is_embedded() and FLET_DART_BRIDGE_PORT: dart_bridge
    elif is_socket_server:                       socket (existing)
    else:                                        web (existing)
- New helper `__run_dart_bridge_server` modelled on `__run_socket_server`.

Dart side:
- New `FletBackendChannelBuilder` typedef in
  `transport/flet_backend_channel.dart`.
- `FletApp` accepts optional `channelBuilder`; `FletBackend` honours it in
  `connect()` and skips the URL-scheme factory when present. URL-based
  routing for socket / websocket / mock / Pyodide is unchanged.

Wire protocol — unchanged. Same `[ClientAction, body]` MsgPack frames,
same encoder/decoder, same Session dispatch. Only the byte transport
differs.

* feat(transport): export FletBackendChannel + msgpack helpers from flet.dart (lets embedders implement channelBuilder)

* fix(transport): park embedded dart_bridge run loop until host shutdown

The dart_bridge transport has no accept loop equivalent — start() registers a
byte handler with libdart_bridge and returns immediately. Without an explicit
wait, run_async() falls through to conn.close() as soon as main() returns,
killing the bridge before any Dart-side frame can arrive. The embedded
interpreter then exits even though the Flutter host is still running.

Mirror the existing url_prefix/socket-server arm: wait on the terminate event
when is_embedded() and FLET_DART_BRIDGE_PORT are both set.

* templates(build): migrate from sockets to PythonBridge FFI transport

Switches the production transport in `flet build`'s generated app from
TCP/UDS sockets to the in-process dart_bridge FFI channel that the
serious-python `dart-bridge` branch exposes. Web mode (websocket) and
developer mode (external Python process over TCP/UDS) stay unchanged —
PythonBridge only makes sense when the Python interpreter is embedded
in the same OS process as Flutter.

main.dart:
  * Two long-lived PythonBridge instances created in prepareApp():
    `_bridge` carries the MsgPack-framed Flet protocol; `_exitBridge`
    is a dedicated outbound channel for Python's exit code (replaces
    the legacy stdout-callback ServerSocket).
  * pageUrl = `dartbridge://<port>`; env exports FLET_DART_BRIDGE_PORT
    and FLET_DART_BRIDGE_EXIT_PORT. The Python flet package's app.py
    picks up FLET_DART_BRIDGE_PORT and starts FletDartBridgeServer
    instead of FletSocketServer.
  * `_DartBridgeBackendChannel` (lifted from flet_ffi_example): wraps
    PythonBridge as a FletBackendChannel — streaming msgpack decoder
    on inbound, encoder + 30s retry loop on outbound. Injected into
    FletApp via the `channelBuilder` parameter added in the flet PR.
  * runPythonApp drops the ServerSocket setup; subscribes to
    `_exitBridge.messages` and reuses the existing error-screen /
    `exit(code)` handling unchanged.
  * Dropped the now-unused `getUnusedPort` helper.

python.dart:
  * Drops the `socket` callback channel and FLET_PYTHON_CALLBACK_SOCKET_ADDR.
  * `flet_exit` posts the exit code as raw UTF-8 bytes via
    `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`.
  * stdout/stderr → FLET_APP_CONSOLE file redirection preserved (the
    Dart side reads it for the error screen on `flet_exit(100)`).

pubspec.yaml:
  * `serious_python` pinned to the dart-bridge branch via git ref —
    1.0.1 on pub.dev predates PythonBridge. Swap to a version pin
    once serious_python ships a release carrying the bridge API.
  * Added `msgpack_dart: ^1.0.1` for the channel's wire codec.

Dev mode (--debug + page URL in args) still creates no bridges and
FletApp resolves transport via its URL-scheme factory; web mode reads
Uri.base unchanged.

* Add path for serious-python git dependency

Add a `path: src/serious_python` entry to the serious-python git dependency in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This directs the package resolver to the subdirectory within the referenced repo (ref: dart-bridge) so the Dart package is loaded from src/serious_python instead of the repository root.

* Bump 3.13.14 / 3.14.6 / Pyodide 314.0.0; thread date-based python-build vars

Mirror the serious-python registry bump:
  * 3.12 row: Astral PBS date 20260610 (CPython 3.12.13 unchanged).
  * 3.13 row: CPython 3.13.14, PBS date 20260610.
  * 3.14 row: CPython 3.14.6, PBS date 20260610, Pyodide 314.0.0 GA.
  * All three rows gain `python_build_date: "20260611"` for the new
    date-keyed flet-dev/python-build release scheme.

The 3.13 wheel platform tag was also wrong — `pyodide-2025.0-wasm32`
where it should have been `pyemscripten-2025.0-wasm32` (the prefix
transition happened at Pyodide 0.28/0.29, not at 314.0). `flet build web
--python-version 3.13` would have failed to match Pyodide-built native
wheels. Fixed in the registry and called out in the 0.86.0 changelog.

`build_base.py` now exports two new env vars alongside the existing
`SERIOUS_PYTHON_VERSION` so the serious-python platform plugin build
scripts can construct the new URL form (`…/<YYYYMMDD>/python-*-<full>-*`):
  * SERIOUS_PYTHON_FULL_VERSION  → python_release.standalone
  * SERIOUS_PYTHON_BUILD_DATE    → python_release.python_build_date

Both are set in `package_env` (for `serious_python:main package`) and
`build_env` (for the subsequent `flutter build`).

Breaking-changes docs for 0.86: new 0.86.0 section in the index plus two
new guide pages covering (a) the default-Python bump 3.12 → 3.14 with
three pin options, and (b) the removal of `flet.version.pyodide_version`
/ `PYODIDE_VERSION` with the registry-lookup replacement. The dart_bridge
default-transport migration guide is intentionally not in this commit;
it'll be authored separately.

Publish docs tables (`publish/index.md`, `publish/web/static-website`)
updated to the new patch + Pyodide versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(transport): DataChannel API for high-throughput widget byte streams

Adds dedicated byte channels (`ft.DataChannel`) that let widgets exchange
bulk binary data (image frames, audio buffers, ML tensors) with their
Python counterpart without going through the MsgPack control protocol.

Architecture:

* `package:flet` exposes abstract `DataChannel` + `DataChannelFactory`.
  Embedders inject a fast-path factory; absent that, a built-in
  `ProtocolMuxedDataChannelFactory` muxes channel bytes over the active
  Flet protocol transport.
* Python side: `ft.DataChannel` ABC with `_DartBridgeDataChannel`
  (embedded native, dedicated PythonBridge) and `_ProtocolMuxedDataChannel`
  (muxed fallback) impls. `Control.get_data_channel(id)` resolves a
  channel allocated on the Dart side.
* Handshake: control-level event `data_channel_open` carrying
  `{channel_name, channel_id}` — push-driven, no polling, no race.

Wire format change (breaking):

* All transports now prefix every packet with a 1-byte type
  discriminator: `0x00` = MsgPack-encoded Flet protocol frame,
  `0x01` = raw DataChannel frame (`[channel_id:u32 LE][bytes]`).
* Stream-oriented transports (UDS/TCP) gain a 4-byte little-endian
  length prefix; message-oriented transports (WebSocket, postMessage,
  dart_bridge) keep native message boundaries.
* `StreamingMsgpackDeserializer` removed — every inbound packet is one
  complete MsgPack value, decoded via one-shot `msgpack.deserialize`.
  Same simplification on the Python side: `Unpacker.feed` loops →
  `msgpack.unpackb(payload)`.

Updated all four Connection subclasses (`FletSocketServer`,
`FletDartBridgeServer`, `flet_web.fastapi.FletApp`, `PyodideConnection`)
and all five Dart transports (socket, WebSocket, JavaScript/postMessage,
mock, JS stub) to the new framing. Pyodide outbound uses Transferable
ArrayBuffer for zero-copy across the worker boundary.

Three smoke tests in `packages/flet/test/transport/data_channel_test.dart`
cover factory allocation, inbound routing by channel id, and the outbound
muxed packet shape.

* feat(flet-charts): migrate MatplotlibChartCanvas to DataChannel

Replaces the `_invoke_method`-based `apply_full` / `apply_diff` / `clear`
plumbing with a dedicated `DataChannel` carrying 1-byte-opcode frames
(0x01=full PNG, 0x02=diff PNG, 0x03=clear). PNG bytes no longer pay
MsgPack encode/decode — they flow at memory-bandwidth-class speed in
embedded native mode and at near-bandwidth speed in dev/web modes (raw-
byte frames muxed over the protocol transport).

Backpressure follows the WebAgg pattern: Dart sends a 1-byte `[0xFF]`
ack back over the same channel after each apply chain resolves; the
canvas exposes `set_on_frame_applied(callback)` so `MatplotlibChart`
clears `_waiting` only after Dart confirms the frame painted, mirroring
mpl.js's `img.onload → waiting=false` flow. Without this gate,
interactive drags pile up frames in the Dart-side queue and replay in a
burst.

The 3D example (`examples/.../matplotlib_chart/three_d/main.py`) adds a
status bar showing avg full/diff frame size, total bytes transferred,
sliding-window transfer speed, FPS, and per-stage latency (dart-side
paint vs mpl-side render+idle) so users can see where time is spent.

GPU / CPU strategy code in both State subclasses is unchanged — only
the source of frames switched from `_invokeMethod(...)` to the channel
listener.

* refactor(build): split native FFI runtime out of main.dart for web compat

`flet build web` was failing to compile with errors like "Type 'Pointer'
not found" because the build template's `main.dart` unconditionally
imported `package:serious_python/bridge.dart` and
`package:serious_python/serious_python.dart`, both of which transitively
pull in `dart:ffi` types via `package:serious_python_platform_interface`.
`dart:ffi` isn't available in the web compile target.

Extract everything that touches `serious_python` into a separate
`native_runtime.dart`:

* `initBridges(envVars) → pageUrl` — creates the protocol + exit
  PythonBridge instances and stamps env vars.
* `channelBuilder`, `dataChannelFactory` getters for the embedded
  PythonBridge-backed transports.
* `runPython(...)` — wraps `SeriousPython.runProgram` + the exit-bridge
  listener.
* `extractAppAssets(...)` — wraps `extractAssetZip`.
* The `_DartBridgeBackendChannel`, `_PythonBridgeDataChannel`, and
  `_PythonBridgeDataChannelFactory` impls.

`main.dart` now uses a conditional import:

    import 'native_runtime_stub.dart'
        if (dart.library.ffi) 'native_runtime.dart' as nrt;

On web, the stub (`native_runtime_stub.dart`) is selected — every
entry point either returns null or throws `UnsupportedError`, and is
guarded behind `kIsWeb` so the stub is never reached at runtime. The
result: `flet build web` compiles cleanly without `dart:ffi` ever
entering the compile graph.

No behavior change on native (mobile/desktop) builds — they pick up the
real `native_runtime.dart` via the conditional and execute the same code
that lived in `main.dart` before.

* fix(web): switch Pyodide worker to module type + pyodide.mjs

Pyodide >= 0.29 (and 314.0.0, the Python 3.14 line) throws "Classic web
workers are not supported" inside any worker where `importScripts` is
callable. python-worker.js was spawned as a classic worker, so booting
the Python 3.13 / 3.14 lines surfaced a hard error before any user code
ran.

Switch to module workers across both the flet web client and the
`flet build` template:

* `new Worker(url, { type: "module" })` — module workers don't expose
  `importScripts`, so Pyodide's check passes.
* `importScripts(pyodideUrl)` → `const { loadPyodide } = await
  import(pyodideUrl)` — the dynamic-import form module workers must
  use.
* All `pyodideUrl` defaults flip from `pyodide.js` to `pyodide.mjs` —
  the ES-module variant has the named export the dynamic import expects.

URL injection paths:

* `flet publish` / `flet run --web` go through `patch_index.py`, which
  now injects `pyodide.mjs` URLs (both CDN and `--no-cdn` branches).
* `flet build web` uses the cookiecutter template's index.html, which
  was hardcoded at `/pyodide/pyodide.js` regardless of `--no-cdn`.
  Replace with a Jinja conditional that honors `cookiecutter.no_cdn`
  and uses the new `cookiecutter.pyodide_version` variable for the
  jsdelivr CDN URL. `build_base.py` populates `pyodide_version` from
  the resolved `python_release.pyodide`.

Forward-compatible across all three supported Pyodide lines:
0.27.7 (Python 3.12), 0.29.4 (Python 3.13), 314.0.0 (Python 3.14).
Older lines accept module workers too; 0.29+ require them.

* docs(0.86.0): DataChannel + protocol framing breaking-change guide

* CHANGELOG: new features (DataChannel API), improvements (length-prefix
  framing + type-byte discriminator, StreamingMsgpackDeserializer
  removed), breaking changes (wire format on stream transports, mixed
  flet versions across `flet run` CLI and runtime no longer supported).
* New breaking-changes guide
  `data-channel-protocol-upgrade.md` — migration notes for users with
  custom backends speaking the Flet protocol, plus a heads-up for
  anyone subclassing `MatplotlibChartCanvas` (the Dart-side
  `_invokeMethod` handler no longer fires).
* Add the new guide to the 0.86.0 entry in the breaking-changes index.

* perf(web): transfer Pyodide-worker bytes to main via Transferable ArrayBuffer

The worker→main `postMessage` path was structured-cloning every bulk
payload (matplotlib PNG frames, etc.) — measurable cost at ~300 KB
per frame. Switch to Transferable: extract the Uint8Array's
underlying ArrayBuffer and pass it in the second argument to
postMessage. Main thread receives the buffer with ownership transferred,
no copy.

The matching main→worker (Dart→Python) direction already used
Transferable since the DataChannel landing. Both directions are now
zero-copy across the worker boundary on Pyodide.

This does not move the matplotlib bottleneck — that's WASM-compute-bound
on mplot3d — but trims a few ms of structured-clone cost per frame and
makes the perf budget closer to what the dart_bridge embedded path
delivers natively.

* fix(flet-charts): restore await-based backpressure for matplotlib frames

The sync `apply_full` + side-channel `_on_frame_applied` callback was
losing matplotlib "draw" events in pyodide mode. Sequence:

1. `_receive_loop` reads frame bytes, calls `apply_full(bytes)` — sync,
   returns immediately.
2. Loop iterates, reads next event from `_receive_queue`.
3. Next event is a `"draw"` notification matplotlib emitted just after
   the previous frame (figure dirty again from mouse drag).
4. Gate check: `_waiting=True` (ack hasn't arrived from Dart yet) →
   **drop the event**.
5. Ack arrives 200+ ms later, `_waiting=False`, but the queue is empty
   and matplotlib doesn't re-emit "draw" until next mouse event.

Result in pyodide: ~1.5 fps observed, vs the 0.85 `_invoke_method`
implementation's much higher rate. The 0.85 pattern wasn't faster
because it lacked an ack — it had one (the INVOKE_METHOD reply). It
was faster because `await self._invoke_method(...)` **blocked the
`_receive_loop`** during the round-trip, so matplotlib events queued
naturally in `_receive_queue` and were processed in order after the
await returned, rather than being eagerly drained against a stale gate.

Fix: re-introduce the await pattern at the canvas level.

* `MatplotlibChartCanvas.apply_full / apply_diff / clear` are now
  async. Each enqueues a per-frame `asyncio.Future`, sends the channel
  packet, and awaits the future.
* `_on_dart_message` resolves the head future when `[0xFF]` arrives.
* `MatplotlibChart._receive_loop` awaits each `apply_*` call —
  matplotlib events that arrive during the wait stay queued and are
  processed after the ack returns. Same behaviour shape as 0.85's
  `_invoke_method` round-trip, but over the DataChannel transport
  (no msgpack on the bulk payload).
* `set_on_frame_applied(cb)` is preserved as a pure observer callback
  for instrumentation (e.g. the 3D example's stats panel) — no longer
  load-bearing for backpressure.

The 3D example's `apply_full` / `apply_diff` wrappers updated to
`async def` + `await` accordingly.

* ci: fix web client build after flet.version.pyodide_version removal

The multi-version Python PR (#6577) removed flet.version.pyodide_version
but the 'Get Pyodide version' step still read it, failing every
'Build Flet Client for Web' run. Resolve the version from the
flet_cli.utils.python_versions registry instead (default release's
Pyodide), and replace the hand-rolled tarball + wheel downloads with
flet_cli.utils.pyodide.ensure_pyodide — the hardcoded
micropip-0.8.0/packaging-24.2 filenames would have silently broken on
the new Pyodide line (3.14's lock resolves micropip 0.11.1), since
curl without -f writes 404 pages into the .whl files.

Cherry-picked from 2d8f4a1 on fix-android-arch-filtering.

Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>

* docs(breaking-changes): drop dead /docs/extending-flet/data-channels link

The 0.86 protocol-framing breaking-change guide linked to a DataChannel
API reference page that doesn't exist yet — there's no extending-flet/
folder, and no DataChannel doc has been authored. Docusaurus' broken-link
scan failed the docs build on every push. Replace the link with prose
pointing at the data_channel.py module docstring; dedicated reference
pages can land in a follow-up once the API doc generator covers it.

* ci: bump Node 20 actions to Node 24 versions

GitHub Actions emitted Node.js 20 deprecation warnings on every job in
run 27457389406. Node 20 will be removed from runners 2026-09-16. Bump
the affected actions to their latest Node 24 majors across all workflows:

- actions/checkout@v4 → v6
- actions/setup-node@v4 → v6 (v6 limited auto-cache to npm, the website
  uses yarn via corepack — no caching behavior change)
- actions/upload-artifact@v4 / v5.0.0 → v7
- actions/download-artifact@v4 → v8
- astral-sh/setup-uv@v6 → v8.2.0 (v8 dropped the major @v8 tag for
  supply-chain reasons, full tag required)
- dart-lang/setup-dart@<old SHA> → v1.7.2

All six actions' action.yml now declare `runs.using: node24`.

* fix(tester): preserve ValueKey value type in find_by_key

`ValueKey(controlKey.value)` produces `ValueKey<Object>` because
`controlKey.value` is statically typed Object. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
the `ValueKey<String>('foo')` that ControlWidget assigns to the
rendered widget — making `find_by_key("foo")` from Python tests
find 0 widgets.

Mirrors the fix already applied in control_widget.dart (7367050).
Switch-dispatch on the runtime type so String → ValueKey<String>,
int → ValueKey<int>, etc.

Resolves the cascade of "RangeError: no indices are valid: 0" and
"assert 0 == 1" failures across apps, controls/core, controls/material,
controls/cupertino, and controls/theme integration suites.

* fix(tests): update example imports after folder rename in #6545

#6545 renamed 131 example folders (mostly basic/ → descriptive
control name, plus example_1/2/3, nested_themes_1/2 collapsing, and
removing the basic/ wrapper where there was only one example) but the
matching imports in packages/flet/integration_tests/examples/ were
never updated. Test collection failed with ModuleNotFoundError on
every affected suite (examples/apps, examples/extensions, and
examples/controls/{core,cupertino,material}).

Rewrites the 45 test files referencing those modules to the new paths
derived from the rename history of commit 1b2e914.

* Docusaurus 3.10.1 and Node.js 24

* feat(cli): add 'flet --version --json' and source CI version/dep reads from it

Add a --json flag to 'flet --version' that emits a machine-readable
document (Flet/Flutter versions, supported Python/Pyodide table, Linux
build deps). CI workflows and the publish docs now read it via jq instead
of importing Flet internals with 'python -c'.

Move the canonical Linux apt dependency list from flet.utils.linux_deps
(runtime package) to flet_cli.utils.linux_deps (build tooling), where it
sits next to python_versions.py and is a same-package import for the CLI.

* ci: pin Windows runners to windows-2025-vs2026

GitHub is redirecting windows-latest to windows-2025-vs2026 by June 15,
2026. Pin the label explicitly in the Flet Build Test and Build & Publish
workflows to silence the redirect notice and make the image deterministic.

* Allow flutter_secure_storage updates (^10.0.0)

Update pubspec.yaml dependency for flutter_secure_storage from fixed 10.0.0 to caret ^10.0.0, allowing compatible minor/patch updates instead of pinning to a single patch version. This lets the package accept backwards-compatible releases without manual changes. Fix #6586

* Resolve Python/Pyodide versions from python-build's manifest

Drop flet's hand-mirrored SUPPORTED_PYTHON_VERSIONS table and load the
supported Python/Pyodide/dart_bridge set from python-build's date-keyed
manifest.json — the single source of truth shared with serious_python.

- python_versions.py: pin one PYTHON_BUILD_RELEASE_DATE; fetch that release's
  manifest.json (cached immutably under ~/.flet/cache/python-build, offline
  fallback to cache) and parse it lazily. Module constants become
  get_supported_python_versions()/get_default_python_version(); resolution
  logic unchanged. Dev/CI overrides: FLET_PYTHON_BUILD_RELEASE_DATE,
  FLET_PYTHON_BUILD_MANIFEST.
- flet build: pass only SERIOUS_PYTHON_VERSION; serious_python derives the full
  version, build date, and dart_bridge version from its committed snapshot.
  Drops the SERIOUS_PYTHON_FULL_VERSION/SERIOUS_PYTHON_BUILD_DATE exports.
- flet --version: drop the Python/Pyodide matrix (stays offline); --json keeps
  flet/flutter/linux_dependencies.
- ci.yml: read the default Pyodide version via the manifest-backed resolver
  instead of jq over `flet --version --json`.
- Docs: update the removed-pyodide-version-export guide + CHANGELOG to the new
  accessors; document the pin in CONTRIBUTING.
- Add offline tests driven by FLET_PYTHON_BUILD_MANIFEST.

* Pin screen_brightness_macos to 2.1.2 (SPM macOS deployment-target regression)

screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a
Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 SPM floor,
so `flutter build macos` fails to resolve with Swift Package Manager enabled.
Pinning the app-facing screen_brightness alone doesn't help — the federated
macOS implementation is separately versioned. Override the impl to the last
good 2.1.2 in both the build template and the client app.

Upstream: aaassseee/screen_brightness#99

* flet build: clean build dir when the bundled Python version changes

Switching --python-version (or requires-python) between builds left the previous
version's compiled bytecode in the reused build directory's native bundles
(stdlib/site-packages .pyc), crashing the app at runtime with
`ImportError: bad magic number`. Record the resolved Python short version in the
build dir and, when it changes, wipe the build dir so the native bundles are
regenerated for the new interpreter.

* Correct 0.86.0 changelog for the manifest-backed version model

The earlier 0.86.0 entries described `flet --version` listing the Python/Pyodide
matrix and `--version --json` carrying the full table — both removed when version
resolution moved to python-build's manifest. Update those entries to the shipped
behavior (flet/flutter only; --json drops the Python table), add the
manifest-model entry (constants -> get_supported_python_versions(), `flet build`
forwards only SERIOUS_PYTHON_VERSION), and fix the stale "central registry" /
`serious_python >= 2.0.0` references.

* Bump Flutter to 3.44.2

Move the pinned Flutter from 3.41.7 to 3.44.2 (.fvmrc; CI/Docker/version.py
derive from it) and absorb the 3.44 breaking changes.

- Android built-in Kotlin migration: the Flet client and the `flet build`
  template no longer apply the Kotlin Gradle plugin themselves (Flutter applies
  kotlin-android); Java 11 -> 17, `kotlinOptions` -> the `kotlin { compilerOptions }`
  DSL. Client AGP 8.12.1 -> 8.11.1, KGP 2.1.0 -> 2.2.20, Gradle 8.13 -> 8.14,
  hardcoded NDK -> flutter.ndkVersion.
- No Dart changes needed: flet already uses the modern Color/WidgetState/Material3/
  SharePlus APIs and the page-transition builders survived the 3.44 reorg.

Verified: flutter analyze clean (packages/flet + client), packages/flet tests
pass, flet --version reports 3.44.2, client web (wasm) build succeeds.

* Refresh date-picker locale goldens for Flutter 3.44.2

Flutter 3.44.2 slightly changed Material/Cupertino date-picker rendering, so the
*_locale screenshot goldens drifted past the 99% similarity threshold.
Regenerated on macOS (matching the macos-26 CI runner) via FLET_TEST_GOLDEN=1;
re-verified at 100% similarity without golden mode.

- controls/material: date_picker/locale, date_range_picker/locale
- controls/cupertino: cupertino_date_picker/locale
- examples/controls/material: date_picker/custom_locale, date_range_picker/custom_locale

* feat(app): dart_bridge session-restart loop (Android process reuse)

`run_async`'s dart_bridge branch wraps the `FletDartBridgeServer` in a
new `_DartBridgeServerHandle` facade that swaps the underlying
connection in place when a Dart VM restart signal arrives from
libdart_bridge.

Trigger: on Android, the OS may keep the OS process alive across a
back-button quit and restart only the Dart VM on re-launch. The new
VM's `PythonBridge` allocates fresh native ports; the running Python
still has handlers on the (now-dead) ports from the previous VM.
libdart_bridge 1.3.0's `dart_bridge.add_session_restart_handler` fires
the callback registered here with `{label: new_port}`, this rebuilds
`FletDartBridgeServer` on the new "protocol" port, then closes the
stale one. User-level Python state (singletons, in-memory data) is
preserved; the Flet session is rebuilt from REGISTER_CLIENT.

Soft-binding: `add_session_restart_handler` is looked up with
`getattr` so older libdart_bridge that doesn't expose the API still
loads — process-reuse is just unsupported on those, matching today's
behavior.

* feat(build): wire process-reuse signal + skip-on-reuse in build template

native_runtime.dart `initBridges`:
* Unconditionally calls `DartBridge.instance.signalDartSession(...)`
  with the new `{protocol, exit}` port map after allocating the bridges.
* On fresh start: libdart_bridge has no embedded Python yet → no-op.
* On Android process reuse: fires every Python session-restart handler
  registered by flet.app + python.dart bootstrap, so the running Python
  rebinds to the new ports.
* Exposes `pythonAlreadyRunning` (forwards `DartBridge.isPythonInitialized`)
  so `main.dart` can skip `SeriousPython.runProgram` on reuse — the
  embedded Python is already running with the user's `main()`, and
  trying to run it again would either crash (pre-1.3.0) or no-op-return
  immediately (1.3.0+).

main.dart `runPythonApp`:
* Checks `nrt.pythonAlreadyRunning` and parks on an unresolved future
  when true. Keeps the existing `FutureBuilder` plumbing intact while
  the just-restarted Dart VM picks up its UI session through the
  already-rewired FletDartBridgeServer.

Requires libdart_bridge >= 1.3.0 for the actual process-reuse path to
fire; against older binaries the soft-lookup in serious-python's
DartBridge bindings drops `signalDartSession` to a no-op and
`pythonAlreadyRunning` returns false — degrades to the pre-1.3.0
behavior (no process-reuse support, no crash either).

* feat(build): mutable _exit_port + native-log tee in python.dart bootstrap

Two related changes to the bundled bootstrap script:

1. `_exit_port` becomes a one-element list so the session-restart
   handler can update it in place on Android process reuse. Without
   this, after a Dart VM restart, `flet_exit` would still post the
   exit code to the OLD (now-dead) PythonBridge port and the host
   process would never receive the signal.

   Subscribe to `dart_bridge.add_session_restart_handler` (>= 1.3.0)
   to mutate `_exit_port[0]` whenever the new VM signals fresh ports.
   `getattr` lookup keeps older libdart_bridge working — process-reuse
   is just unsupported on those, exit-code routing unchanged.

2. `sys.stdout` / `sys.stderr` become `_TeeWriter` instances that
   duplicate writes to BOTH the libdart_bridge native log writer
   (logcat / os_log / stderr, installed at Py_Initialize by 1.3.0+) AND
   the existing `out_file` (the error-screen capture file). Previously
   the script overwrote sys.stdout/stderr with just `out_file`, which
   silenced the new native log path entirely on Android — the whole
   point of the stdio install in libdart_bridge.

   On older libdart_bridge without the native install, the tee falls
   back to writing through the default text streams + file, so error
   capture works the same as today and there's no console output
   regression.

* fix(build): line-buffer the native-log side of python.dart's TeeWriter

Python's `print(x)` is internally two writes: `write(x)` then
`write("\n")`. libdart_bridge's native-log writer strips the trailing
newline before calling `__android_log_write`, so the standalone "\n"
write becomes an empty string and produces a blank logcat row after
every print:

    I  Hello, Flet!
    I
    I  Another line of output.
    I
    I  And another one.
    I

The file half of the tee still receives writes raw — `out_file` keeps
byte-for-byte parity with what Python wrote so the error-screen
capture matches a plain `python` console run.

The native half now accumulates until "\n", emits the line without
the trailing newline, and skips purely empty lines (so `print()` with
no args also stays out of the log instead of producing a blank
entry). `flush()` drains any pending partial line in case the user
app calls it mid-line.

Result is one logcat row per logical print line — what the user
expects.

* Bump screen_brightness to 2.1.11

Add a local Swift package product (FlutterGeneratedPluginSwiftPackage) to the macOS Xcode project and Runner scheme, including a pre-build action to run Flutter's macos_assemble.sh prepare step to ensure the framework is prepared. Update Windows plugin registration to use the screen_brightness_windows C API header and registration function. Bump screen_brightness in packages/flet and remove prior direct overrides from client and template pubspecs so the project uses the newer package resolution and avoids the previous workaround for macOS/SVM target issues.

* fix(build/web): add pythonAlreadyRunning getter to native_runtime_stub

The web stub of native_runtime didn't expose the new pythonAlreadyRunning
getter introduced for the Android process-reuse path, so main.dart's
unconditional check `if (nrt.pythonAlreadyRunning)` failed to compile on
web targets:

    lib/main.dart:297:11: Error: Undefined name 'pythonAlreadyRunning'.
      if (nrt.pythonAlreadyRunning) {
              ^^^^^^^^^^^^^^^^^^^^

Add the stub getter returning false — there's no embedded CPython on
web (Pyodide runs via the JavaScript channel, not dart_bridge), so
"already running" is by definition never true. Symmetric with how the
stub treats other native-only entry points.

* build(android): consume serious_python native-mmap packaging + --android-extract-packages (#6595)

* build(android): consume serious_python native-mmap packaging + extract-packages option

- Build template: point serious_python git ref at android-native-mmap; drop the stale
  packaging{jniLibs{useLegacyPackaging=true; keepDebugSymbols}} block from the Android
  app build.gradle.kts (native modules now load mmap'd from the APK; modern packaging at
  minSdk 23+ is all that's needed).
- flet-cli: add --android-extract-packages CLI flag + [tool.flet.android].extract_packages
  (cross-platform [tool.flet].extract_packages fallback), a built-in
  ANDROID_DEFAULT_EXTRACT_PACKAGES set (certifi), merged and exported as
  SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES for the serious_python package step. Mirrors the
  existing --source-packages -> SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS flow.

* build(android): route extract-packages env to the flutter build + docs

The serious_python_android Gradle split (which partitions site-packages into
the stored sitepackages.zip vs the extracted extract.zip) runs during
`flutter build`, not the `serious_python package` step. Set
SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES on build_env (resolved once into
self.android_extract_packages) so the default set (certifi) and user list
actually reach the split — previously it was set on the package step's env
and silently ignored, leaving extract.zip empty.

Add the "Android extract packages" publish-docs section (resolution order,
CLI + pyproject example), a CHANGELOG entry, and allowlist `certifi` in typos.

Verified end-to-end on an arm64 emulator via both `flet build apk` and
`flet build aab` (bundletool split install): numpy mmap'd from the APK,
certifi extracted to disk, flet icons.json read from the stored zip,
flet.version resolved, app launched.

* build(android): empty the default extract set — certifi is zip-safe

certifi reads cacert.pem via importlib.resources.as_file(), which extracts it
to a temp file on demand, so certifi.where() works from inside the stored
sitepackages.zip (verified: imported via zipimport, where() returns a valid
234KB cacert.pem). It does not need to ship extracted.

Make ANDROID_DEFAULT_EXTRACT_PACKAGES empty — the common data-bundling
packages use importlib.resources (zip-safe). The --android-extract-packages /
[tool.flet.android].extract_packages mechanism stays for genuinely path-hungry
packages (those reading data via __file__ / pkg_resources). Update docs and
CHANGELOG accordingly.

* Move Android extract packages to android.md

Relocate the "Android extract packages" section from publish/index.md into publish/android.md so Android-specific guidance lives on the Android doc. The moved content explains path-hungry packages, zipimport limitations, the resolution order (CLI flag, [tool.flet.android], [tool.flet]) and includes examples for the CLI and pyproject.toml. Removes the duplicate section from the index to keep platform-specific docs consolidated.

* Update serious_python git ref to dart-bridge

Change the serious_python git dependency ref from 'android-native-mmap' to 'dart-bridge' in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This ensures the template uses the dart-bridge variant of serious-python, aligning with the dart_bridge FletBackendChannel implementation.

* build: compile app + packages to .pyc by default (with --no-compile-* opt-out) (#6598)

* build: compile app + packages to .pyc by default

flet build / flet publish now compile the app and installed packages to bytecode
by default (previously off). Shipping .pyc removes per-launch recompilation,
which is a large cold-start win on mobile where pure Python is imported in place
from a stored zip and can't cache bytecode back to disk.

- --compile-app / --compile-packages use argparse.BooleanOptionalAction, adding
  --no-compile-app / --no-compile-packages; the positive flags still work.
- [tool.flet.compile].app / .packages default to true (get_bool_setting default
  flipped False -> True); precedence unchanged (CLI > per-platform > global).
- Verified a compiled `flet build web` bundle: app ships .pyc only and the magic
  matches Pyodide's CPython 3.14 (standalone 3.14.6 compile vs Pyodide 3.14.0 —
  magic is frozen across a minor version's patch releases).

Docs: publish "Compilation and cleanup" section updated, new breaking-changes
guide + index entry, CHANGELOG breaking-changes entry.

* docs: update summary to clarify that only `flet build` compiles to .pyc by default

* perf(flet): lazy public-API imports + defer eager subsystem clusters (mobile cold start) (#6597)

* perf(flet): lazily import the public API (PEP 562 __getattr__)

`import flet` previously executed the whole ~270-module package eagerly
(~240 flet modules, ~2.0s on a mid-range Android device from .pyc). Move the
entire public surface behind a generated `_LAZY = {name: module}` map and a
module-level `__getattr__` that imports each name on first access and caches it
into globals(); the real imports stay under `if TYPE_CHECKING:` so type
checkers / IDEs / `__all__` are unaffected. `__version__` stays eager.

Apps now load only the modules they touch. On the counter example (moto g15),
PY_BOOT -> UI built drops 2.10s -> 0.82s (import flet alone 2.02s -> 0.15s;
22 flet modules at import vs 240). Full pytest suite passes (210 passed,
9 skipped); resolved objects are identical to the eager imports by construction
(the map is generated from the original import statements).

* perf(flet): defer Cupertino + deprecated-service imports off the Page/View hubs

Page/View/base_page are loaded by every app and eagerly pulled clusters a
typical Material app never touches:

- Cupertino app bar / navigation bar: only referenced in `appbar` /
  `navigation_bar` field & property annotations. Move the imports to
  TYPE_CHECKING and string-quote those (non-event) annotations so they are not
  evaluated at class-definition time. control_event only resolves event-handler
  fields, so quoting these is safe.
- 4 deprecated page service properties (BrowserContextMenu, SharedPreferences,
  Clipboard, StoragePaths): construct their service on demand via a
  function-local import instead of importing the whole set at module load.

Counter example: flet modules at first-frame 114 -> 108 (cupertino 4 -> 2,
services 7 -> 3). Full pytest suite still passes (210 passed, 9 skipped).

Note: module-wide `from __future__ import annotations` is intentionally NOT
used here — flet's control_event resolves event-handler annotations at runtime
via get_args(), which requires real type objects, not PEP 563 strings.

* perf(flet): defer the auth subsystem off Page import

Page eagerly imported the whole flet.auth subsystem (7 modules) even though a
typical app never calls Page.login(). The eager edges were: module-level
Authorization / OAuthProvider imports, the AuthorizationImpl default
(AuthorizationService / Authorization chosen at import via is_pyodide()), and
AT = TypeVar("AT", bound=Authorization).

Defer all of it:
- Authorization / OAuthProvider imports -> TYPE_CHECKING; their annotations are
  string-quoted so they aren't evaluated at class-definition time.
- TypeVar bound -> "Authorization" (string forward ref).
- AuthorizationImpl default -> a lazy _default_authorization_impl() helper that
  imports AuthorizationService / Authorization on first Page.login() call; the
  login() authorization parameter defaults to None and resolves to it.

Counter example: flet modules at first-frame 108 -> 101 (flet.auth 7 -> 0).
Full pytest suite passes (210 passed, 9 skipped), including test_auth_lazy_imports.

* perf(flet): defer the components/hooks subsystem off the core import path

Page / BasePage / Session pulled the whole components + hooks subsystem
(9 modules) at import even though most apps use no components or hooks:

- public_utils.unwrap_component (called by Page/BasePage on hot paths) imported
  Component at module load just for an isinstance check. Import it lazily and
  cache it, so importing the helper no longer pulls component.py.
- Page.render / render_views import Renderer function-locally (only component
  apps call these).
- Session.schedule_effect's EffectHook parameter is annotation-only -> move to
  TYPE_CHECKING and string-quote (Session never constructs EffectHook; the hook
  is passed in by the component runtime).

Counter example: flet modules at first-frame 101 -> 94 (flet.components 9 -> 2,
the two remaining being the empty package and the lightweight public_utils).
The components/hooks machinery now loads on first actual use. Full pytest suite
passes (210 passed, 9 skipped).

* docs: version-prefix the 0.86.0 breaking-changes guides (#6599)

Match the `v<major>-<minor>-<patch>-` filename convention (introduced in the
flet-clean PR for the 0.85.0 guides). Prefix the four guides added on
dart-bridge for 0.86.0 with `v0-86-0-`:

- compile-on-by-default
- data-channel-protocol-upgrade
- default-bundled-python-3-14
- removed-pyodide-version-export

Update the breaking-changes index links and the two CHANGELOG links
(compile-on-by-default, data-channel-protocol-upgrade) to the new paths.

* docs(changelog): add missing 0.86.0 items

Three user-facing changes done in the dart-bridge branch had no CHANGELOG
entry. Add them:

- New feature: in-process Python transport (dart_bridge FFI) — the third
  protocol transport alongside UDS/TCP sockets, the FletApp(channelBuilder:)
  seam serious_python uses to embed Python in-process, the build template's
  migration off sockets, and the Android session-restart rebinding.
- Improvement: lazy `import flet` (PEP 562 __getattr__), ~2.0s -> ~0.15s on
  a mid-range Android device (#6597).
- Bug fix: ValueKey value-type preservation so find.byKey / find_by_key locate
  controls by user-assigned key (Dart + Python tester).

* docs(extend): add DataChannel section to user-extensions guide

Practical reference for extension authors who need to move bulk binary
data (image frames, audio buffers, ML tensors) between Python and Dart
without the MsgPack control protocol overhead. Source distillation of
`dedicated-data-channels.md` in flet-dev/serious-python, scoped to what
an extension author actually needs to ship a working widget:

* When to reach for DataChannel vs. when the regular property protocol
  is already fast enough.
* Python-side API: `on_data_channel_open` event handler +
  `Control.get_data_channel(channel_id)` to attach.
* Dart-side API: `FletBackend.of(context).openDataChannel()` +
  firing the `data_channel_open` control event with
  `{channel_name, channel_id}`.
* Multi-channel pattern (`channel_name` dispatch).
* Threading + backpressure caveats — `on_bytes` runs under the GIL on a
  transport thread, receive queues are unbounded by default.
* Cross-links to the full design doc, the breaking-change protocol
  guide, and the MatplotlibChartCanvas reference impl (both halves).

Deliberately omits wire-format, Isolate-scope, and throughput-table
material — those live in `dedicated-data-channels.md` for anyone
needing the deeper picture.

Placed between "Customize properties" and "Examples" since it's
opt-in advanced functionality most extension authors won't need.

* fix(build): only package requested --arch ABIs in Android APK/AAB

Lift the Android-ABI fixes from #6578 onto the dart-bridge branch,
adapted to the manifest-driven `PythonRelease`. Per-version ABI set
is now sourced from python-build's `pythons.<short>.android_abis`
(release 20260618+); flet keeps no hardcoded ABI table.

- `PythonRelease` gains `android_abis: tuple[str, ...]`, populated from
  the manifest. Pinned `PYTHON_BUILD_RELEASE_DATE` bumped 20260614 ->
  20260618 (the python-build release that introduced the field).
- New `utils/android.py` with `ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM`,
  `flutter_target_platforms()`, `excluded_android_abis()`.
- `build_base.py`: `action="extend"` on `--arch`, `--source-packages`,
  `--permissions` (repeated flags now accumulate instead of overwriting).
  Android-only `--arch` validation: invalid ABI -> clean error; ABI not
  in `python_release.android_abis` -> clean error pointing at the
  bundled Python; empty defaults to the supported ABI list. Cookiecutter
  context gains `android_excluded_abis`. `--arch` to serious_python is
  comma-joined (Dart multi-option). `resolve_output_path()` factored out
  so `build.py`'s pre-build cleanup can share the path resolution.
- `build.py`: forwards requested ABIs to Flutter as
  `--target-platform android-arm,android-arm64,android-x64` (so
  `--split-per-abi` builds only the requested splits), wipes each
  platform's output dir before the flutter build so stale artifacts
  don't leak into the user's output, fixes a `self.target_platform in
  "apk"` substring-vs-equality bug uncovered by the new code path.
- Cookiecutter Android template: `packaging.jniLibs.excludes +=
  listOf("lib/<abi>/**", ...)` block, gated on
  `cookiecutter.options.android_excluded_abis`. Needed because AGP
  unions defaultConfig + buildType `ndk.abiFilters`, and other plugins'
  jniLibs slip past `--target-platform`.
- CHANGELOG breaking-change + two bug-fix bullets; android.md gets
  per-version ABI support, PEP 738 note, default-by-bundled-Python
  wording.

Refs: #6567, #6578 by @ndonkoHenri.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(sidebar): list 0.86.0 breaking-change pages under v0.86.0

PR #6601 review (@ndonkoHenri) flagged that the four breaking-change
guides added on dart-bridge weren't wired into website/sidebars.yml's
v0.86.0 section (only flet-0.86's clear-cache deprecation entry was).
Added them in the same order as updates/breaking-changes/index.md:
breaking changes first, then deprecations.

* Add blog post STUB: Flet v0.86.0 release announcement

Create a new blog post stub for the Flet 0.86.0 release at website/blog/2026-07-01-flet-v-0-86-release-announcement.md. The file (author: feodor, tag: releases) includes a short summary highlighting routing and dialogs, smoother video, real-time audio, and bug fixes, plus a CTA to GitHub Discussions and Discord; body content is currently marked TBD.

* Pin serious_python to 3.0.0

Replace the git-based serious_python dependency in the Python template pubspec with a version pin (3.0.0). Removes the git url/ref/path block that referenced the dart-bridge branch, simplifying the template and using the published package instead of an external repo reference.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
…#6608)

* Update CONTRIBUTING.md hello.py example to use modern `ft.run()` and `ft.Page`/`ft.Text` instead of deprecated `flet.app (#6582)

* Fix issue #6579

* docs: use fvm for Flutter commands

* feat: add flet-spinkit extension package wrapping flutter_spinkit ^5.2.2 (#6596)

* feat(flet-spinkit): add flet-spinkit extension package wrapping flutter_spinkit ^5.2.2

Adds 31 separate spinner animation controls (SpinKitRotatingCircle,
SpinKitWave, SpinKitRipple, etc.) following the same structure as
flet-color-pickers. Includes a spinkit_showcase example.

* fix(flet-spinkit): use local path source for flet-spinkit in example

* fix(flet-spinkit): use ft.Alignment.CENTER instead of ft.alignment.center

* fix(flet-spinkit): use ft.Border.all instead of ft.border.all

* refactor(flet-spinkit): simplify example to a single SpinKitRotatingCircle

* feat(flet-spinkit): register flet_spinkit extension in client runner

* fix(flet-spinkit): remove SpinKitPumpCurve/RingCurve (Curve helpers, not widgets), add SpinKitWaveSpinner

* refactor(flet-spinkit): strip SpinKit prefix from all Python class names

Python API is now fsk.RotatingCircle, fsk.Wave, fsk.WaveType etc.
Flutter control type strings (@ft.control decorator values) are unchanged.

* feat(flet-spinkit): expand example to a full 30-spinner grid showcase

* feat(flet-spinkit): add spinkit_props example; use system theme in both examples

* docs(flet-spinkit): add single-page SpinKit docs and sidebar entry

* docs(flet-spinkit): register flet_spinkit in crocodocs; remove image refs from docs

* ci(flet-spinkit): add flet-spinkit to build and publish pipeline; remove local path source overrides from examples

* test(flet-spinkit): add unit tests; fix stale showcase description

* refactor(flet-spinkit): rename import alias from fsk to spins in examples and tests

* feat(flet-spinkit): complete integration and cleanup

- Register flet-spinkit in sdk/python/pyproject.toml (dependencies + uv.sources)
- Register flet-spinkit in sdk/python/packages/flet/pyproject.toml (extensions group)
- Register flet-spinkit in flet_build_test/pyproject.toml (dependencies, uv.sources, dev_packages)
- Replace custom _parseWaveType() helper with parseEnum() in spinkit.dart
- Update implement-flet-extension SKILL.md with lessons learned from this implementation

* docs(flet-spinkit): split into per-control pages, add WaveType page

Replace single-page spinkit docs with individual pages per control,
consistent with flet-color-pickers and other extensions. Add WaveType
enum page to fix unresolved reST cross-reference on the Wave page.

* test(flet-spinkit): rewrite as proper integration tests using ftt.FletTestApp

Replace pure Python unit tests with integration tests that render each
control in a real Flutter app. Use tester.pump() instead of
pump_and_settle() to advance animation clock without waiting for
continuous animations to settle. Assert finder.count == 1 to verify
each control is mounted in the Flutter widget tree.

* docs(flet-spinkit): reformat README to match other extension packages

* Lift app packaging into serious_python: unpacked bundle + reworked storage dirs

App Python sources now ship unpacked inside the app bundle (next to
stdlib/site-packages) on macOS/iOS/Windows/Linux instead of being extracted
from app.zip on first launch; Android ships a stored app.zip asset unpacked
once. The app dir is read-only, so the process cwd moves to a writable,
app-private data dir.

- build_base.py: stage the app via SERIOUS_PYTHON_APP for native platforms;
  gate the app.zip existence check to Emscripten/web.
- templates main.dart / native_runtime.dart: ask serious_python for the app
  dir (prepareApp/getAppDir) instead of extracting app.zip; set cwd +
  FLET_APP_STORAGE_DATA to <support>/data, add FLET_APP_STORAGE_CACHE, repoint
  FLET_APP_STORAGE_TEMP to the OS temp dir; pubspec pins serious_python 4.0.0
  and makes app.zip a web-only asset.
- run.py: dev-mode storage under a hidden, self-gitignored <project>/.flet/;
  cwd = .flet/storage/data, redirect TMPDIR, set the three FLET_APP_STORAGE_*
  vars; hide .flet/ on Windows; write a README explaining the dirs.
- docs: new breaking-change guide + updated environment-variables,
  read-and-write-files, storagepaths, sidebars.

* Bump pasteboard to ^0.5.0 (and refresh transitive deps in client lockfile)

* flet build: Swift Package Manager by default for iOS/macOS (#6607)

* flet build: use Swift Package Manager for iOS/macOS by default

Drives the new serious_python_darwin SPM build path. SPM is Flet's default
darwin integration (CocoaPods remains the fallback); Flet auto-enables it only
on its own managed Flutter, never a Flutter found on PATH.

- flutter_base.py: install_flutter() now enables Swift Package Manager on the
  Flet-managed Flutter (`flutter config --enable-swift-package-manager`) and sets
  flutter_installed_by_flet.
- build_base.py: _darwin_spm_active() reports whether the darwin build uses SPM
  (true on Flet's managed Flutter; otherwise honors the user's flutter config via
  `flutter config --machine`). When active, the package step gets
  SERIOUS_PYTHON_DARWIN_SPM (so serious_python does the host-side SPM staging the
  podspec prepare_command can't) + SERIOUS_PYTHON_SPM_KEY_FILE, and the flutter
  build env gets SP_NATIVE_SET read back from that key file so SwiftPM re-resolves
  when the staged native set changes.

Requires serious_python >= the SPM-capable release.

* flet build: SPM-by-default plumbing + client plugin migration + stale flutter-packages fix

- build_base.py: set SERIOUS_PYTHON_DARWIN_SPM explicitly (true/false) and
  pass SP_NATIVE_SET key file through to serious_python's package step;
  --swift-package-manager / pyproject opt-out with flet-video auto-fallback.
- build_base.py: register_flutter_extensions now always clears the permanent
  flutter-packages dir before staging this build's set, so an extension
  removed since the previous build (e.g. dropping flet-video) no longer
  lingers in the built app.
- flutter_base.py: drop the global 'flutter config' SPM toggle (flet must not
  mutate Flutter's machine-wide config).
- client: migrate off non-SPM plugins for the default desktop client —
  window_to_front -> windowManager.show(); pin screen_retriever 0.2.1 (SPM);
  regenerate plugin registrants + Runner SPM project wiring.
- typos: exclude generated *.pbxproj (pre-commit hook + _typos.toml).
- templates/build pubspec + CHANGELOG.

* flet build: always stage serious_python for SPM (drop flet-video CocoaPods fallback)

The flet-video auto-fallback assumed that an app with a non-SPM plugin builds
entirely with CocoaPods, so serious_python was staged for CocoaPods. That's
wrong for Flutter 3.44+ (SPM on by default): Flutter builds hybrid — plugins
with a Package.swift (incl. serious_python_darwin) use SPM while non-SPM plugins
(media_kit/flet-video, torch_light) use CocoaPods in the same build. Staging
serious_python for CocoaPods while Flutter linked it via its Package.swift left
dart_bridge unlinked -> 'Undefined symbol: _DartBridge_*/_serious_python_run' on
macOS and iOS.

Always stage for SPM (Flutter's default); other non-SPM plugins ride CocoaPods
via Flutter's hybrid mode independently. Keep --no-swift-package-manager /
swift_package_manager=false only for when SPM is disabled in Flutter itself.
Removes _references_non_spm_plugins/_NON_SPM_PLUGINS/_app_uses_non_spm_plugin
(and the now-unused canonicalize_name/contextlib imports).

* Add example deps and modernize typing imports

Add flet-code-editor and flet-color-pickers to the flet_build_test example (pyproject and example imports) and register flet_spinkit import in main.py. Modernize typing usage across packages: add conditional typing-extensions dependency for flet-webview, use typing.Self on Python >=3.11 with a fallback to typing_extensions.Self, and simplify/remove try/except fallbacks by importing Protocol, TypeVar and ParamSpec directly from typing. These changes simplify type annotations and ensure compatibility with older Python via typing-extensions.

* Pin serious_python to 4.0.0

Commented out the Shizuku Android provider entries in sdk/python/examples/apps/flet_build_test/pyproject.toml to disable that provider by default. Replaced the git-based serious_python dependency in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml with a pinned version (4.0.0) to stop tracking the main branch and use the published package.

---------

Co-authored-by: Federico Rao <157750791+Federicorao@users.noreply.github.com>
Co-authored-by: InesaFitsner <inesa@appveyor.com>
* Support Android TV in device info retrieval

* Simplify platform check for Android devices

* Add changelog entry
* Custom boot screen: unified, extensible boot/startup screen

Replace the two separate boot/startup screens with one customizable boot
screen that spans both phases (preparing + starting up), is told its stage
and any startup error via a notifier, and always renders something.

- FletExtension.createBootScreen(name, options, status) hook; BootStage +
  BootStatus model
- Built-in FletBootScreen + resolveBootScreen() resolver (fallback lives in
  the resolver so it works in both phases)
- FletApp/FletBackend: bootScreenName/bootScreenOptions + bootStatus notifier
  kept in sync via notifyListeners(); render in page.dart/view.dart for
  loading and error states; remove LoadingPage
- flet build: _resolve_boot_screen() reads [tool.flet.boot_screen] with
  platform merge and legacy app.boot_screen/startup_screen mapping, emits
  base64 JSON; declare boot_screen in cookiecutter.json
- Split template main.dart: move all jinja declarations to flet_generated.dart
- Docs: publishing guide + extension authoring guide

Note: includes temporary 4s test delays in prepareApp() and connect().

* flet-spinkit: animated SpinKit boot screen as a createBootScreen example

- Extract createSpinKit() from SpinKitControl so the spinner switch is reused
- Add SpinKitBootScreen (boot_screen.dart): theme_mode/colors from options,
  ValueListenableBuilder on BootStatus for stage message + error, spinner
  selected by prefix-less name (default WanderingCubes)
- Register the createBootScreen hook in extension.dart for name "spinkit"
- Docs: boot screen usage in controls/spinkit + reference from the extension
  authoring guide
- Demo: [tool.flet.boot_screen] config in the spinkit_showcase example

* Update website sidebars with v0.86 docs

Add v0.86 breaking-changes category and corresponding docs; rename several v0.85 breaking-change doc ids to include v0-85-0 prefix. Also add new sidebar entries for controls/DropdownM2, types/run, types/run_async, and CLI command flet-clean to reflect new/updated documentation pages.

* Boot screen: seamless overlay, fade-out option, embedded FletApp props

- Render the root app's boot screen in a persistent top-level overlay
  (BootHost in the build template) above the prepare->startup swap, so the
  animation runs continuously instead of remounting at the phase boundary;
  fades out when the app is ready
- BootStatus gains `done`; FletBackend.bootStatus is injectable (shared with
  the overlay) and sets `done` from isLoading/error; FletApp forwards it
- Configurable `fade_out_duration` (ms) boot screen option (0 = instant)
- Embedded ft.FletApp control: add `boot_screen_name` / `boot_screen_options`
  (deprecate show_app_startup_screen / app_startup_screen_message; new wins,
  legacy still mapped)
- Docs: publishing guide, spinkit, extension guide; flet_build_test demo config

Temp boot test delays removed.

* Remove manage_audio_session and bump record dep

Remove the iOS-specific manage_audio_session option from IosRecorderConfiguration and stop parsing manageAudioSession in the Flutter recorder utils (aligns with upstream API change). Also bump the Flutter 'record' dependency from ^6.2.0 to ^7.1.0. Update callers if they relied on toggling AVAudioSession management, as that option has been removed.

* Boot screen: follow Flet's default theme; default fade-out to 0

- FletBootScreen and the SpinKit boot screen now build Flet's default theme
  (parseTheme(null, ...)) and wrap their content in it, so unset background,
  text and spinner colors follow the theme (scaffold bg / on-surface / primary,
  and theme error colors) instead of hard-coded white/black. Explicit color
  options still override.
- Default fade_out_duration is now 0 (overlay removed instantly); set a value
  to fade out.
- Docs + flet_build_test demo updated.

* Update Flutter package versions & deps

Bump dependency versions: upgrade google_mobile_ads to ^9.0.0 in flet-ads and flutter_map_animations to 0.10.0 in flet-map. Regenerate client/pubspec.lock to capture transitive package version and checksum updates resulting from these dependency changes.

* Replace Unicode ellipsis with ASCII in messages

Normalize the ellipsis characters in sdk/python/examples/apps/flet_build_test/pyproject.toml by replacing the single-character Unicode ellipsis (…) with three ASCII periods (...) for the prepare_message and startup_message entries. This avoids potential encoding/display inconsistencies across environments.

* TEMP: wire Android build test to unreleased serious-python + dart-bridge

Exercises the Android boot-screen jank fixes end-to-end before they're released:

- pubspec.yaml: point serious_python at the serious-python android-boot-jank
  branch (platform main-thread offload of extract/unzip/loadLibrary + pyjnius
  R8 keep rules).
- flet-build-test-matrix: for apk/aab targets, pull libdart_bridge Android .so
  from dart-bridge CI run 28121718882 (worker-thread priority) and point the
  build at them via SERIOUS_PYTHON_DART_BRIDGE_DIST.

Revert before merge.

* Revert "TEMP: wire Android build test to unreleased serious-python + dart-bridge"

This reverts commit 2e721f4.

* Bump Flutter 3.44.3, add iOS SwiftPM plugins, misc fixes

Update Flutter version to 3.44.3 (.fvmrc) and apply related platform changes: enable new Android DSL and built-in Kotlin in gradle.properties; adjust main.dart debug URL to use 10.0.2.2 for Android emulator. On iOS, restructure Info.plist (add scene manifest, permissions and other keys), implement FlutterImplicitEngineDelegate in AppDelegate to register plugins for implicit engines, and add Xcode project changes to include a FlutterGeneratedPluginSwiftPackage (package product dependency, file refs and build phase entry). Add Package.resolved files for SwiftPM and update Runner.xcscheme to run the flutter prepare script as a pre-action. Podfile.lock was regenerated/trimmed and CocoaPods version set to 1.14.3. Also bump path_provider_linux in pubspec.lock (2.2.1 -> 2.2.2).

* Boot screen: only show 'preparing' during Android first-launch unpack

Seed the boot status to startingUp so 'Preparing…' no longer flashes on every
launch. On Android, switch to preparing only if prepareApp() runs longer than
400ms (i.e. it's actually unpacking the app bundle on the first launch after
install/update). Non-Android platforms and already-unpacked Android launches
stay in startingUp the whole time.

* Bump serious_python to 4.1.0 in template

Update serious_python from 4.0.0 to 4.1.0 in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml so generated projects use the newer dependency version.

* Deprecate FletApp.show_app_startup_screen / app_startup_screen_message via @deprecated

Use the standard @deprecated decorator (like DragTargetEvent.x/y) instead of a
docstring note: the two settings are now deprecated property getters/setters
that proxy to boot_screen_options (spinner_size / startup_message). Since they
are properties, they're no longer constructor kwargs — set them as attributes
or migrate to boot_screen_options.

Drop the now-dead Dart legacy fallback in flet_app_control.dart and the unused
legacyBootScreenOptions helper (the legacy fields are no longer serialized).
* Add package version handling and runtime variable references

Extended workflows to handle package versions by introducing `pkg_version`. Updated staging process to pass version details. Enhanced docs with runtime equivalents for specific environment variables, improving clarity and usability.

* initial commit

* add example

* docs

* Update runtime environment variable references in docs

Refine `environment-variables.md` to align runtime equivalent references with updated API methods for improved accuracy and clarity.

* remove timeout

* update changelog

* Add AdMob test app IDs to examples

* update changelog

* Download x64 arch of Android SDK on Windows ARM64 computers

* 在android_sdk.py中令cmdline-tools-url检测到windows使用arm64架构时返回与x64相同的下载链接,利用转译层运行

* Add comment for ARM64 Windows device handling

Added comment for ARM64 Windows devices in android_sdk.py.

---------

Co-authored-by: xiaocai2011 <134185030+xiaocai2011@users.noreply.github.com>
Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
* fix(website): escape stray braces in crocodocs docstrings to fix MDX build

* feat(downloads): rich progress bars for client and pyodide downloads

Show a rich progress bar for the flet-desktop first-run client download
and the pyodide runtime download, matching the bars already shown for
Flutter/JDK/Android SDK installs.

- distros: tolerate missing Content-Length and add a description arg
- pyodide: download via download_with_progress; move cache to
  ~/.flet/cache/pyodide (honors FLET_CACHE_DIR)
- flet-desktop: download client via a rich progress bar
- declare rich dependency in flet-cli and flet-desktop
* fix(controls): preserve concrete value type when constructing ValueKey

`ValueKey(controlKey.value)` produced `ValueKey<Object>(value)` because
`controlKey.value` is statically typed `Object`. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
`ValueKey<String>('foo')` — making `find.byKey(Key('foo'))` /
`find.byKey(ValueKey('foo'))` in flutter_test fail to locate any
Flet-rendered control by user-assigned key.

Switch-dispatch on the runtime type so a String value yields
`ValueKey<String>`, int → `ValueKey<int>`, etc. This matches what
`Key('foo')` (factory for `ValueKey<String>('foo')`) and analogous
test-side constructions produce.

Repro: flet_example in flet-dev/serious-python on the dart-bridge
branch — its integration_test/app_test.dart with
`find.byKey(Key('increment'))` for an IconButton with
`key="increment"` was finding 0 widgets until this fix.

* feat(transport): add dart_bridge in-process transport (alongside socket)

Adds a third transport (`FletDartBridgeServer` + Dart-side channel-builder
injection) that exchanges Flet's MsgPack protocol over the in-process
`dart_bridge` byte channel — the prebuilt-binary FFI bridge consumed by
serious_python plugins via `package:serious_python/bridge.dart`.

Coexists with the existing UDS / TCP socket transport. Activation:
- Python: `FLET_DART_BRIDGE_PORT=<port>` env var + `is_embedded()` true.
- Dart: pass `FletApp(channelBuilder: ...)` — the embedder constructs a
  `FletBackendChannel` impl wrapping a `PythonBridge` and feeds it in.

`flet` package itself stays Python-independent: it does NOT depend on
`serious_python` or know about `PythonBridge`. The whole PythonBridge
wiring lives in the embedder's code (proven by a forthcoming
`flet_ffi_example` in serious-python). What lands here in `flet` is just
the seam.

Python side:
- New `flet/messaging/flet_dart_bridge_server.py` — `FletDartBridgeServer`
  with the same protocol dispatch as `FletSocketServer`, lazy-imported so
  non-embedded runs never load `dart_bridge`. Inbound: `__on_bytes`
  enqueues payloads from the C-callback thread onto an asyncio.Queue
  drained by `__inbound_loop`. Outbound: `send_message` calls
  `dart_bridge.send_bytes(port, packb(...))`.
- `flet/app.py`: `run_async` selection block grows a third arm:
    if is_embedded() and FLET_DART_BRIDGE_PORT: dart_bridge
    elif is_socket_server:                       socket (existing)
    else:                                        web (existing)
- New helper `__run_dart_bridge_server` modelled on `__run_socket_server`.

Dart side:
- New `FletBackendChannelBuilder` typedef in
  `transport/flet_backend_channel.dart`.
- `FletApp` accepts optional `channelBuilder`; `FletBackend` honours it in
  `connect()` and skips the URL-scheme factory when present. URL-based
  routing for socket / websocket / mock / Pyodide is unchanged.

Wire protocol — unchanged. Same `[ClientAction, body]` MsgPack frames,
same encoder/decoder, same Session dispatch. Only the byte transport
differs.

* feat(transport): export FletBackendChannel + msgpack helpers from flet.dart (lets embedders implement channelBuilder)

* fix(transport): park embedded dart_bridge run loop until host shutdown

The dart_bridge transport has no accept loop equivalent — start() registers a
byte handler with libdart_bridge and returns immediately. Without an explicit
wait, run_async() falls through to conn.close() as soon as main() returns,
killing the bridge before any Dart-side frame can arrive. The embedded
interpreter then exits even though the Flutter host is still running.

Mirror the existing url_prefix/socket-server arm: wait on the terminate event
when is_embedded() and FLET_DART_BRIDGE_PORT are both set.

* templates(build): migrate from sockets to PythonBridge FFI transport

Switches the production transport in `flet build`'s generated app from
TCP/UDS sockets to the in-process dart_bridge FFI channel that the
serious-python `dart-bridge` branch exposes. Web mode (websocket) and
developer mode (external Python process over TCP/UDS) stay unchanged —
PythonBridge only makes sense when the Python interpreter is embedded
in the same OS process as Flutter.

main.dart:
  * Two long-lived PythonBridge instances created in prepareApp():
    `_bridge` carries the MsgPack-framed Flet protocol; `_exitBridge`
    is a dedicated outbound channel for Python's exit code (replaces
    the legacy stdout-callback ServerSocket).
  * pageUrl = `dartbridge://<port>`; env exports FLET_DART_BRIDGE_PORT
    and FLET_DART_BRIDGE_EXIT_PORT. The Python flet package's app.py
    picks up FLET_DART_BRIDGE_PORT and starts FletDartBridgeServer
    instead of FletSocketServer.
  * `_DartBridgeBackendChannel` (lifted from flet_ffi_example): wraps
    PythonBridge as a FletBackendChannel — streaming msgpack decoder
    on inbound, encoder + 30s retry loop on outbound. Injected into
    FletApp via the `channelBuilder` parameter added in the flet PR.
  * runPythonApp drops the ServerSocket setup; subscribes to
    `_exitBridge.messages` and reuses the existing error-screen /
    `exit(code)` handling unchanged.
  * Dropped the now-unused `getUnusedPort` helper.

python.dart:
  * Drops the `socket` callback channel and FLET_PYTHON_CALLBACK_SOCKET_ADDR.
  * `flet_exit` posts the exit code as raw UTF-8 bytes via
    `dart_bridge.send_bytes(FLET_DART_BRIDGE_EXIT_PORT, ...)`.
  * stdout/stderr → FLET_APP_CONSOLE file redirection preserved (the
    Dart side reads it for the error screen on `flet_exit(100)`).

pubspec.yaml:
  * `serious_python` pinned to the dart-bridge branch via git ref —
    1.0.1 on pub.dev predates PythonBridge. Swap to a version pin
    once serious_python ships a release carrying the bridge API.
  * Added `msgpack_dart: ^1.0.1` for the channel's wire codec.

Dev mode (--debug + page URL in args) still creates no bridges and
FletApp resolves transport via its URL-scheme factory; web mode reads
Uri.base unchanged.

* Add path for serious-python git dependency

Add a `path: src/serious_python` entry to the serious-python git dependency in sdk/python/templates/build/{{cookiecutter.out_dir}}/pubspec.yaml. This directs the package resolver to the subdirectory within the referenced repo (ref: dart-bridge) so the Dart package is loaded from src/serious_python instead of the repository root.

* Bump 3.13.14 / 3.14.6 / Pyodide 314.0.0; thread date-based python-build vars

Mirror the serious-python registry bump:
  * 3.12 row: Astral PBS date 20260610 (CPython 3.12.13 unchanged).
  * 3.13 row: CPython 3.13.14, PBS date 20260610.
  * 3.14 row: CPython 3.14.6, PBS date 20260610, Pyodide 314.0.0 GA.
  * All three rows gain `python_build_date: "20260611"` for the new
    date-keyed flet-dev/python-build release scheme.

The 3.13 wheel platform tag was also wrong — `pyodide-2025.0-wasm32`
where it should have been `pyemscripten-2025.0-wasm32` (the prefix
transition happened at Pyodide 0.28/0.29, not at 314.0). `flet build web
--python-version 3.13` would have failed to match Pyodide-built native
wheels. Fixed in the registry and called out in the 0.86.0 changelog.

`build_base.py` now exports two new env vars alongside the existing
`SERIOUS_PYTHON_VERSION` so the serious-python platform plugin build
scripts can construct the new URL form (`…/<YYYYMMDD>/python-*-<full>-*`):
  * SERIOUS_PYTHON_FULL_VERSION  → python_release.standalone
  * SERIOUS_PYTHON_BUILD_DATE    → python_release.python_build_date

Both are set in `package_env` (for `serious_python:main package`) and
`build_env` (for the subsequent `flutter build`).

Breaking-changes docs for 0.86: new 0.86.0 section in the index plus two
new guide pages covering (a) the default-Python bump 3.12 → 3.14 with
three pin options, and (b) the removal of `flet.version.pyodide_version`
/ `PYODIDE_VERSION` with the registry-lookup replacement. The dart_bridge
default-transport migration guide is intentionally not in this commit;
it'll be authored separately.

Publish docs tables (`publish/index.md`, `publish/web/static-website`)
updated to the new patch + Pyodide versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(transport): DataChannel API for high-throughput widget byte streams

Adds dedicated byte channels (`ft.DataChannel`) that let widgets exchange
bulk binary data (image frames, audio buffers, ML tensors) with their
Python counterpart without going through the MsgPack control protocol.

Architecture:

* `package:flet` exposes abstract `DataChannel` + `DataChannelFactory`.
  Embedders inject a fast-path factory; absent that, a built-in
  `ProtocolMuxedDataChannelFactory` muxes channel bytes over the active
  Flet protocol transport.
* Python side: `ft.DataChannel` ABC with `_DartBridgeDataChannel`
  (embedded native, dedicated PythonBridge) and `_ProtocolMuxedDataChannel`
  (muxed fallback) impls. `Control.get_data_channel(id)` resolves a
  channel allocated on the Dart side.
* Handshake: control-level event `data_channel_open` carrying
  `{channel_name, channel_id}` — push-driven, no polling, no race.

Wire format change (breaking):

* All transports now prefix every packet with a 1-byte type
  discriminator: `0x00` = MsgPack-encoded Flet protocol frame,
  `0x01` = raw DataChannel frame (`[channel_id:u32 LE][bytes]`).
* Stream-oriented transports (UDS/TCP) gain a 4-byte little-endian
  length prefix; message-oriented transports (WebSocket, postMessage,
  dart_bridge) keep native message boundaries.
* `StreamingMsgpackDeserializer` removed — every inbound packet is one
  complete MsgPack value, decoded via one-shot `msgpack.deserialize`.
  Same simplification on the Python side: `Unpacker.feed` loops →
  `msgpack.unpackb(payload)`.

Updated all four Connection subclasses (`FletSocketServer`,
`FletDartBridgeServer`, `flet_web.fastapi.FletApp`, `PyodideConnection`)
and all five Dart transports (socket, WebSocket, JavaScript/postMessage,
mock, JS stub) to the new framing. Pyodide outbound uses Transferable
ArrayBuffer for zero-copy across the worker boundary.

Three smoke tests in `packages/flet/test/transport/data_channel_test.dart`
cover factory allocation, inbound routing by channel id, and the outbound
muxed packet shape.

* feat(flet-charts): migrate MatplotlibChartCanvas to DataChannel

Replaces the `_invoke_method`-based `apply_full` / `apply_diff` / `clear`
plumbing with a dedicated `DataChannel` carrying 1-byte-opcode frames
(0x01=full PNG, 0x02=diff PNG, 0x03=clear). PNG bytes no longer pay
MsgPack encode/decode — they flow at memory-bandwidth-class speed in
embedded native mode and at near-bandwidth speed in dev/web modes (raw-
byte frames muxed over the protocol transport).

Backpressure follows the WebAgg pattern: Dart sends a 1-byte `[0xFF]`
ack back over the same channel after each apply chain resolves; the
canvas exposes `set_on_frame_applied(callback)` so `MatplotlibChart`
clears `_waiting` only after Dart confirms the frame painted, mirroring
mpl.js's `img.onload → waiting=false` flow. Without this gate,
interactive drags pile up frames in the Dart-side queue and replay in a
burst.

The 3D example (`examples/.../matplotlib_chart/three_d/main.py`) adds a
status bar showing avg full/diff frame size, total bytes transferred,
sliding-window transfer speed, FPS, and per-stage latency (dart-side
paint vs mpl-side render+idle) so users can see where time is spent.

GPU / CPU strategy code in both State subclasses is unchanged — only
the source of frames switched from `_invokeMethod(...)` to the channel
listener.

* refactor(build): split native FFI runtime out of main.dart for web compat

`flet build web` was failing to compile with errors like "Type 'Pointer'
not found" because the build template's `main.dart` unconditionally
imported `package:serious_python/bridge.dart` and
`package:serious_python/serious_python.dart`, both of which transitively
pull in `dart:ffi` types via `package:serious_python_platform_interface`.
`dart:ffi` isn't available in the web compile target.

Extract everything that touches `serious_python` into a separate
`native_runtime.dart`:

* `initBridges(envVars) → pageUrl` — creates the protocol + exit
  PythonBridge instances and stamps env vars.
* `channelBuilder`, `dataChannelFactory` getters for the embedded
  PythonBridge-backed transports.
* `runPython(...)` — wraps `SeriousPython.runProgram` + the exit-bridge
  listener.
* `extractAppAssets(...)` — wraps `extractAssetZip`.
* The `_DartBridgeBackendChannel`, `_PythonBridgeDataChannel`, and
  `_PythonBridgeDataChannelFactory` impls.

`main.dart` now uses a conditional import:

    import 'native_runtime_stub.dart'
        if (dart.library.ffi) 'native_runtime.dart' as nrt;

On web, the stub (`native_runtime_stub.dart`) is selected — every
entry point either returns null or throws `UnsupportedError`, and is
guarded behind `kIsWeb` so the stub is never reached at runtime. The
result: `flet build web` compiles cleanly without `dart:ffi` ever
entering the compile graph.

No behavior change on native (mobile/desktop) builds — they pick up the
real `native_runtime.dart` via the conditional and execute the same code
that lived in `main.dart` before.

* fix(web): switch Pyodide worker to module type + pyodide.mjs

Pyodide >= 0.29 (and 314.0.0, the Python 3.14 line) throws "Classic web
workers are not supported" inside any worker where `importScripts` is
callable. python-worker.js was spawned as a classic worker, so booting
the Python 3.13 / 3.14 lines surfaced a hard error before any user code
ran.

Switch to module workers across both the flet web client and the
`flet build` template:

* `new Worker(url, { type: "module" })` — module workers don't expose
  `importScripts`, so Pyodide's check passes.
* `importScripts(pyodideUrl)` → `const { loadPyodide } = await
  import(pyodideUrl)` — the dynamic-import form module workers must
  use.
* All `pyodideUrl` defaults flip from `pyodide.js` to `pyodide.mjs` —
  the ES-module variant has the named export the dynamic import expects.

URL injection paths:

* `flet publish` / `flet run --web` go through `patch_index.py`, which
  now injects `pyodide.mjs` URLs (both CDN and `--no-cdn` branches).
* `flet build web` uses the cookiecutter template's index.html, which
  was hardcoded at `/pyodide/pyodide.js` regardless of `--no-cdn`.
  Replace with a Jinja conditional that honors `cookiecutter.no_cdn`
  and uses the new `cookiecutter.pyodide_version` variable for the
  jsdelivr CDN URL. `build_base.py` populates `pyodide_version` from
  the resolved `python_release.pyodide`.

Forward-compatible across all three supported Pyodide lines:
0.27.7 (Python 3.12), 0.29.4 (Python 3.13), 314.0.0 (Python 3.14).
Older lines accept module workers too; 0.29+ require them.

* docs(0.86.0): DataChannel + protocol framing breaking-change guide

* CHANGELOG: new features (DataChannel API), improvements (length-prefix
  framing + type-byte discriminator, StreamingMsgpackDeserializer
  removed), breaking changes (wire format on stream transports, mixed
  flet versions across `flet run` CLI and runtime no longer supported).
* New breaking-changes guide
  `data-channel-protocol-upgrade.md` — migration notes for users with
  custom backends speaking the Flet protocol, plus a heads-up for
  anyone subclassing `MatplotlibChartCanvas` (the Dart-side
  `_invokeMethod` handler no longer fires).
* Add the new guide to the 0.86.0 entry in the breaking-changes index.

* perf(web): transfer Pyodide-worker bytes to main via Transferable ArrayBuffer

The worker→main `postMessage` path was structured-cloning every bulk
payload (matplotlib PNG frames, etc.) — measurable cost at ~300 KB
per frame. Switch to Transferable: extract the Uint8Array's
underlying ArrayBuffer and pass it in the second argument to
postMessage. Main thread receives the buffer with ownership transferred,
no copy.

The matching main→worker (Dart→Python) direction already used
Transferable since the DataChannel landing. Both directions are now
zero-copy across the worker boundary on Pyodide.

This does not move the matplotlib bottleneck — that's WASM-compute-bound
on mplot3d — but trims a few ms of structured-clone cost per frame and
makes the perf budget closer to what the dart_bridge embedded path
delivers natively.

* fix(flet-charts): restore await-based backpressure for matplotlib frames

The sync `apply_full` + side-channel `_on_frame_applied` callback was
losing matplotlib "draw" events in pyodide mode. Sequence:

1. `_receive_loop` reads frame bytes, calls `apply_full(bytes)` — sync,
   returns immediately.
2. Loop iterates, reads next event from `_receive_queue`.
3. Next event is a `"draw"` notification matplotlib emitted just after
   the previous frame (figure dirty again from mouse drag).
4. Gate check: `_waiting=True` (ack hasn't arrived from Dart yet) →
   **drop the event**.
5. Ack arrives 200+ ms later, `_waiting=False`, but the queue is empty
   and matplotlib doesn't re-emit "draw" until next mouse event.

Result in pyodide: ~1.5 fps observed, vs the 0.85 `_invoke_method`
implementation's much higher rate. The 0.85 pattern wasn't faster
because it lacked an ack — it had one (the INVOKE_METHOD reply). It
was faster because `await self._invoke_method(...)` **blocked the
`_receive_loop`** during the round-trip, so matplotlib events queued
naturally in `_receive_queue` and were processed in order after the
await returned, rather than being eagerly drained against a stale gate.

Fix: re-introduce the await pattern at the canvas level.

* `MatplotlibChartCanvas.apply_full / apply_diff / clear` are now
  async. Each enqueues a per-frame `asyncio.Future`, sends the channel
  packet, and awaits the future.
* `_on_dart_message` resolves the head future when `[0xFF]` arrives.
* `MatplotlibChart._receive_loop` awaits each `apply_*` call —
  matplotlib events that arrive during the wait stay queued and are
  processed after the ack returns. Same behaviour shape as 0.85's
  `_invoke_method` round-trip, but over the DataChannel transport
  (no msgpack on the bulk payload).
* `set_on_frame_applied(cb)` is preserved as a pure observer callback
  for instrumentation (e.g. the 3D example's stats panel) — no longer
  load-bearing for backpressure.

The 3D example's `apply_full` / `apply_diff` wrappers updated to
`async def` + `await` accordingly.

* ci: fix web client build after flet.version.pyodide_version removal

The multi-version Python PR (#6577) removed flet.version.pyodide_version
but the 'Get Pyodide version' step still read it, failing every
'Build Flet Client for Web' run. Resolve the version from the
flet_cli.utils.python_versions registry instead (default release's
Pyodide), and replace the hand-rolled tarball + wheel downloads with
flet_cli.utils.pyodide.ensure_pyodide — the hardcoded
micropip-0.8.0/packaging-24.2 filenames would have silently broken on
the new Pyodide line (3.14's lock resolves micropip 0.11.1), since
curl without -f writes 404 pages into the .whl files.

Cherry-picked from 2d8f4a1 on fix-android-arch-filtering.

Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>

* docs(breaking-changes): drop dead /docs/extending-flet/data-channels link

The 0.86 protocol-framing breaking-change guide linked to a DataChannel
API reference page that doesn't exist yet — there's no extending-flet/
folder, and no DataChannel doc has been authored. Docusaurus' broken-link
scan failed the docs build on every push. Replace the link with prose
pointing at the data_channel.py module docstring; dedicated reference
pages can land in a follow-up once the API doc generator covers it.

* ci: bump Node 20 actions to Node 24 versions

GitHub Actions emitted Node.js 20 deprecation warnings on every job in
run 27457389406. Node 20 will be removed from runners 2026-09-16. Bump
the affected actions to their latest Node 24 majors across all workflows:

- actions/checkout@v4 → v6
- actions/setup-node@v4 → v6 (v6 limited auto-cache to npm, the website
  uses yarn via corepack — no caching behavior change)
- actions/upload-artifact@v4 / v5.0.0 → v7
- actions/download-artifact@v4 → v8
- astral-sh/setup-uv@v6 → v8.2.0 (v8 dropped the major @v8 tag for
  supply-chain reasons, full tag required)
- dart-lang/setup-dart@<old SHA> → v1.7.2

All six actions' action.yml now declare `runs.using: node24`.

* fix(tester): preserve ValueKey value type in find_by_key

`ValueKey(controlKey.value)` produces `ValueKey<Object>` because
`controlKey.value` is statically typed Object. Flutter's `ValueKey.==`
is runtimeType-strict, so `ValueKey<Object>('foo')` never equals
the `ValueKey<String>('foo')` that ControlWidget assigns to the
rendered widget — making `find_by_key("foo")` from Python tests
find 0 widgets.

Mirrors the fix already applied in control_widget.dart (7367050).
Switch-dispatch on the runtime type so String → ValueKey<String>,
int → ValueKey<int>, etc.

Resolves the cascade of "RangeError: no indices are valid: 0" and
"assert 0 == 1" failures across apps, controls/core, controls/material,
controls/cupertino, and controls/theme integration suites.

* fix(tests): update example imports after folder rename in #6545

#6545 renamed 131 example folders (mostly basic/ → descriptive
control name, plus example_1/2/3, nested_themes_1/2 collapsing, and
removing the basic/ wrapper where there was only one example) but the
matching imports in packages/flet/integration_tests/examples/ were
never updated. Test collection failed with ModuleNotFoundError on
every affected suite (examples/apps, examples/extensions, and
examples/controls/{core,cupertino,material}).

Rewrites the 45 test files referencing those modules to the new paths
derived from the rename history of commit 1b2e914.

* Docusaurus 3.10.1 and Node.js 24

* feat(cli): add 'flet --version --json' and source CI version/dep reads from it

Add a --json flag to 'flet --version' that emits a machine-readable
document (Flet/Flutter versions, supported Python/Pyodide table, Linux
build deps). CI workflows and the publish docs now read it via jq instead
of importing Flet internals with 'python -c'.

Move the canonical Linux apt dependency list from flet.utils.linux_deps
(runtime package) to flet_cli.utils.linux_deps (build tooling), where it
sits next to python_versions.py and is a same-package import for the CLI.

* ci: pin Windows runners to windows-2025-vs2026

GitHub is redirecting windows-latest to windows-2025-vs2026 by June 15,
2026. Pin the label explicitly in the Flet Build Test and Build & Publish
workflows to silence the redirect notice and make the image deterministic.

* Allow flutter_secure_storage updates (^10.0.0)

Update pubspec.yaml dependency for flutter_secure_storage from fixed 10.0.0 to caret ^10.0.0, allowing compatible minor/patch updates instead of pinning to a single patch version. This lets the package accept backwards-compatible releases without manual changes. Fix #6586

* Resolve Python/Pyodide versions from python-build's manifest

Drop flet's hand-mirrored SUPPORTED_PYTHON_VERSIONS table and load the
supported Python/Pyodide/dart_bridge set from python-build's date-keyed
manifest.json — the single source of truth shared with serious_python.

- python_versions.py: pin one PYTHON_BUILD_RELEASE_DATE; fetch that release's
  manifest.json (cached immutably under ~/.flet/cache/python-build, offline
  fallback to cache) and parse it lazily. Module constants become
  get_supported_python_versions()/get_default_python_version(); resolution
  logic unchanged. Dev/CI overrides: FLET_PYTHON_BUILD_RELEASE_DATE,
  FLET_PYTHON_BUILD_MANIFEST.
- flet build: pass only SERIOUS_PYTHON_VERSION; serious_python derives the full
  version, build date, and dart_bridge version from its committed snapshot.
  Drops the SERIOUS_PYTHON_FULL_VERSION/SERIOUS_PYTHON_BUILD_DATE exports.
- flet --version: drop the Python/Pyodide matrix (stays offline); --json keeps
  flet/flutter/linux_dependencies.
- ci.yml: read the default Pyodide version via the manifest-backed resolver
  instead of jq over `flet --version --json`.
- Docs: update the removed-pyodide-version-export guide + CHANGELOG to the new
  accessors; document the pin in CONTRIBUTING.
- Add offline tests driven by FLET_PYTHON_BUILD_MANIFEST.

* Pin screen_brightness_macos to 2.1.2 (SPM macOS deployment-target regression)

screen_brightness_macos 2.1.3 ("Fix: swift package manager warning") ships a
Package.swift declaring macOS 10.11, below FlutterFramework's 10.15 SPM floor,
so `flutter build macos` fails to resolve with Swift Package Manager enabled.
Pinning the app-facing screen_brightness alone doesn't help — the federated
macOS implementation is separately versioned. Override the impl to the last
good 2.1.2 in both the build template and the client app.

Upstream: aaassseee/screen_brightness#99

* flet build: clean build dir when the bundled Python version changes

Switching --python-version (or requires-python) between builds left the previous
version's compiled bytecode in the reused build directory's native bundles
(stdlib/site-packages .pyc), crashing the app at runtime with
`ImportError: bad magic number`. Record the resolved Python short version in the
build dir and, when it changes, wipe the build dir so the native bundles are
regenerated for the new interpreter.

* feat(testing): add `flet test` for on-device integration testing

Let Flet users write and run integration tests for their apps. Tests live in
`tests/` and drive the app running on-device (built monolithic app with embedded
Python over dart_bridge): find controls by key/text, tap, enter text, assert
state and screenshots.

- New `flet test` CLI command (mirrors `flet debug`): provisions a Flutter test
  host via the build pipeline in test mode, packages the app's Python, then runs
  pytest. Supports platform positional + `--device-id`, `-k`, `-u` (goldens), `-v`.
- pytest plugin shipped with `flet` (zero boilerplate): function-scoped
  `flet_app` fixture (fresh app per test); also runs via plain `uv run pytest`,
  with `FLET_TEST_PLATFORM`/`FLET_TEST_DEVICE`/`FLET_TEST_GOLDEN` env overrides.
- Independent tester channel: Dart `RemoteWidgetTester` <-> Python `RemoteTester`
  over a raw socket with length-prefixed JSON frames, separate from Flet's
  transport. The Flutter WidgetTester driver moved into `packages/flet` behind
  `package:flet/testing.dart`, shared by host (`runFletHostTest`) and device
  (`runFletDeviceTest`) modes.
- `flet create` scaffolds `tests/test_main.py` + pytest config; build template
  gains a test_mode-gated integration_test entry point.
- Docs: getting-started/integration-testing guide + cli/flet-test reference.

* fix(testing): extract integration-test driver into flet_integration_test package

`dart pub publish --dry-run` for `packages/flet` failed: its lib/ imported the
dev-only `flutter_test`/`integration_test` packages, which pub forbids (packages
used in lib/ must be in `dependencies`). Putting the driver inside `flet` was the
wrong call — it can't ship to pub.dev that way.

Move the concrete Flutter driver (flutter_tester, flutter_test_finder,
device_test, host_test, remote_widget_tester, frame_decoder) into a new
`packages/flet_integration_test` package (publish_to: none) that depends on flet
+ integration_test. flet's published lib/ no longer references any test-only
package; the abstract Tester/TestFinder interfaces stay in flet as before.

- packages/flet: drop integration_test dev-dep, remove lib/testing.dart entry.
- packages/flet_integration_test: new package; cross-package imports of
  Tester/TestFinder/Lock collapse to package:flet/flet.dart; redundant dart:io
  imports dropped (flet re-exports it). Standard Flutter .gitignore.
- client + build template: import package:flet_integration_test instead of
  package:flet/testing.dart; add it as a path dev-dependency (test_mode-gated in
  the template).
- build_base: for local dev, rewrite the flet_integration_test path the same way
  it already rewrites flet (it's publish_to:none, only resolvable from the repo).

Verified: flet `pub publish --dry-run` passes; flet_integration_test and client
integration_test analyze clean.

* fix(testing): inject test driver at build time instead of templating it

The build template referenced flet_integration_test by a repo-relative path
gated with a Jinja `{% if %}` block. That broke two things for the released
(zipped) template:

1. The release pipeline patches the template pubspec with patch_pubspec_version.py,
   which does a yaml.safe_load round-trip. The uncommented `{% if %}` block made
   the pubspec invalid YAML, so the patch/zip step would fail on tag.
2. A repo-relative path can't resolve once the template is zipped and shipped.

Stop putting the test dependencies in the template pubspec. flet-cli now injects
them after rendering (build_base.create_flutter_project), gated on test_mode:
- local dev: flet_integration_test by path (+ dependency_override), like flet.
- end user: flet_integration_test as a git dependency pinned to this flet
  version's tag (the package is publish_to:none, never on pub.dev) — consistent
  with how the template already pulls serious_python from git.

The template pubspec is now plain valid YAML again (the patch tooling round-trips
it cleanly) and a normal `flet build` never pulls the test driver.

flet_integration_test depends on flet by version (not path) with a local
dependency_override, so flet unifies to a single source across the graph whether
it's consumed by path (repo) or git (user); flutter_test becomes a regular dep so
test hosts get it transitively.

Verified: template pubspec parses; patch_pubspec_version.py round-trips it in both
release and dev modes; `flet test` provisioning injects the deps and
`flutter pub get` resolves; flet_integration_test analyzes clean.

* docs(testing): add Testing types reference + link API symbols in guide

Add a "Testing" section under Reference > Types with stubs for FletTestApp,
Tester, Finder and DisposalMode (website/docs/types/testing/), wired into the
sidebar. Replace the stale top-level mkdocs-style stubs (types/finder.md,
flettestapp.md, tester.md) that used the old `:::` syntax.

Link every API class/method/property mentioned in the integration-testing guide
to its reference page using the `[label][flet.testing.Symbol]` xref format, like
other docs.

* docs(testing): fix unresolved reST xrefs in FletTestApp docstrings

The new FletTestApp reference page surfaced two reST cross-references that didn't
resolve (caught by the docs build's reST xref check):

- `FletTestApp.tester` referenced the internal, undocumented
  `flet.testing.remote_tester.RemoteTester` via :class: — changed to plain code.
- `create_gif` referenced `:meth:`Page.take_animation``; the documented symbol is
  `flet.BasePage.take_animation` — corrected the target.

Verified: full docusaurus build + check_docs.sh pass (reST xrefs OK).

* ci(testing): add flet-test workflow + Counter test app

New `.github/workflows/flet-test.yml` runs `flet test` (on-device) across macOS,
iOS, Windows, Linux and Android in a single matrix, against a new
`sdk/python/examples/apps/flet_test_counter` app (keyed +/- buttons,
interaction-only test, no goldens). The embedded app is built with Python 3.14;
the host venv stays on 3.13. Per-platform handling: xvfb for Linux, a booted
simulator (UDID) for iOS, reactivecircus/android-emulator-runner (KVM) for
Android. Pins the in-repo flet packages like flet_build_test.

Allowlist UDID/udid in typos (legitimate iOS term + simctl JSON field).

Verified locally: `flet test macos --python-version 3.14` -> 1 passed.

* fix(testing): drive device-mode integration tests with benchmarkLive

After merging flet-0.86, `flet test` (device mode) hung: the very first
`WidgetTester.pump()` never returned, so the tester never connected and the run
timed out.

Root cause (isolated by single-variable bisection): the build template's #6616
`BootHost` boot structure (`runApp(StatefulWidget)` -> `initState` ->
`await prepareApp()` -> `setState`) deadlocks the default `fadePointers` frame
policy under `flutter test` — `pump()` schedules a frame and blocks on
`_pendingFrame` until it's drawn, but that frame never arrives during this boot.
Swapping only `BootHost` back to the old `runApp(FutureBuilder(...))` shape made
it pass reliably on the same Flutter 3.44.3, and the old structure on 3.44 was
fine — so it's the boot structure, not the Flutter bump or the boot screen
animation.

Fix lives entirely in the test driver (no shipping-app/template change):
- `runFletDeviceTest` sets `framePolicy = benchmarkLive` — Flutter's documented
  policy "for running the test on a device". Its `pump()` doesn't wait on an
  engine-drawn frame (just delays), while framework-requested frames (the app's
  setState/animations, incl. Python's dart_bridge updates) still render.
- Because benchmarkLive pumps don't advance wall-clock or force frames, the
  driver waits for *sustained* tree idle (the boot screen's CircularProgress
  Indicator keeps it busy until Python renders the page) before connecting, and
  `FlutterWidgetTester.pumpAndSettle` (gated on benchmarkLive only, so host mode
  is untouched) pumps with real delays until the tree stays idle — so async
  tap -> on_click -> control-update round-trips land before asserting.

Verified: counter app (+/- buttons, 0 -> 1 -> -1) passes 3/3 on macOS / Flutter
3.44.3; host-mode driver unchanged.

* fix(testing): use FutureBuilder boot path under test + propagate flutter test failures

Replaces the benchmarkLive approach (previous commit), which was wrong: it
un-blocked WidgetTester.pump() but its continuous redraw triggered a
rebuild-during-build (`!_dirty`) that *failed* the on-device flutter test — and
that failure was hidden by a false-green bug (below). Verified by comparison on
Flutter 3.44.3: BootHost+benchmarkLive => `!_dirty`, 0 passed/1 failed; the
FutureBuilder boot path => `All tests passed!`, clean, 3/3.

Two fixes:
- Template main.dart: under `FLET_TEST`, boot via the old
  `runApp(FutureBuilder(future: prepareApp(), ...))` shape instead of #6616's
  `BootHost`. BootHost's StatefulWidget/initState-async/setState boot deadlocks
  `tester.pump()` (it blocks on a frame that never arrives during boot);
  FutureBuilder is driven cleanly. Production builds are unchanged (still
  BootHost). The app — embedded Python over dart_bridge, FletApp — is identical.
- FletTestApp.teardown: check the `flutter test` process exit code and raise if
  non-zero. The host-side find/tap assertions can all pass while the on-device
  `testWidgets` body fails (e.g. a widget exception), so pytest was reporting a
  pass over a failed flutter run — a false green. Now surfaced.

Revert the benchmarkLive changes to the device driver (device_test.dart,
flutter_tester.dart) — default frame policy works with the FutureBuilder path.

Verified: counter (+/-, 0 -> 1 -> -1) genuinely passes 3/3 on macOS / 3.44.3
(`All tests passed!`, no `!_dirty`, pytest + flutter agree).

* fix(testing): use resolved Flutter exe path when spawning flutter test (Windows)

On Windows the device-mode run failed at fixture setup with
`FileNotFoundError [WinError 2]`: FletTestApp spawned `flutter test` via
`create_subprocess_exec("flutter", ...)`, but Windows' CreateProcess does no
PATHEXT lookup, so a bare "flutter" (really `flutter.bat`) isn't found.

`flet test` already resolves the real Flutter executable (full path, `.bat` on
Windows) for provisioning — pass it to the pytest subprocess as
`FLET_TEST_FLUTTER_EXE` (and propagate it via `_TEST_ENV_KEYS`), and have
FletTestApp use it as argv[0], falling back to a bare "flutter" on PATH.

* fix(testing): name the test binary after project_name (Windows/Linux desktop)

Windows and Linux `flet test` failed after a successful build with
`Unable to start executable ... Failed to find "<project_name>.exe/binary"`.
Root cause (the "doesn't build on desktop" hypothesis was wrong — Flutter does
build): the Windows/Linux runner sets the executable OUTPUT_NAME to
`artifact_name`, but `flutter test -d <desktop>` launches the binary by the
Flutter pubspec `name` (== project_name). When `[tool.flet] artifact` differs
from the project name (e.g. `flet-test-counter` vs `flet_test_counter`), the
built binary and the launched path don't match. macOS is unaffected (its `.app`
is located by the product/artifact name).

In test mode, pin `artifact_name = project_name` so the desktop binary's name
matches what the integration-test host launches. Verified: macOS still passes
(now builds `flet_test_counter.app`); fixes the Windows/Linux launch path.

* fix(test): pass serious_python native-build env to flet test

flet test spawns its own 'flutter test integration_test' (via FletTestApp)
instead of going through _run_flutter_command, so it never received the
serious_python build-time env that flet build sets. Most critically
SERIOUS_PYTHON_APP was unset, which makes the Android packageApp Gradle task
early-return and leave a stale app.zip (old-Python main.pyc) in the APK,
crashing the embedded runtime with 'ImportError: bad magic number'.

Extract the serious_python native-build env into a shared
_serious_python_build_env() and use it from both _run_flutter_command and
flet test's _flutter_path_env, so the two paths bundle an identical app and
can't drift. Adds SERIOUS_PYTHON_APP, SERIOUS_PYTHON_ANDROID_EXTRACT_PACKAGES
and SP_NATIVE_SET to the test env (and _TEST_ENV_KEYS).

* ci(flet-test): capture android logcat; force software GL on linux

Android: stream device-side logs (embedded Python stdout/stderr, Flet,
Flutter, native crashes) to a file during the run and dump them in a
collapsible group afterwards, so on-device failures are diagnosable from CI.

Linux: xvfb has no GPU, so the Flutter GTK app crashes on GL context
creation (exit 79); install Mesa's software GL (llvmpipe) and force it via
LIBGL_ALWAYS_SOFTWARE/GALLIUM_DRIVER.

* ci(flet-test): non-blocking android logcat dump; linux GL diagnostics

Android: stop streaming logcat live (verbose device logs bog down the
software emulator and stall the job); instead dump the relevant slice of the
ring buffer after the run with non-blocking 'adb logcat -d'.

Linux: add a failure-diagnostic step that reports the active GL renderer
(glxinfo) and runs the built bundle directly to surface its exit-79 crash
output, which the test harness otherwise swallows.

* fix(flet-test): wait for first render in counter test; robust CI diagnostics

The counter test asserted on the first frame, but on a device the embedded
Python cold start (interpreter init + import flet + main()) can take several
seconds — longer than the device driver's fixed warmup — so find_by_text('0')
ran before the app rendered and returned 0 on the slow CI emulator (passed
locally on a faster one). pump_and_settle only settles Flutter frames, not a
pending python->dart round-trip, so poll for the first render instead.

CI: make the android logcat dump run even on failure (|| CODE=$?) with a
tight python+crash filter that won't bog the emulator; cap the linux
bundle-diagnostic with timeout so it can't hang the job.

* ci(flet-test): non-blocking android logcat; disable AT-SPI a11y on linux

Android: drop the background 'adb logcat &' (a streaming child can keep the
emulator-runner script from finishing); dump the ring buffer after the run
with non-blocking 'adb logcat -d' instead.

Linux: the app and software GL are fine (glxinfo shows llvmpipe; the bundle
runs directly without crashing) — exit 79 is specific to the integration_test
path, which enables the semantics tree and makes GTK embed an ATK a11y socket
that doesn't exist under xvfb. Disable the AT-SPI bridge (NO_AT_BRIDGE/GTK_A11Y).

* fix(flet-test): kill android false-green; poll 60s for render; logcat to artifact

The android job reported success while pytest actually failed: the
emulator-runner ran the multi-line script such that 'exit $CODE' saw an empty
CODE (and a '\'-continuation in the logcat line broke, dumping the entire
unfiltered logcat = ~58k console lines). Run the script as a single folded
line so the test command is last and its exit code is the job's, and write a
filtered device log (embedded Python + crashes only) to a file via an EXIT
trap, uploaded as an artifact instead of streaming to the console.

Also: the counter never rendered within the 10s poll window on the slow CI
emulator (cold-start embedded Python is much slower there), so poll on a 60s
deadline instead of a fixed 40 attempts.

* test(flet-test): pull serious_python from x86_64 sysconfigdata fix branch

Temporarily override serious_python_android + serious_python_platform_interface
to flet-dev/serious-python#218 (fix/android-x86_64-sysconfigdata) so the android
x86_64 CI leg validates the fix end-to-end (embedded Python no longer crashes
with ModuleNotFoundError: _sysconfigdata__android_x86_64-linux-android).

Locally confirmed: pubspec.lock resolves to the branch and stdlib.zip now ships
both aarch64 and x86_64 _sysconfigdata. Revert to the pub.dev release once #218
ships.

* ci(flet-test): add 40-min job timeout so a hung emulator auto-cancels

The android on-device run can wedge (emulator goes offline) and run until the
default 6h limit; cap the job at 40 minutes.

* fix(testing): don't hang in RemoteTester.stop() waiting on a dead client

After an on-device test passes, teardown calls RemoteTester.stop(), which did
'await self._server.wait_closed()' with no timeout. wait_closed() blocks until
the active _handle_client finishes, but _read_loop blocks on readexactly() until
EOF — and the on-device app's socket close doesn't always deliver EOF to us
(seen on Linux), so the asyncio loop hung forever after 'All tests passed!' (the
flet test process never exits). Cancel the read task so _handle_client completes,
close the writer, and bound wait_closed() with a timeout.

* fix(testing): target generated Flutter device test driver

* ci(flet-test): capture x86_64 linux integration-test verbose diagnostic

The linux job fails with 'No tests were found' + exit 79 on the x86_64 official
flutter (passes on arm64). flet_test_app already uses the file-form target and
verifies app_test.dart is non-empty, so it's neither. Re-run the integration
test directly with --verbose (unreachable dummy server) to capture which build
target/entrypoint flutter uses, whether the testWidgets body runs, and the exit
reason; upload the full verbose log as an artifact.

* fix(testing): skip linux ready-to-show wait under flet test

* fix(testing): show linux test window without ready wait

* fix(testing): size hidden linux integration test surface

* ci(flet-test): remove linux diagnostic artifact

* refactor(testing): use directory target, keep generated-driver guard

The dir->file change in 17d368b was not what fixed Linux (the window-realize
/ ready-to-show fixes were; both the dir and file forms reported 'No tests were
found' until then). Revert the flutter test target to the directory form
('integration_test') and keep only the useful part: in device mode, validate
the generated integration_test/app_test.dart exists and is non-empty so a
missing/empty driver surfaces as a clear error instead of a confusing
'No tests were found'.

* test(flet-test): simplify counter test to plain pump_and_settle + assert

Drop the _find_text_when_ready polling helper. It was a band-aid for the
android render race, but the real cause was the serious_python x86_64 crash
(PR #218) — now fixed. Try the plain template-style test and let CI confirm the
counter renders in time on the slow emulator.

* Bump Flutter SDK to 3.44.4

Update .fvmrc to pin Flutter version 3.44.4 (patch bump from 3.44.3) to ensure a consistent SDK across development and CI environments.

* ci(flet-test): drop dead mesa-utils + obsolete a11y env

- mesa-utils was only used by glxinfo in the (removed) Diagnose Linux step.
- NO_AT_BRIDGE/GTK_A11Y were added mid-debugging but didn't fix Linux (the
  window realize / ready-to-show change did); the Atk-CRITICAL warnings were
  non-fatal. Remove them and the now-inaccurate comment. Keep the software-GL
  env (xvfb has no GPU) and the android logcat artifact (only window into
  on-device failures).

* feat(cli): install Flutter on arm64 Linux via git clone; add CI leg

Flutter publishes no prebuilt arm64 Linux SDK (releases are x64-only), so
flet-cli's install_flutter downloaded a broken x64 tarball on arm64 Linux. For
arm64 Linux, clone the SDK at the version tag instead; the first 'flutter' run
then fetches the arch-appropriate engine/Dart artifacts (how fvm/git installs
work).

Add a 'linux-arm64' CI leg (ubuntu-24.04-arm) that skips the Flutter setup
action so 'flet test' installs Flutter via this path, exercising it end-to-end.

* fix(cli): precache engine after arm64 Linux Flutter clone

A bare git clone has no bin/cache, so 'dart run serious_python:main' failed
with 'could not find package sky_engine ... solving failed'. Run
'flutter precache --linux' right after the clone to populate the engine
artifacts (sky_engine + the Linux desktop engine) the prebuilt archives ship.

* test(flet-test): matrix Python 3.12/3.13/3.14; app shows + test asserts version

- CI matrix now crosses each platform with python 3.12/3.13/3.14 (job env
  PYTHON_VERSION/EXPECTED_PYTHON_VERSION from matrix; dropped the workflow_dispatch
  python_version input the matrix supersedes).
- Counter app displays 'Python <platform.python_version()>'.
- test_counter asserts the app reports the expected major.minor
  (EXPECTED_PYTHON_VERSION), falling back to 'any version shown' locally.
  Validated on macOS (renders Python 3.14.6; 1 passed).

* chore: use released serious_python 4.1.1; drop temp git override

serious_python 4.1.1 (with the x86_64 _sysconfigdata fix, PR #218) is on
pub.dev. Bump the build template serious_python 4.1.0 -> 4.1.1 and remove the
temporary git override from flet_test_counter (the fix branch was deleted after
release, which broke 'flutter pub get' on fresh runners — the linux-arm64 legs
failed with 'could not find git ref fix/android-x86_64-sysconfigdata').

* test: remove test_flet_test_app.py unit test

It imported FletTestApp, which pulls in the screenshot-comparison deps
(numpy/Pillow/scikit-image) from the optional 'test' extra at module load.
The base unit-test suite installs flet without that extra, so collection
failed with ModuleNotFoundError: No module named 'numpy'. The
__flutter_test_target device-mode guard is exercised end-to-end by the
flet-test on-device workflow.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
… 4.2.0 (#6628)

* Pin python-build 20260630 (armeabi-v7a for 3.13/3.14); serious_python 4.2.0

python-build 20260630 publishes armeabi-v7a (32-bit ARM) for Python 3.13 and
3.14, not just 3.12 (built with dart_bridge 1.4.1). Bump the pinned release
date so the manifest-driven ABI set picks them up automatically, and bump the
build template's serious_python pin to 4.2.0.

- python_versions.py: PYTHON_BUILD_RELEASE_DATE 20260618 -> 20260630; refresh
  the now-stale "only 3.12 carries armeabi-v7a" comment.
- build.gradle.kts template: collapse the dead per-version abiFilters fallback
  (target_arch is always set from the manifest) and drop the PEP 738 comment.
- pubspec.yaml template: serious_python 4.1.1 -> 4.2.0.
- CHANGELOG: correct the "3.13+ drops armeabi-v7a" note; Pyodide 3.14 314.0.1.

* ci: skip integration/build-test workflows on tag pushes

macos-integration-tests, flet-test and flet-build-test only need to run on
branch/PR pushes; on release tag pushes they're redundant (and can fail).
Add `tags-ignore: ['**']` to each push trigger so tags no longer fire them.
* Add flet-mcp package and integrate CLI/CI

Introduce a new flet-mcp Python package (pyproject, README, package code and data) providing an MCP server for LLM agents, including ApiStore and bundled icons/docs data. Integrate flet-mcp into the CLI by registering an optional "mcp" subcommand only when the package is installed and add an optional dependency entry in flet-cli's pyproject. Update GitHub Actions to build, package, and publish flet-mcp (new build_flet_mcp job and inclusion in the publish step). Also add .gitignore for generated MCP artifacts and include various minor Python package updates.

* Add MCP build modules and indexing tools

Introduce a new flet_mcp build subpackage and tooling to generate MCP data. Adds api.py (API reference builder using Griffe, injects icon enums and extracts CLI help), docs.py (mkdocs search_index -> SQLite FTS5 indexer), examples.py (pyproject.toml-based examples indexer), and indexer.py (orchestrator that runs examples/docs/API pipelines and writes mcp.db/api.json). Update .gitignore to un-ignore the build subpackage and tweak icons.yml comment/content.

* feat(flet-mcp): gate tool groups behind FLET_MCP_ENABLE_* env vars

Defaults: API and ICONS groups on, EXAMPLES/DOCS/CLI off, so the
hallucination-reduction starter set is what consumers get out of the
box. Server instructions enumerate the active groups so the model sees
only relevant guidance.

* docs(flet-mcp): document FLET_MCP_ENABLE_* tool-group gating

Adds a 'Toggling groups' section to the package README explaining
which tool groups are on by default, the env var per group, and how
the active set is surfaced to MCP clients via the server's
initialize instructions.

* docs(flet-mcp): document mcp-build group and SDK-workspace build

* feat(flet-mcp): merge get_control_api+get_type_api into get_api, mark async methods, classify @value as dataclass

- Single get_api(name) tool replaces get_control_api + get_type_api. Looks
  across controls, services, dataclass types, and events and tags every
  match with a 'kind' discriminator ('control'/'service'/'type'/'event').
  One tool means one lookup instead of multiple guess-the-bucket attempts.

- Methods declared async def are now marked '"async": true' in the api.json
  methods list. The get_api tool docstring and the FastMCP server
  instructions both call this out, so callers know to await them (e.g.
  page.window.center() is async — handler must be async def).

- Treat Flet's @value decorator as a dataclass in the API builder. Most
  styling/value classes (ButtonStyle, Padding, ...) use @value, so prior
  builds were missing 70% of them. Type count: 28 -> 199.

* fix(flet-mcp): index Page by treating BasePage as a control base

Page extends BasePage, which extends BaseControl, but the classifier
only matched direct inheritance from BaseControl/LayoutControl/
AdaptiveControl/Service. Adding BasePage to the known control bases
catches Page itself (the most-used class in Flet). Controls: 166 -> 168
(Page + anything else inheriting from BasePage).

* feat(flet-mcp): expose pip package name on every API entry

get_api responses now carry a 'package' field naming the pip-installable
package the class lives in. Derived from the first segment of 'module'
with underscores swapped for hyphens — so flet_video -> flet-video,
flet_audio -> flet-audio, etc. Core flet classes stay 'flet'.

Agents can now route 'use Video' to 'first add flet-video to deps'
without having to guess the distribution name from the import path.
The tool docstring, FastMCP server instructions, and README all call
this out so consumers know to surface it to the user.

* feat(flet-mcp): walk inheritance recursively, fold enums into get_api, mark deprecated classes

Three related changes to make get_api the true universal verifier:

1. _is_control_class now walks the inheritance chain through a global
   class-name registry built in a pre-pass over all loaded packages.
   Catches transitive subclasses of BaseControl/LayoutControl/
   AdaptiveControl/Service through intermediate bases (DialogControl,
   BasePage, ...) without hardcoding every link. AlertDialog, BottomSheet,
   SnackBar, Banner, Page, and ~80 other previously-missed controls now
   resolve. Controls indexed: 168 -> 248. BasePage drops out of the
   hardcoded base set since it's no longer needed.

2. api_store.get() now falls through to enums after controls/types/events.
   Calling get_api('MainAxisAlignment') returns the enum directly instead
   of 'not found' (it used to force a separate get_enum round-trip). Large
   enums (Icons, CupertinoIcons) come back truncated, same as get_enum.

3. Deprecated classes are kept in the index but marked with a
   'deprecated' object carrying reason/version/delete_version parsed from
   @deprecated_class. ElevatedButton now answers with
   'deprecated: {reason: "Use Button instead."}' — the model sees both
   the verdict and the replacement in one shot.

* Annotate Gradient colors with ColorValue

for Gradient, list[ColorValue] instead of list[str]

* Clarify colors cookbook usage and examples

* feat(flet-mcp): defer docs tools, guard unindexed queries, document in-process use

- Add table guards to search_docs/get_doc so they return empty results
  instead of crashing when no docs index is built
- Mark the docs index as deferred (mkdocs search_index.json no longer
  produced after the Docusaurus + Algolia migration) in README and the
  --docs CLI help
- Document in-process FastMCP usage via fastmcp.Client(mcp); fix stale
  get_control_api reference (now get_api) and bump the Pydantic AI model

* docs(cli): add flet mcp command page to fix broken docs link

The CLI docs index auto-lists every registered command (including the new
'mcp' command), so the Docusaurus build failed on a broken link to a missing
flet-mcp page. Add the cli/flet-mcp.md stub (importing the generated
cli-mcp.mdx partial) and the sidebar entry, matching the other commands.

* docs(cookbook): add Flet MCP server article

Explain how to install and run the flet-mcp server, configure it in AI
clients, toggle tool groups, and consume it from a custom agent via
Pydantic AI or an in-process FastMCP client.

* docs(flet-mcp): use single backticks in docstrings

* docs(breaking-changes): remove orphaned duplicate guides

Drop the unprefixed data-channel-protocol-upgrade.md,
default-bundled-python-3-14.md, and removed-pyodide-version-export.md —
identical, unreferenced leftovers of the v0-86-0-* rename. Only the
v0-86-0-prefixed files are linked from the sidebar.

---------

Co-authored-by: InesaFitsner <inesa@appveyor.com>
* initial commit

* improvements

* improve caching

* zizmor action

* update

* delete sidebars.js

* fix #6614: `ProgressRing.year_2023` being ignored
- python_versions.py: PYTHON_BUILD_RELEASE_DATE 20260630 -> 20260701. The new
  python-build release only rebuilds the iOS runtime (now bundles the
  `_multiprocessing` extension, importable-not-spawnable); the manifest is
  otherwise identical to 20260630, so the supported Python/Pyodide/dart_bridge/
  ABI set is unchanged.
- build template pubspec.yaml: serious_python 4.2.0 -> 4.2.1, which ships the
  ctypes `.dylib` iOS-simulator load fix (serious_python#223).
- CHANGELOG: note the iOS simulator .dylib fix + `_multiprocessing`.
The `build_flet_mcp` job built the wheel at flet-mcp's static pyproject
version (0.1.0) instead of the release version, because it only ran
`patch_python_package_versions` (which stamps just the core flet/flet-cli/
flet-desktop/flet-web packages) and skipped the per-extension
`patch_toml_versions` stamp that `build_flet_extensions` applies to every
other package. So every publish re-attempted `flet_mcp-0.1.0`, which already
exists on PyPI, and `uv publish` aborted the whole job with `400 File already
exists`.

Stamp flet-mcp's pyproject with $PYPI_VER before `uv build`, exactly as the
extensions job does, so flet-mcp ships at the unified release version.
…ntal builds (#6634)

register_flutter_extensions() unconditionally wiped the permanent
build/flutter-packages directory before repopulating it only when the
temp dir exists. On incremental builds the package hash is unchanged, so
serious_python is invoked with --skip-site-packages and does not copy
Flutter packages to the temp dir. The absent temp dir then meant the
wiped extensions were never restored, breaking 'flutter build web'
(unresolved web plugins such as audioplayers_web, camera_web, etc.).

Track when the package step ran with --skip-site-packages and skip the
wipe/move in that case, keeping the previous build's extension copy. A
removed extension changes the package requirements, flips the hash, and
takes the full (non-skip) path that still clears stale extensions.

Regression from #6608.
auto_scroll only scrolled to the end from ScrollableControl.build(), so it
followed added children but not content that grows *in place* (e.g. text
streamed into an existing child) — that never rebuilds the scrollable.

- Add a ScrollMetricsNotification listener that re-pins to the end whenever the
  scroll extent grows, even without a rebuild.
- Track the user's position; suspend pinning while they've scrolled away from
  the end and resume when they return.
- Add an auto_scroll_animation property (duration + curve) to control the
  scroll; defaults to the historical 1s ease, set duration 0 for an instant
  jump.
…links survive) (#6639)

* fix(build): select web URL strategy before boot, not in prepareApp()

The build template called usePathUrlStrategy() from prepareApp(), which runs
after the boot UI mounts. By then Flutter's web routing had already initialized
under the default hash strategy, so path-routed web apps got /#/... URLs. Move
the strategy selection to the first statement in main(), before
FletDeepLinkingBootstrap.install() (which calls ensureInitialized) and any
runApp() — matching the before-boot ordering already used in client/lib/main.dart.

Necessary for path routing but not sufficient for cold-start deep links; see the
follow-up boot-overlay fix.

* fix(build): don't let the boot-screen MaterialApp report route '/' to the browser

The boot overlay wrapped the boot screen in MaterialApp(home: ...). A MaterialApp
with `home` builds its Navigator with reportsRouteUpdateToEngine: true, so on web
it pushes the home route '/' to the browser URL the moment it mounts — during
boot, before FletApp's real router reads the location. A cold-start deep link
like /gallery or /apps/<id> was therefore overwritten to '/'.

Verified with a headless-Chromium probe: pre-fix the URL reset /gallery -> /
~1.7s after 'Flutter app loaded' (before the Python worker even ran); post-fix
/gallery holds.

Render the boot screen via `builder:` instead of `home:`. With no
home/routes/onGenerateRoute, WidgetsApp creates no Navigator (per its own docs)
and reports nothing to the engine, so the deep link survives until the app's
router takes over. The overlay never navigates, so it needs no Navigator.

Regression from the 0.86 boot-screen rework (#6616); 0.85.3 rendered a bare
SizedBox during boot, which reported nothing.
…6642)

* Fix auto_scroll end-following past code blocks / nested scrollables

Two issues broke auto_scroll's end-following when a tall child (e.g. a Markdown
code block) was inserted:

- The ScrollMetricsNotification listener reacted to *nested* scrollables (a code
  block renders inside a horizontal scroller), tracking the wrong extent. Only
  react to this scrollable's own metrics (notification.depth == 0).
- _onScroll unpinned purely on proximity (pixels >= max - threshold). A tall
  insert jumps maxScrollExtent past the threshold in one frame while pixels
  stays put, flipping to unpinned so it stopped following. Now it only unpins
  when the user scrolls *up* (pixels decrease) and re-pins at the end; content
  growing beneath a stationary position keeps it pinned.

* Fix embedded FletApp stdout/stderr dropped by the framed transport

The Pyodide↔Dart postMessage transport frames every packet as [type:u8][payload]
(0x00 = MsgPack Flet protocol frame, 0x01 = raw DataChannel frame), and
PyodideConnection.send_message prepends the 0x00 byte. The python_output shim,
however, posted the raw msgpack [7, {...}] with no type byte, so the Dart side
read msgpack's leading 0x92 (array-of-2 marker) as an unknown packet type and
silently dropped every stdout/stderr line — the host page's Console pane never
saw embedded-app output. Prepend the 0x00 type byte in both worker templates
(client + cookiecutter build template).
* Fix O(N²) mount/unmount scans in Session.patch_control

patch_control checked every removed control against all added controls
(and vice versa) with linear any() scans, making each patch O(N²) in the
number of added/removed controls. When a @ft.component re-renders, the
frozen diff reports every matched control as both removed and re-added,
so components rendering large lists pay this cost on every re-render,
blocking the asyncio event loop for the duration.

Precompute added/removed ID sets once so each membership check is O(1),
reducing the whole function to O(N). Behaviour is unchanged:
will_unmount/did_mount still fire only for controls not present in both
sets, and index management, logging, and message sending are untouched.

* Updating CHANGELOG.md for #6651
changelog entry was placed in 0.85.3 instead of 0.86.0, position fixed
…es (#6654)

* flet-mcp: token-efficient, correct get_api + Google-metadata icon search

get_api:
- default response is compact signature-style text (~44% smaller than
  JSON, tokenizes better); format="json" escape hatch
- member docstrings trimmed to first sentence; member=<name> drill-down
  returns full docs; query=<substring> filters members, walking base
  classes (TextField border props live on FormFieldControl)
- event handler types unwrapped (Optional[EventHandler[TapEvent[...]]]
  -> on_tap(TapEvent)); Optional[X] -> X?; field(...) defaults unwrapped
- "fields" member section (dataclass types, event payloads) now included
- name collisions resolved by ranked candidates instead of last-wins:
  Text/Image/Rect no longer shadowed by canvas shapes; dotted names
  (canvas.Text, flet_map.Camera) select alternates; response notes them
- Sphinx :attr:/:class: roles stripped from all rendered docstrings

get_example pages large examples per file; get_doc capped with a
narrowing hint; search_enum_members ranked (exact/prefix/substring)
with Material style variants collapsed.

Icon search rebuilt on Google's fonts.google.com icon metadata (tags +
popularity, Apache-2.0), committed as data/icons.json and refreshed via
`python -m flet_mcp.build.icons`; hand-curated icons.yml and the pyyaml
dependency removed. IconStore no longer imports the flet package
(find_icon was broken on runtime-only installs) and ranks by
tags/popularity with golden-query regression tests.

* Inject web-only app.zip assets in flet build instead of Jinja-in-comments pubspec

The build template's pubspec.yaml guarded the web-only `assets:` section
(app/app.zip, app/app.zip.hash) with Jinja tags hidden in YAML comments.
Any tooling that round-trips the file with a YAML parser — like the CI
release patch script — strips comments, making the assets unconditional
and failing native builds on the missing app/app.zip.

Move the conditionality into flet build: the template pubspec is now
plain YAML, and build_base.py injects flutter.assets into the rendered
pubspec when config_platform is "web", in the same post-cookiecutter
patching pass as the local-dev and test-mode overrides. Native platforms
get no assets entry, as before — serious_python places the app inside
the bundle.

Also remove the vestigial flet_geolocator Jinja block: its dependencies
were themselves commented out, so it could never take effect.

* Drop __init__.py from flet-mcp tests

A second top-level `tests` package collides with packages/flet/tests
under pytest's prepend import mode — whichever imports first owns
`tests` in sys.modules and the other's modules fail collection. Without
the __init__.py, pytest names the modules by file (unique basenames),
which is what `pytest packages/*/tests` in CI needs.
FeodorFitsner and others added 10 commits July 8, 2026 20:09
…6663)

* ResponsiveRow: preserve child state across Row/Wrap layout switches

Key each child's ControlWidget with a GlobalObjectKey tied to its Control
identity so Flutter re-parents existing elements when the layout flips
between Row and Wrap on a breakpoint change, instead of unmounting and
re-inflating the subtree. Stateful descendants (video players, WebViews,
scroll positions) now survive window resizes across breakpoints.

Fixes #6661

* add changelog entry
* feat(build): macOS runner intercepts multiprocessing child re-execs

Python's multiprocessing (spawn/forkserver start methods and the resource
tracker) launches child processes by re-executing sys.executable with a
CPython command line — and in a flet-built app sys.executable is the app
binary itself. Each worker therefore booted a full Flutter GUI instance
that treated '-B' as a dev-mode page URL, never ran the spawn payload,
and never exited: one stray window per worker, hung pools, and
"resource_tracker: process died unexpectedly, relaunching" loops.

Add macos/Runner/main.swift as the explicit entry point (AppDelegate
drops @main; the xib still instantiates and wires the delegate): before
NSApplicationMain, it checks argv against dart_bridge >= 1.5.0's
serious_python_is_mp_invocation and, on a match, exits with
serious_python_main's return code — the embedded interpreter services
the child headlessly (Py_BytesMain) with no AppKit/Flutter
initialization. The child resolves the bundled stdlib/site-packages via
the PYTHONHOME/PYTHONPATH the parent already setenv'd process-wide.

Both entry points are resolved via dlsym from the current process image
(dart_bridge is a static archive force-loaded into the host binary), so
apps built against an older dart_bridge still link and launch — they
just don't get the interception.

Verified end-to-end on macOS with an instrumented probe app:
Process+Queue round-trip with a main.py-defined worker, and a
ProcessPoolExecutor run (4 workers, reused across 8 tasks) — headless
children, correct results, ~4x speedup over sequential, no leftover
processes.

* feat(build): Windows runner intercepts multiprocessing child re-execs

Same mechanism as the macOS runner: multiprocessing spawn children (and
the resource tracker) re-execute sys.executable — the app .exe — with a
CPython command line, which previously booted one GUI window per worker
in dev-mode and never ran the spawn payload.

At the top of wWinMain, before any console/COM/window/Flutter work,
MaybeRunPythonChild() parses the wide command line
(CommandLineToArgvW), loads dart_bridge.dll (falling back to
dart_bridge_d.dll for debug-CRT builds), and resolves the wide-char
entry points serious_python_is_mp_invocation_w / serious_python_main_w
(dart_bridge >= 1.5.0; wide variants because decoding wWinMain argv
through the ANSI code page would be lossy — serious_python_main_w wraps
Py_Main). On a match, the process runs as a headless interpreter and
returns its exit code.

Resolution is dynamic (GetProcAddress with graceful fallthrough), so
apps built against an older dart_bridge launch unchanged. Eagerly
loading the DLL costs nothing on the normal startup path — the plugin
loads it moments later anyway.

Note: not yet runtime-verified on Windows (the macOS equivalent is
verified end-to-end); needs a probe run on a Windows machine.

* feat(build): Linux runner intercepts multiprocessing child re-execs

Same mechanism as the macOS/Windows runners, with an extra reason to
care on Linux: Python 3.14 changed the default start method from fork
to forkserver (gh-84559), and the forkserver's server process is
launched through the same sys.executable re-exec as spawn — so Linux
apps that previously happened to work via raw fork break by default on
the bundled 3.14.

At the top of main(), before any GTK/Flutter initialization,
maybe_run_python_child() dlopen()s libdart_bridge.so — resolved through
the runner's $ORIGIN/lib RUNPATH, exactly the way the Dart FFI's
DynamicLibrary.open() finds it later — and resolves
serious_python_is_mp_invocation / serious_python_main (dart_bridge >=
1.5.0). On a match, the process runs as a headless interpreter and
returns its exit code.

Resolution is dynamic with graceful fallthrough, so apps built against
an older dart_bridge launch unchanged. ${CMAKE_DL_LIBS} is added to the
runner link for dlopen/dlsym (a no-op with glibc >= 2.34, where libdl
is merged into libc).

Note: not yet runtime-verified on Linux (the macOS equivalent is
verified end-to-end); needs a probe run on a Linux machine.

* fix(build): run the app module as the real __main__; point multiprocessing at the host binary

Two independent multiprocessing breakages lived in the boot script, and
either alone was fatal even with the runners' child interception in
place:

1. __main__ identity. runpy.run_module(run_name="__main__") executes the
   app module in a scratch namespace that is never installed in
   sys.modules, so pickling any function defined in the app module
   failed IN THE PARENT with "Can't pickle <fn>: it's not found as
   __main__.<name>" — before a child was ever spawned. Replace it with
   _sp_run_module_as_main(): resolve the module spec (with `python -m`
   package semantics — pkg runs pkg.__main__ — and clear ImportErrors
   for loaderless/codeless specs), build a fresh module with
   __spec__/__file__/__cached__/__loader__/__package__ set, install it
   as sys.modules["__main__"], and exec the module code in its dict.
   Spawn children then re-import the app module as __mp_main__ via the
   standard init_main_from_name path — which is why the documented
   `if __name__ == "__main__":` guard around ft.run() is now mandatory
   for multiprocessing users, exactly as in plain CPython.

   The module is also aliased as sys.modules["__mp_main__"] in the
   parent, matching what multiprocessing does in children: objects
   defined in the app module and pickled by a child carry
   __module__ == "__mp_main__", and without the alias the parent fails
   to unpickle them (ModuleNotFoundError: __mp_main__).

2. Re-exec target. sys.executable / sys._base_executable are set to the
   host binary (new {host_executable} placeholder, filled from
   Platform.resolvedExecutable in native_runtime.dart; JSON string
   literals are valid Python string literals). On macOS/Windows CPython
   already computes the host binary itself, but on Linux bare
   Py_Initialize() PATH-guesses an unrelated "python3" (or none) — this
   makes the target the runner binary, whose argv interception services
   the children, on all three desktops deterministically. Set before
   any user import because multiprocessing snapshots sys.executable at
   import time.

   PYTHONINSPECT is also dropped from the inherited environment: it did
   nothing in the embedded parent, but a real interpreter child would
   stay open in interactive mode after its -c command completed.
   (Defense in depth — serious_python >= 4.3.0 stops setting it and
   dart_bridge's serious_python_main unsets it too.)

Verified end-to-end on macOS together with the runner interception; the
rendered boot script is byte-checked to compile after all placeholder
substitutions.

* feat(create): guard ft.run() with __main__ in the app scaffold

Start every new project with the standard entry-point guard:

    if __name__ == "__main__":
        ft.run(main)

With multiprocessing now working in packaged desktop apps, spawn
children re-import the app's main module (as __mp_main__) exactly like
plain CPython — an unguarded ft.run() would start a whole new app
session in every worker process. The guard has always been Python best
practice; the scaffold now models it so multiprocessing users don't
learn it the hard way.

Also type the scaffold's event handler parameter
(e: ft.Event[ft.FloatingActionButton]) to model typed event handlers.

* refactor(build): splice all dynamic boot-script values via jsonEncode

JSON string/array literals are valid Python literals, so jsonEncode gives
correct escaping for free. The previous hand-rolled escaping was uneven:
{outLogFilename} doubled backslashes but not quotes, and {argv} escaped
quotes but not backslashes — a Windows-style path in either would corrupt
the generated Python source. {host_executable} already used jsonEncode;
now all three share the one convention. Empty argv still renders as [""]
(CPython always has a sys.argv[0]).

Verified: boot script rendered through the real Dart substitution with
hostile inputs (backslashes, quotes, non-ASCII) compiles as valid Python
and every value round-trips exactly; full flet build macos + the
multiprocessing probe suite passes unchanged.

* docs: Multiprocessing cookbook recipe

New cookbook page documenting multiprocessing support in Flet apps, now
that packaged desktop apps service the spawn re-exec protocol:

- when to reach for processes vs async/threads, and the platform/version
  support matrix (desktop-only, Flet >= 0.86.0; iOS/Android/browser
  unsupported);
- the rules that are standard Python multiprocessing discipline but
  MANDATORY in packaged apps: the `if __name__ == "__main__":` guard
  (spawn/forkserver children re-import the main module), importable and
  picklable worker functions (top-level, plain data, no controls/page/
  lambdas), and no GUI access from workers;
- how it works in a `flet build` app: the embedded interpreter, the app
  binary recognizing CPython helper command lines and running them as a
  headless interpreter, and the practical consequences — sys.executable
  points at the app binary by design (don't set_executable() over it),
  freeze_support() is unnecessary-but-harmless, worker stdout isn't the
  app console log, and forcing fork on Linux is unsafe under a running
  Flutter engine;
- a runnable ProcessPoolExecutor example (parallel chunk sorting with
  live progress) driven off the UI thread via page.run_thread.

Listed in the cookbook sidebar after Subprocess.

* add changelog entry

* fix(build): open console.log as UTF-8

The boot script opened the console-log tee file without an explicit
encoding, so Windows used the locale codepage (e.g. cp1252) and the
first non-ASCII character an app printed raised UnicodeEncodeError
inside the stdout tee — crashing the printing thread. macOS/Linux never
hit it because their default filesystem encoding is UTF-8 already.

Found while verifying the multiprocessing fix on a Windows VM: the demo
app's status line contains "→" and its print() died mid-benchmark.

errors="backslashreplace" additionally guarantees that even malformed
data (e.g. lone surrogates) degrades to an escaped representation
instead of ever breaking the log.

* fix: hide the console window of the git version-fallback on Windows

flet/version.py resolves the package version at import time; in a
source checkout (empty baked flet_version) it falls back to
`git describe`. On Windows, git.exe is a console-subsystem program, so
spawning it from a windowless GUI process pops a visible conhost window.

In a dev-built app this meant one console flash at app start — and,
with multiprocessing now working, one more flash per spawned
worker, since every spawn child re-imports flet and re-runs the
fallback. Traced on a Windows VM via Win32_ProcessStartTrace: each
worker pid parented a git.exe (+ conhost.exe) at click time.

Pass CREATE_NO_WINDOW on Windows so the fallback stays invisible.
Released packages are unaffected either way (CI bakes flet_version, so
the git path never runs).

* add cookbook recipe and examples

* docs: add detailed usage guides to CLI command docstrings

Enhance docstrings for `debug`, `test`, `create`, `build`, `run`, and `publish` CLI commands with links to detailed usage guides and examples from the Flet documentation.

* docs: update README of build-template with installation, platform support, and usage guidance

* breaking changes in version folders

* update changelog

* fix(build): splice {module_name} into the boot script via jsonEncode too

The jsonEncode refactor's comment promised "every dynamic value is
spliced into the boot script through jsonEncode", but {module_name} was
still inserted raw inside hand-written quotes. No live bug — the module
name must already survive the cookiecutter render and find_spec(), so
hostile values can't reach it — but the exception made the comment
wrong and left a footgun for future entry-point changes (e.g. dotted or
path-like module names).

Encode moduleName like the other values; the Python template now calls
_sp_run_module_as_main({module_name}) without surrounding quotes, since
the JSON string literal brings its own.

Addresses the review note on #6662 (discussion_r3544535152). The
rendered boot script is byte-checked to compile after all substitutions.

* docs: drop leftover debug prints from the persistent-worker example

calc_worker still carried two print() calls from local testing. They
would also be misleading in a packaged app, where worker stdout isn't
connected to the app's console log — which the cookbook page itself
points out.

* docs: fix relative links broken by the breaking-changes version folders

Moving the v0.85/v0.86 breaking-change pages into v0-85-0/ and v0-86-0/
subfolders (876a006) was a pure rename, so every relative link inside
them started resolving one directory too shallow and the site build
failed its broken-links check ("Docusaurus found broken links!"):

- "](.)" pointed at the (non-existent) version-folder index instead of
  the breaking-changes index -> now ../index.md
- ../release-notes.md -> ../../release-notes.md
- ../../{cli,reference,cookbook}/... -> ../../../{cli,reference,cookbook}/...

All nine moved pages updated; every relative .md link target verified to
exist, and a full local `docusaurus build` passes the broken-links check
again.

* Bump python build release and serious_python

Update the pinned python-build release date to 20260708 and raise the build template's `serious_python` dependency to 4.3.0 to keep the Python packaging stack in sync.

---------

Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
* Fix integration test failures caused by render overflow errors

Any FlutterError reported during a frame (typically "RenderFlex
overflowed") fails the on-device testWidgets body, which the exit-code
check surfaces as a teardown error on the last test of the module even
though all host-side assertions and goldens pass.

Test harness:
- FletTestApp now captures Flutter test process output and dumps its
  tail when the process exits non-zero, so CI failures show the reason.
- host_test.dart hooks reportTestException to print the exception and
  stack (flutter test otherwise never prints it) and delays tearDownAll
  so end-of-run reports are not lost when the process exits.
- Screenshot captures are hosted in a hidden-scrollbar scrollable
  column, so content taller than the test window scrolls instead of
  overflowing the page; captures and goldens are unaffected.

Tests and examples:
- test_spinkit: render all spinners inside a scrollable column.
- data_table/sortable_and_selectable example: make the page scrollable;
  the table is 2px taller than the default 800x600 window and drew an
  overflow on the first frame.
- Regenerate stale client/pubspec.lock.

CI: temporarily run only the affected tests; restore the full suite
list before merging.

* Restore full macOS integration test matrix
…ation (#6666)

* Fix boot overlay '!_dirty' assertion in debug mode with zero fade duration

With the default boot_screen.fade_out_duration of 0, _BootOverlay's
AnimatedOpacity completed synchronously when boot status turned done,
firing onEnd (and its setState) in the middle of the overlay's own
rebuild and tripping the framework's assert(!_dirty) in debug mode.
Remove the overlay in a single state update when no fade is configured;
non-zero fade durations still take the animated path.

* Add PR link to changelog entry
Update serious_python dependency to the latest patch version in the Flutter template pubspec.yaml.
…restore flet-charts tests (#6673)

* Raw RGBA Matplotlib frames over DataChannels on local transports

Skip per-frame diffing and PNG encode/decode when the client runs on the
same machine: MatplotlibChart now streams uncompressed RGBA full frames
(opcode 0x04) straight from Agg's buffer, displayed with a single
decodeImageFromPixels + swap + dispose on the Dart side. Remote WebSocket
clients keep the compact PNG full+diff pipeline.

- Add Connection.local_data_transport capability flag, set by socket,
  dart_bridge and Pyodide transports; MatplotlibChart auto-selects the
  frame format from it
- Fix O(n^2) length-prefixed packet reassembly in the Dart socket
  transport (flattened the accumulation buffer on every incoming chunk;
  multi-MB frames arrive in dozens of chunks)
- Single-copy DataChannel frame framing in FletSocketServer
- Default flet build web / flet publish renderer to canvaskit: with
  "auto" Chromium selects dart2wasm/skwasm whose JS <-> Dart typed-data
  boundary costs make byte-streaming Pyodide apps ~6-7x slower per frame;
  also let tool.flet.web.renderer take effect in flet publish (argparse
  default shadowed it)
- three_d example: report raw frames in the stats bar and stop the
  refresh loop when the session is destroyed

three_d figure at 1600x1000 @ DPR 2 (24 MB/frame, local socket):
7.4 fps (PNG diff) -> 18.7 fps (raw), matplotlib's own render is now the
dominant per-frame cost.

* Add 0.86 breaking-change guides for Android extract_packages and x86 removal

- New guide: Android site-packages ship zipped; path-hungry packages
  (matplotlib, scikit-learn) need extract_packages to be shipped
  extracted to disk
- New guide: x86 removed from Android target architectures; --arch x86
  now fails the build upfront
- Register both in the breaking-changes index and sidebar; add the
  missing index link for the app-files-unpacked guide
- Cross-link the guides from the Android publishing docs

* Restore flet-charts integration tests, fix chart example layouts, canvaskit docs

The flet-charts integration tests have been broken since the examples
reorg (055cd09) deleted the importable example modules they used —
they failed at collection, so golden screenshot regressions went
undetected.

Tests:
- Add example_apps.py loader that imports an example app's main.py by
  path, and repoint all 8 test modules at the relocated apps under
  examples/extensions/charts (old example_N names mapped by content:
  e.g. bar_chart/example_1 -> interactive_bar_chart)
- test_three_d: use a full-page screenshot like the sibling tests; the
  page-controls capture path measures intrinsic dimensions, which the
  LayoutBuilder-based matplotlib canvas cannot provide
- Regenerate all macOS goldens, each visually verified; the matplotlib
  tests now exercise the raw RGBA frame pipeline on every run
- Ignore *_actual.png diagnostic screenshots

Examples — the reorg wrapped every chart in a SafeArea without
expand=True, so charts rendered blank (layout exception) or shrunken:
- Add the missing expand chain in bar_chart x2, line_chart x2,
  pie_chart x3, plotly_chart x4, radar_chart, scatter_chart and
  candlestick_chart examples
- three_d: make the stats bar scrollable (was overflowing narrow
  windows), count raw frames in it, and stop the refresh loop when the
  session is destroyed on window close

Docs — record the canvaskit web renderer default: publish guide
(with rationale), --web-renderer CLI help, WebRenderer docstrings.

* Address review comments in 0.86 Android breaking-change guides

- extract_packages entries are import names (top-level directory under
  site-packages), not PyPI distribution names: sklearn, not
  scikit-learn. Say so explicitly and fix the examples.
- Extend the known-packages table with tested entries: cv2
  (opencv-python), astropy, thinc, spacy.
- Fix broken publish/index.md#bundled-python anchors (the section is
  #choosing-a-python-version) in the x86 guide and android.md, and
  inline an ABI x Python version support matrix in the x86 guide.
- Fix typo'd NDK ABI anchor (#86-64 -> #x86-64) in android.md.

* armeabi-v7a is supported by all bundled Python versions

flet-dev/python-build publishes arm64-v8a, x86_64 and armeabi-v7a
distributions for every supported Python version (verified against the
20260708 manifest) — the "Python 3.12 only" restriction and the derived
PEP 738 note in android.md were wrong, and the x86 migration guide
repeated them. State the same thing in both places: all three ABIs,
every bundled Python version; upstream's PEP 738 drop does not apply to
Flet's own CPython builds.
…hannels (#6674)

* New RawImage control: full-bandwidth pixel-frame streaming over DataChannels

Generalize the matplotlib raw-frame pipeline into a core control for
Pillow output, numpy arrays, camera frames and procedural graphics.
Updating Image.src resends the whole blob through the msgpack protocol
and re-decodes it on every change; RawImage streams frames over a
dedicated DataChannel with awaitable render() methods that resolve on
the client's frame-applied ack, so producer loops self-pace to display
speed.

- Same wire format as MatplotlibChartCanvas: 0x04 raw premultiplied
  RGBA8888 on local transports, 0x01 encoded (auto PNG fallback,
  encoded off-loop) on remote web, 0x03 clear, 0xFF ack
- render() accepts PIL images (duck-typed) and __array_interface__
  arrays; Pillow and numpy stay optional via lazy imports
- Premultiplication runs in Pillow's C loops (ImageChops.multiply per
  band) with a getextrema() opaque fast-path; numpy path reuses the
  Agg backend's uint16 math
- Last frame is retained and replayed on widget remount so the image
  survives page rebuilds like Image.src
- Dart side holds at most one live ui.Image (web-safe) and renders
  through Flutter's RawImage for Image-consistent fit/filter_quality/
  scale semantics
- Unit tests for packet layout and premultiply math (PIL vs numpy
  parity), 8 integration tests with goldens incl. a premultiply proof
- Examples: plasma (fps counter, detail slider), paint (supersampled
  anti-aliasing, dirty-flag render loop), mandelbrot (click-to-zoom,
  letterbox-aware tap mapping), game_of_life (pointer cell drawing);
  registered in the gallery catalog
- Docs page with RawImage vs Image guidance

* RawImage: photo viewer example and PNG/JPEG file tests

Cover displaying regular encoded images (not just synthetic frames):

- photo_viewer example: downloads network JPEGs and displays the bytes
  with render_encoded, with prev/next navigation and a byte cache
- Integration tests rendering real PNG (with transparency) and JPEG
  files from the test assets straight from disk, with goldens
- Docs: photo viewer section on the RawImage page

* Fix unresolved DataChannel xref in RawImage docstring

DataChannel has no API entry in the docs data, so the :class: role
can't resolve; use inline code instead.

* Document DataChannel and DataChannelOpenEvent

Add types pages and sidebar entries so :class: cross-references to
flet.DataChannel resolve; restore the role in the RawImage docstring.

* Explain premultiplied alpha in RawImage docstring

* Changelog: RawImage control, raw Matplotlib frames, canvaskit web default

* Fix AttributeError when streaming RawImage frames to a disconnected flet-web client

send_data_channel_frame in the flet-web FastAPI transport dereferenced
the send queue after the WebSocket send loop cleared it on disconnect;
drop the frame instead, mirroring FletSocketServer.

Dropped frames also mean the frame-applied ack never arrives, which
would leave awaited render() loops hanging forever (Session.close does
not cancel user tasks). Add RawImage.ack_timeout (default 10s): a
render call now raises TimeoutError when the ack does not arrive, the
same exception producer loops already catch to exit. The abandoned ack
future is withdrawn so a late ack cannot resolve the wrong frame's
wait. Also make the local-transport probe safe on detached controls.
* Add support for Android TV platform in permission handler

* Update CHANGELOG

* Update CHANGELOG
* Flet 0.86 release announcement blog post

* Link dart-bridge in 0.86 release blog

Update the Flet 0.86 release announcement to link `dart-bridge` directly to its GitHub repository, giving readers immediate access to the project referenced in the architecture change section.

* Update Android packaging documentation for extract_packages feature

* Bump python-build to 20260714 and serious_python to 4.3.2

---------

Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>
* docs(studio): update legal docs for AI agent, billing, newsletter; add what's-new entry

* docs(studio): apply legal-doc review feedback (PR flet-app#40 review)
Comment thread .github/workflows/ci.yml

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
Comment thread .github/workflows/ci.yml
- build_flet_package
steps:
- name: Checkout repository
uses: actions/checkout@v4
Comment thread .github/workflows/ci.yml
uses: actions/checkout@v4

- name: Setup uv
uses: astral-sh/setup-uv@v6
Comment thread .github/workflows/ci.yml
uses: actions/checkout@v4

- name: Setup uv
uses: astral-sh/setup-uv@v6
Comment thread .github/workflows/ci.yml
uv build --package flet-mcp

- name: Upload artifacts
uses: actions/upload-artifact@v4
# -------- Run: Android (inside the emulator) --------
- name: Run flet test (android)
if: matrix.platform == 'android'
uses: reactivecircus/android-emulator-runner@v2
# Upload the device log as an artifact (don't stream it to the console).
- name: Upload android logcat
if: always() && matrix.platform == 'android'
uses: actions/upload-artifact@v4
Comment thread .github/workflows/ci.yml
Comment on lines +808 to +809
- name: Checkout repository
uses: actions/checkout@v4
Comment thread .github/workflows/ci.yml

- name: Create/Update GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
Comment on lines +103 to +107
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 300 files, and this pull request has 511

@FeodorFitsner FeodorFitsner merged commit 5cde560 into main Jul 14, 2026
15 of 110 checks passed
@FeodorFitsner FeodorFitsner deleted the flet-0.86 branch July 14, 2026 22:11
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.

7 participants