Skip to content

refactor!: Remove all deprecated APIs for a deprecation-free 1.0 - #6693

Merged
FeodorFitsner merged 13 commits into
mainfrom
fix/remove-deprecations
Aug 27, 2026
Merged

FeodorFitsner merged 13 commits into
mainfrom
fix/remove-deprecations

Conversation

@ndonkoHenri

@ndonkoHenri ndonkoHenri commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Prepares Flet for a deprecation-free 1.0 by removing every deprecated API from shipping source — both the version-scheduled deprecations (0.88 → 1.0) and the ones that had no scheduled removal. A multi-agent audit across all 24 Python and 19 Dart packages confirms zero active deprecations remain.

Removed

Controls & events

  • DragTargetEvent.x / .y / .offset → use local_position / global_position
  • ConstrainedControl → inherit from LayoutControl
  • ElevatedButton → use Button
  • Video.show_controls (→ controls=None), Video.playlist_add() / playlist_remove() (→ mutate Video.playlist)

Page

  • Page.go() → Page.push_route()
  • Page.url_launcher / browser_context_menu / shared_preferences / clipboard / storage_paths → instantiate the service classes directly (UrlLauncher(), etc.)
  • Page.launch_url() / can_launch_url() / close_in_app_web_view() → UrlLauncher().*

App entry

  • app() / app_async() → run() / run_async()
  • run(target=...) / run_async(target=...) → pass main
  • FletApp.show_app_startup_screen / app_startup_screen_message → boot_screen_options

Colors — 14 non-underscored aliases (BLACK12…WHITE70) → underscored names (Colors.BLACK_12); Colors reverted to a plain Enum

CLI / build

  • --clear-cache flag of flet build / flet debug → flet clean
  • Legacy [tool.flet.app.boot_screen] / [tool.flet.app.startup_screen] config → [tool.flet.boot_screen]

Dart — empty-string ("") widget-state key back-compat → use "default"

Notes

  • DropdownM2 is kept and un-deprecated — its 0.84.0 deprecation in favor of Dropdown is reverted; it remains a supported control.
  • Migrated 9 examples + the auth cookbook doc off the removed page.launch_url() to ft.UrlLauncher().
  • Fixed a pre-existing bug in 4 flet_map examples: SimpleAttribution.on_click=lambda e: <async launch_url> never awaited the coroutine (click did nothing) → replaced with proper async def handlers.

⚠️ Breaking changes

All of the above are breaking. See the ## 1.0.0 section of CHANGELOG.md and the updated 0.85.0 / 0.86.0 migration guides.

Summary by Sourcery

Remove deprecated APIs and compatibility shims across Flet and update user-facing examples and migration documentation for a deprecation-free 1.0 release.

Bug Fixes:

  • Fix asynchronous attribution-link handlers in map examples so URL launches are awaited correctly.
  • Update examples and authentication documentation to use the supported service APIs.

Enhancements:

  • Remove deprecated Python, Dart, control, page, app-entry, color, CLI, and configuration APIs to provide a deprecation-free 1.0 release.
  • Restore DropdownM2 as a supported, non-deprecated control.
  • Add a consolidated Flet 1.0 migration guide and update existing deprecation timelines and references.

Build:

  • Remove the deprecated --clear-cache build and debug option in favor of flet clean.
  • Remove support for legacy boot-screen configuration in favor of the current boot-screen configuration.

Documentation:

  • Document all removed deprecated APIs and their replacements in the Flet 1.0 breaking-changes guide.

…rols APIs

Removes APIs deprecated in 0.85.0 as part of the pre-v1 deprecation cleanup:

- DragTargetEvent.x/.y/.offset -> use local_position / global_position
- Video.show_controls -> set controls=None to hide controls
- Video.playlist_add()/playlist_remove() -> mutate Video.playlist directly

Also simplifies the flet_video Dart control (the show_controls gate is now
dead code) and updates the root + flet-video changelogs and the 0.85.0
migration guides.
… flag

Continues the pre-1.0 deprecation cleanup:

- Remove FletApp.show_app_startup_screen and FletApp.app_startup_screen_message
  (deprecated in 0.86.0) -> use boot_screen_options
- Remove the --clear-cache flag of flet build / flet debug (deprecated in
  0.86.0) -> use the flet clean command; the flag's build/flutter deletion
  behavior is removed with it
- Finalize the cleanup changelog version placeholder from 'Unreleased' to
  1.0.0 (root + flet-video changelogs and the 0.85.0/0.86.0 migration-guide
  timelines)
Continues the pre-1.0 deprecation cleanup:

- Remove Page.go() (deprecated in 0.80.0) -> use Page.push_route()
- Remove the Page.url_launcher, Page.browser_context_menu,
  Page.shared_preferences, Page.clipboard, and Page.storage_paths service
  accessors (deprecated in 0.80.0) -> instantiate the service classes
  directly: UrlLauncher(), BrowserContextMenu(), SharedPreferences(),
  Clipboard(), StoragePaths()
- Migrate two examples off the removed accessors
… Colors aliases

Completes the scheduled (1.0) deprecation removals:

- Remove the ConstrainedControl base class (deprecated 0.80.0) -> inherit
  from LayoutControl
- Remove ElevatedButton (deprecated 0.80.0) -> use Button
- Remove DropdownM2 and the dropdownm2 module, incl. its Dart widget and
  control registration (deprecated 0.84.0) -> use Dropdown
- Remove the 14 deprecated non-underscored Colors aliases (BLACK12..WHITE70)
  -> use the underscored names; revert Colors to a plain Enum
- Clean up flet package exports, delete the DropdownM2 docs page and sidebar
  entry, and migrate one example type annotation to ft.Button
…ee 1.0

Sweeps the deprecations that had no scheduled removal version:

- Remove app() and app_async() (deprecated 0.80.0) -> use run()/run_async()
- Remove the target= alias parameter of run()/run_async() -> pass main
- Remove Page.launch_url()/can_launch_url()/close_in_app_web_view()
  (deprecated 0.90.0) -> use UrlLauncher()
- Remove the legacy [tool.flet.app.boot_screen]/[tool.flet.app.startup_screen]
  build-config fallback -> use [tool.flet.boot_screen]
- Remove the Dart empty-string widget-state key back-compat mapping -> 'default'
- Migrate 8 examples + the authentication cookbook doc off page.launch_url() to
  ft.UrlLauncher().launch_url() (and fix a stale web_window_name kwarg)
Reverses the DropdownM2 removal from 76e33fa and drops its deprecation:

- Restore the Python control, the Dart widget + its control registration,
  and the docs page
- Strip the @deprecated_class decorator (and its import) so DropdownM2 is no
  longer deprecated
- Re-add the flet package exports (class + module) and the sidebar entry

DropdownM2 remains a supported control; its 0.84.0 deprecation in favor of
Dropdown is reverted.
…waited)

SimpleAttribution.on_click used `lambda e: ft.UrlLauncher().launch_url(...)`,
but launch_url is async — Flet's sync-handler dispatch calls the lambda and
discards the returned coroutine, so it was never awaited and the click did
nothing. Replace with a proper `async def` handler that Flet awaits. This was a
pre-existing bug (the prior `page.launch_url()` form was also async).
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 18, 2026 •

Copy link
Copy Markdown

Deploying flet-website-v2 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6922e92
Status: ✅  Deploy successful!
Preview URL: https://eae7f464.flet-website-v2.pages.dev
Branch Preview URL: https://fix-remove-deprecations.flet-website-v2.pages.dev

View logs

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Point the 1.0.0 CHANGELOG entries (root + flet-video) at the removal PR
#6693 instead of the original deprecation PRs, and add #6693 to the References
of the drag-target/video/clear-cache migration guides.

Copilot AI 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.

Pull request overview

This PR prepares Flet for a deprecation-free 1.0 by removing previously deprecated public APIs across Python, Dart, CLI tooling, docs, and examples, and updating references to their supported replacements.

Changes:

  • Removed deprecated Python APIs (navigation helpers, service accessors, legacy controls, color aliases) and updated exports accordingly.
  • Removed deprecated CLI/build flags and legacy build config fallbacks, aligning tooling with current commands/config keys.
  • Updated docs, changelogs, Dart back-compat behavior, and multiple examples to use the new APIs (e.g., UrlLauncher(), Button, "default" widget-state key).

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
website/docs/updates/breaking-changes/v0-86-0/deprecated-clear-cache-flag.md Updates timeline/references for --clear-cache removal in 1.0.0.
website/docs/updates/breaking-changes/v0-85-0/deprecated-video-apis.md Updates timeline/references for deprecated Video APIs removal in 1.0.0.
website/docs/updates/breaking-changes/v0-85-0/deprecated-drag-target-event-coordinates.md Updates timeline/references for drag event coordinate fields removal in 1.0.0.
website/docs/cookbook/authentication.md Migrates auth flow URL opening to ft.UrlLauncher() APIs.
sdk/python/packages/flet/src/flet/controls/page.py Removes deprecated Page.go() and URL-launch/service accessor helpers.
sdk/python/packages/flet/src/flet/controls/material/elevated_button.py Removes deprecated ElevatedButton wrapper (file deleted).
sdk/python/packages/flet/src/flet/controls/material/dropdownm2.py Undeprecates DropdownM2 by removing its deprecation wrapper.
sdk/python/packages/flet/src/flet/controls/layout_control.py Removes deprecated ConstrainedControl alias in favor of LayoutControl.
sdk/python/packages/flet/src/flet/controls/core/flet_app.py Removes deprecated boot screen convenience properties from FletApp.
sdk/python/packages/flet/src/flet/controls/core/drag_target.py Removes deprecated DragTargetEvent coordinate aliases.
sdk/python/packages/flet/src/flet/controls/colors.py Removes deprecated color aliases and reverts Colors to a plain Enum.
sdk/python/packages/flet/src/flet/app.py Removes deprecated app()/app_async() and target= aliasing in run()/run_async().
sdk/python/packages/flet/src/flet/init.py Stops exporting removed/legacy symbols (e.g., app, ConstrainedControl, ElevatedButton).
sdk/python/packages/flet-video/src/flutter/flet_video/lib/src/video.dart Drops show_controls handling on the Dart side.
sdk/python/packages/flet-video/src/flet_video/video.py Removes deprecated Video.show_controls and playlist helper methods.
sdk/python/packages/flet-video/CHANGELOG.md Documents removed video APIs in the flet-video 1.0.0 changelog.
sdk/python/packages/flet-cli/src/flet_cli/commands/build_base.py Removes deprecated --clear-cache flag and legacy boot/startup screen config fallback.
sdk/python/examples/extensions/map/multi_layers/main.py Fixes attribution click handling by awaiting URL launch via UrlLauncher().
sdk/python/examples/extensions/map/interaction_flags/main.py Fixes attribution click handling by awaiting URL launch via UrlLauncher().
sdk/python/examples/extensions/map/idle_camera/main.py Fixes attribution click handling by awaiting URL launch via UrlLauncher().
sdk/python/examples/extensions/map/camera_controls/main.py Fixes attribution click handling by awaiting URL launch via UrlLauncher().
sdk/python/examples/extensions/charts/line_chart/line_chart_with_custom_axes/main.py Updates handler typing/usages to Button after ElevatedButton removal.
sdk/python/examples/extensions/audio_recorder/audio_recorder/main.py Migrates page.launch_url() usage to UrlLauncher().
sdk/python/examples/controls/material/context_menu/triggers/main.py Migrates page.browser_context_menu usage to BrowserContextMenu().
sdk/python/examples/controls/core/markdown/markdown/main.py Migrates page.launch_url() usage to UrlLauncher().
sdk/python/examples/controls/core/markdown/listviews/main.py Migrates page.launch_url() usage to UrlLauncher().
sdk/python/examples/controls/core/markdown/code_syntax_highlight/main.py Migrates page.launch_url() usage to UrlLauncher().
sdk/python/examples/apps/declarative/trolli/components/dialogs.py Migrates page.shared_preferences accessor usage to SharedPreferences().
packages/flet/lib/src/utils/widget_state.dart Removes Dart-side empty-string widget-state key back-compat mapping.
CHANGELOG.md Adds 1.0.0 breaking changes entry documenting all removals/changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/docs/cookbook/authentication.md
Comment thread website/docs/cookbook/authentication.md
ndonkoHenri and others added 4 commits July 18, 2026 13:56
…_context_menu

The ContextMenu docstring linked `:attr:`flet.Page.browser_context_menu``,
which this PR removes — leaving an unresolved reST xref that failed the docs
build's cross-reference check (`docs/controls/contextmenu`). Point it at the
`BrowserContextMenu` service directly. Verified locally with the full
`yarn build` + `check_docs` sequence — all checks pass.
The removals were documented only in the changelog, so a user upgrading to 1.0
found nothing under Breaking changes and deprecations. The three removals that
do have guides sit under v0.85.0/v0.86.0 as deprecation notices, where someone
jumping from an older release would never look.

Add one aggregate guide listing every removal against its replacement, plus the
1.0.0 sections in the breaking-changes index and the release notes that the
compatibility policy asks for. A guide per removal would be noise: most are
one-line renames, and the deprecation-era guides already cover the three that
need real migration steps.
The changelog said `Page.launch_url()`, `Page.can_launch_url()` and
`Page.close_in_app_web_view()` were deprecated in `0.90.0` — a release that
never existed, since the line went from 0.86 straight to 1.0. The value came
from the `@deprecated` decorator, which carried a forward-looking removal
target in its `version` slot.

They were deprecated in `0.80.0`: the decorator arrived with the new services
in #5846, first released in v0.80.0, alongside the `Page` service accessors
this entry sits next to.
@FeodorFitsner
FeodorFitsner changed the base branch from release/flet-1.0 to main August 27, 2026 19:39
@FeodorFitsner
FeodorFitsner merged commit 3bb36e9 into main Aug 27, 2026
3 of 144 checks passed
@FeodorFitsner
FeodorFitsner deleted the fix/remove-deprecations branch August 27, 2026 19:44
ndonkoHenri added a commit that referenced this pull request Aug 29, 2026
`/blog/flet-1-0` has no post behind it -- the blog declares
`introducing-flet-1-0-alpha` and `flet-1-0-beta`, and the stable announcement
is not written yet. Docusaurus fails the build on broken links, so this took
down the docs workflow and the Cloudflare deploy with it.

Every other entry links to a post that exists, so the line loses just the
announcement and keeps its changelog and breaking-changes links. Put it back
when the post lands.

Arrived in #6693 and is unrelated to the Linux icon work; it rides on this
branch only because that is where it was hit.

Verified with a full `crocodocs:generate && docusaurus build`: green, and no
other reference to that slug anywhere under website/.
ndonkoHenri added a commit that referenced this pull request Aug 29, 2026
`/blog/flet-1-0` has no post behind it -- the blog declares
`introducing-flet-1-0-alpha` and `flet-1-0-beta`, and the stable announcement
is not written yet. Docusaurus fails the build on broken links, so this took
down the docs workflow and the Cloudflare deploy with it.

Every other entry links to a post that exists, so the line loses just the
announcement and keeps its changelog and breaking-changes links. Put it back
when the post lands.

Arrived in #6693 and is unrelated to the Linux icon work; it rides on this
branch only because that is where it was hit.

Verified with a full `crocodocs:generate && docusaurus build`: green, and no
other reference to that slug anywhere under website/.
FeodorFitsner added a commit that referenced this pull request Sep 2, 2026
* fix(flet pack): Windows taskbar identity of packaged apps (#6767)

- Ship rthooks.dat in the flet-cli wheel via package-data; without it
  PyInstaller silently never ran the Flet runtime hook, so the AUMID
  fix from #6403 was dead on arrival in wheel installs.
- Stamp System.AppUserModel ID/RelaunchCommand/DisplayName/Icon window
  properties on the desktop client window (new flet_desktop.win_taskbar,
  pure ctypes): a process-level AppUserModelID only fixes grouping, while
  the taskbar name, icon and pin target resolve through the window
  relaunch properties.
- Hashed-AUMID fallback for exe paths over 128 chars or with spaces.
- Key ~/.flet/client cache dirs by bundled-archive content fingerprint
  (flet pack writes <archive>.sha256 at build time) so pack-patched and
  vanilla clients of the same version stop shadowing each other.
- Document FLET_APP_RELAUNCH_* env vars.

* simplify: always use raw exe path as AppUserModelID

The hashed-AUMID fallback for >128-char / spaced paths guarded against a
failure that was never actually isolated: in the AUMID-only test round,
every unseeded path failed identically (window relaunch props did not
exist yet), so the long path added no signal. With the window properties
stamped, a raw >128-char path with spaces was verified working on
Windows 11 - the window property store accepts it and the Relaunch*
properties drive name/pin resolution. Keep the single, tested code path.

* harden taskbar stamping and client cache per adversarial review

- Match the client window by its FLUTTER_RUNNER_WIN32_WINDOW class instead
  of visibility: hidden-start apps (AppView.FLET_APP_HIDDEN) were never
  stamped because the worker waited for a visible window and gave up after
  30 s. Stamping works on hidden windows, so they now get their identity
  immediately.
- Hold a SYNCHRONIZE process handle while polling: stops when the client
  exits and prevents PID recycling from ever stamping a foreign window;
  the fixed 30 s deadline is gone.
- Fingerprint sidecar now records '<hash> <size>' and the size must match
  the archive, so a stale sidecar next to a replaced archive re-hashes
  instead of silently mapping to the old client's cache.
- Build client archives deterministically (fixed zip/tar/gzip timestamps,
  sorted entries) so identical content keeps the same fingerprint across
  rebuilds; GC superseded fingerprint dirs after 30 days of disuse with a
  rename-before-delete guard; survive concurrent first-run extraction
  races instead of crashing the loser.

* style: alphabetical env var docs, Google-style docstrings, typing

Move the FLET_APP_RELAUNCH_* doc sections to their alphabetical position,
add type hints and Google-style Args/Returns sections to the new
functions, and use single backticks in docstrings.

[skip ci]

* docs: clarify FLET_APP_USER_MODEL_ID / FLET_APP_RELAUNCH_* are Windows desktop only

[skip ci]

* docs: add docstrings to win_taskbar ctypes structures

[skip ci]

* docs(changelog): reference PR #6793 in taskbar identity entries

[skip ci]

* docs(changelog): tighten taskbar identity entries

Drop verification narrative and investigation history; keep symptom,
cause, and behavior.

* address review: stream zip entries, validate fingerprint sidecar

- compress_flet_client_dir() streamed each file through zf.open(zi, 'w')
  instead of reading it whole into memory with writestr(); the switch to
  a custom ZipInfo (for deterministic timestamps) had lost the streaming
  the previous zf.write() call had.
- __get_archive_fingerprint() now requires the sidecar hash to be 64
  lowercase hex digits before trusting it, so a corrupted or hand-edited
  sidecar falls back to hashing instead of producing a cache directory
  name that could contain path separators - and that the fingerprint GC,
  which only recognizes hex suffixes, would never clean up.
- Note ctypes.OleDLL's automatic HRESULT checking in win_taskbar, which
  is why COM failures surface as OSError.

* docs: drop the dangling 1.0.0 announcement link

`/blog/flet-1-0` has no post behind it -- the blog declares
`introducing-flet-1-0-alpha` and `flet-1-0-beta`, and the stable announcement
is not written yet. Docusaurus fails the build on broken links, so this took
down the docs workflow and the Cloudflare deploy with it.

Every other entry links to a post that exists, so the line loses just the
announcement and keeps its changelog and breaking-changes links. Put it back
when the post lands.

Arrived in #6693 and is unrelated to the Linux icon work; it rides on this
branch only because that is where it was hit.

Verified with a full `crocodocs:generate && docusaurus build`: green, and no
other reference to that slug anywhere under website/.

---------

Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
FeodorFitsner added a commit that referenced this pull request Sep 2, 2026
* feat(build): set window icon for Linux bundles

flutter_launcher_icons has no Linux generator, so flet build linux
produced apps with no icon at all (#2269). Stage the resolved icon
(icon_linux > icon > template default) at linux/app_icon.png in the
Flutter project, install it into the bundle as data/app_icon.png, and
point GTK at it on startup so X11/XWayland taskbars and window
switchers show it.

Also set g_set_prgname(APPLICATION_ID) in the runner (upstream parity
with flutter#154522) so the Wayland app_id and X11 WM_CLASS match the
bundle id, preparing desktop-entry integration.

* feat(build): ship freedesktop integration in Linux bundles

Wayland desktops resolve an app's taskbar icon and launcher name only
from an installed .desktop entry matching the app id — window-set icons
are ignored there by design. Generate a ready-to-install tree in the
bundle: share/applications/<bundle_id>.desktop (Name from --product,
Comment from --description, StartupWMClass=bundle id) and the app icon
at share/icons/hicolor/256x256/apps/<bundle_id>.png, so users and
packagers can register the app with a plain copy into ~/.local/share
or /usr/share.

Also fix --description never reaching the generated project: the value
was passed as template key "description" while every template consumes
"project_description", so the pubspec description, web meta
description, PWA manifest, and the new desktop entry Comment= always
rendered empty (#2269).

* fix(build): harden Linux icon staging and description templating

Review fixes for the Linux icon work:

- Escape project_description in every template sink: pubspec.yaml and
  manifest.json render it via tojson, index.html HTML-escapes it, and
  the desktop entry flattens newlines — a description with quotes or
  line breaks no longer produces an unparsable project or an entry
  desktop-file-validate rejects.
- Quote the desktop entry's Exec value so artifact names with spaces
  don't word-split into the wrong program.
- Keep passing the legacy "description" context key for custom build
  templates that declared it in their own cookiecutter.json.
- Degrade gracefully (icon-less bundle) when a custom template ships
  no images/icon.png instead of crashing on the copy.
- Warn at build time when the Linux icon is not a PNG or not 256x256,
  since it lands in the fixed 256x256 hicolor theme directory
  (new stdlib get_png_size IHDR reader).
- Run the icon_linux lookup only for linux targets: on other platforms
  it copied a dead file and its mtime churned the icons HashStamp,
  re-spawning flutter_launcher_icons for nothing.
- Skip the flutter_launcher_icons spawn entirely on linux targets — it
  only regenerates other platforms' icons the bundle never ships.
- Fold the .desktop install into the icon's if(EXISTS) guard (the
  separate guard was dead) so a desktop entry can never install
  without its themed icon.
- Let the runner warn on a missing/undecodable icon instead of
  silently skipping (dropped the g_file_test pre-check).
- Make .desktop Categories configurable via tool.flet.linux.categories
  (default Utility) and document it.

* fix(build): scale the Linux window icon to fit _NET_WM_ICON

gtk_window_set_default_icon_from_file() reported success while GDK
silently discarded the icon: gdk_x11_window_set_icon_list caps the
_NET_WM_ICON property at GDK_SELECTION_MAX_SIZE (256 KiB of CARDINALs)
and, when the first icon does not fit, breaks out of the loop and calls
XDeleteProperty instead. Any icon from 512x512 up was therefore dropped
— including the build template's own 1024x1024 default, so the common
case shipped no window icon at all.

Load the icon into a pixbuf and scale it to 256x256 before setting it,
so the property always fits, and stop dereferencing a GError that a
FALSE return does not guarantee is set.

Also install the themed icon into the icon-theme directory that matches
its real pixel size (falling back to 256x256 for non-square or
non-hicolor sizes) instead of always claiming 256x256, and escape
backslashes and tabs in desktop entry values, which use backslash
escape sequences and must stay single-line.

`flet build linux` runs for build-template changes too, so
sdk/python/templates/build is added to its CI path filter.

* ci: add a Linux icon verification harness

Builds a generated fixture app for linux across four legs — no icon,
256x256, 512x512, and one with quotes/newlines/tabs/backslashes in the
description plus a space in the artifact name — then asserts on the
bundle: the runner compiled, data/app_icon.png matches its source, the
themed icon landed in the right hicolor directory, the desktop entry
has the expected quoted Exec/Icon/StartupWMClass/Categories and no raw
control characters, and the description round-trips through pubspec.yaml,
manifest.json and index.html. A second tier launches the app under Xvfb
and reads back WM_CLASS and _NET_WM_ICON.

Remove .github/workflows/linux-icon-test.yml and .github/ci-tmp/ before
opening the PR.

* ci: suppress the heavy workflows while iterating on the icon harness

Pushing this branch otherwise fires ci.yml, flet-build-test.yml and
flet-test.yml on every iteration (~100 job-minutes), which only
contends with the Linux icon harness for runners. Exclude the branch
from their push triggers; their pull_request triggers are untouched, so
the PR still gets full coverage.

Revert this commit before opening the PR.

* ci: make the icon harness tolerant of desktop-entry warnings

desktop-file-validate prints warnings (deprecated keys, hints) that are
not spec violations; fail the step only when it reports an error, and
print the entry either way. Also drop the -all flag from the xwininfo
window sweep, which only added output for the id grep to wade through.

* ci: define SDK_PYTHON for the icon harness

The Patch versions step was copied from flet-build-test-matrix.yml without
the env var its common.sh helper cd's into, so every leg failed before
building with "SDK_PYTHON: unbound variable".

* ci: upload runnable bundle tarballs from the icon harness

The artifact was inspection-only, so it could not be downloaded and run
on a real desktop -- which is the whole point of checking an icon. Tar
the bundle inside the job (the artifact zip drops the executable bit and
mangles symlinks) and ship it with a RUN_ME.txt covering the X11 xprop
check and the Wayland desktop-entry install.

Package it before the Xvfb step so a runtime problem can never cost the
download, and stop that step from hanging: the app is launched via
setsid and the whole process group is killed, since a Flet app forks a
bundled Python child that otherwise keeps xvfb-run alive forever (it ran
8 minutes against a 90-second worst case). The step is now bounded by
timeout(1) plus timeout-minutes, and is informational -- Tier 1 is the
contract.

* ci: build the icon bundles on ubuntu-22.04

The bundles are downloaded and run on a real desktop, so the runner sets
the glibc floor: built on ubuntu-latest (24.04, glibc 2.39) they fail to
start on Ubuntu 22.04. Building on 22.04 (glibc 2.35) covers 22.04 and
newer.

Also rewrite the Xvfb check, which timed out on every leg. It now runs
against a wall-clock deadline instead of an iteration count, bounds every
individual X call with timeout(1), prefers the icon-bearing window over
GDK's InputOnly group leader, and dumps the window tree and app log on
any failure so a headless miss is diagnosable instead of silent.

* ci: make the fixture generator work on Python 3.10

Ubuntu 22.04 ships Python 3.10, which has no stdlib tomllib, so every leg
died generating its fixture. The round-trip check it guarded is optional
— assert_bundle.py verifies the description end to end anyway — so skip
it when the interpreter is too old.

* fix(build): correct Linux icon theme sizes and desktop entry escaping

Review of the icon work turned up four real defects:

- resolve_icon_theme_size accepted 28, 42, 160 and 384 as hicolor sizes.
  hicolor-icon-theme declares none of them, so an icon of one of those
  sizes was installed into a directory the theme never scans -- Icon=
  resolved to nothing and Wayland kept the generic icon, which is worse
  than the 256x256 fallback those sizes bypassed.
- Exec= interpolated the artifact name into quotes unescaped. A name
  containing a double quote produced G_SHELL_ERROR_BAD_QUOTING and an
  unlaunchable entry; a backslash voided the whole key. Categories= was
  likewise an unescaped join, where an embedded semicolon silently split
  one category in two and a non-list value crashed the render (wiping
  the build directory with an error that never mentioned pyproject.toml).
  Both values are now escaped and validated in Python, where the
  per-key rules can be expressed, and passed to the template ready to
  interpolate. Categories also gains the usual tool.flet fallback.
- The runner warned on every launch of a bundle that legitimately ships
  no icon (plain flutter build, or a custom template), which aborts
  under G_DEBUG=fatal-warnings. A missing file is now g_debug; only an
  icon that fails to decode warns.
- Staged app_icon.png/app_icon.cmake were never removed when the source
  icon went away, so CMake kept installing the stale icon.

Also widen the scale arithmetic to 64-bit (width * 256 overflows gint
for very wide images, at -O3), warn when scaling fails, correct the docs
and CHANGELOG which promised a fixed 256x256 theme path, fix the
documented sed recipe that stripped the Exec quoting it had just added,
and point the categories link at the current registry URL.

* fix(build): keep the desktop entry group header on its own line

Removing the Categories jinja block left {%- endmacro -%} directly above
[Desktop Entry], and its trailing whitespace-strip glued the group header
onto the last comment line. GLib then parsed no group at all, so every
key was ignored and the entry was inert.

The unit test asserted the header as a substring, which stays true when
it is glued, so it caught nothing -- CI did. Parse the rendered entry the
way a desktop environment does instead, and assert the header starts its
own line.

* ci: assert icon presence, read dimensions with an explicit xprop format

The window carries _NET_WM_ICON -- xprop names it with its CARDINAL type
rather than reporting it missing -- but the default formatter renders a
-len truncated property as empty, so the dimension parse produced
nothing and failed a check that had actually succeeded.

Presence is the load-bearing assertion, and it is now what passes or
fails: an oversized icon is deleted outright by GDK, which is exactly
the regression this exists to catch. The size is read separately with an
explicit 32c dformat and only warns when it cannot be rendered.

* ci: build the icon bundles for arm64 as well

A Linux VM on an Apple Silicon host is arm64, where the x64 bundle only
yields "Exec format error", so the bundles were unusable for the very
verification they exist for. Cross the matrix with ubuntu-22.04-arm and
name the tarballs and artifacts per architecture.

Also quote the Exec rewrite in RUN_ME.txt, which had the same defect the
docs did: the sed stripped the quoting the template adds, so a bundle
under a path containing spaces would not launch from the app grid.

* ci: resolve Flutter on arm64 runners for the icon harness

The arm legs all died at Setup Flutter with "Unable to determine Flutter
version for channel: stable version: 3.44.8 architecture: arm64":
Flutter ships no linux-arm64 archive on stable, so the action cannot
resolve one. ci.yml's build_linux job already works around this by
selecting the master channel for arm64, so take the same route and add
its git safe.directory step, which a cloned SDK needs.

* ci: precache Flutter artifacts on arm64 runners

With Flutter resolving on arm64, the build got one step further and then
failed pub resolution: "could not find package sky_engine in the Flutter
SDK". The arm64 SDK arrives as a git clone with no engine artifacts, and
flet build runs `dart run serious_python:main package` before any flutter
command would fetch them. Precache explicitly; the stable x64 download
already ships them, so the step is arm-only.

* docs(linux): explain the app-id fallback for the app name

Verified on an Ubuntu 22.04 arm64 VM: with the desktop entry not yet
installed, the dock tooltip shows the bundle ID rather than the product
name. That is the same root cause as the generic Wayland icon -- the
desktop environment resolves both from the entry -- and it is newly
visible because the runner now sets its program name to the bundle ID,
so the fallback is a reverse-DNS id instead of the binary name.

* ci: remove the Linux icon verification harness

Drops the temporary workflow, its fixture and assertion scripts, and the
push-trigger suppression on ci.yml, flet-build-test.yml and flet-test.yml,
which are now byte-identical to main again.

The one change that stays is flet-build-test.yml's added
sdk/python/templates/build/** path filter: build-template changes never
triggered the Linux build test before, which is how a broken Linux runner
could have merged unnoticed.

Verified before removal on an Ubuntu 22.04 arm64 VM: the taskbar icon
appears under X11/XWayland with no installation, and under native Wayland
once the shipped desktop entry is installed, with the correct app name.

* docs(publish): document the app description build setting

`--description` had no section in the publish docs, unlike every sibling
setting (product, company, copyright, bundle id), so the only way to learn
where it lands was to read the templates. Add one covering its resolution
order and, more usefully, its actual reach: web meta tag and PWA manifest,
and now the Linux desktop entry's Comment. No other platform template
consumes it.

Also give the changelog entry the user-visible symptom rather than the
mechanism -- web descriptions have been silently empty since the option
was introduced.

* feat(build): add --linux-categories and document the setting

The desktop entry's categories were configurable only from pyproject and
documented only in passing. Add the CLI option, matching the
action="extend" nargs="+" idiom every other list option uses, and give the
setting its own section in the publish docs with a resolution order, as
its siblings have.

Drop the `tool.flet.categories` fallback: nothing else reads it, and a
platform-neutral key would be a trap rather than a convenience, since
macOS (public.app-category.*) and Android (game/productivity) use
vocabularies in which a freedesktop value means nothing.

Also link the desktop entry's Comment and Categories keys to the
freedesktop specification, using the /desktop-entry/ path — the
/desktop-entry-spec/ form 301s to plain http.

Merge the two Linux test modules into test_build_linux.py, grouped into
classes by subject (icon staging, theme size, escaping, template
rendering), following test_project_dependencies.py and test_create.py.

* test(build): document the Linux test methods

Each test now states what it asserts, and the classes state their subject,
matching test_create.py. Several tests carried that explanation as a
leading comment instead; those became docstrings, leaving inline comments
only where they explain a specific line.

* docs(linux): move application categories to the Linux page

Categories are the only build setting that applies to exactly one
platform, so they belong with the rest of the Linux packaging docs rather
than in the shared settings list. The section keeps its resolution order
and both examples, and drops the Tabs component, which the Linux page
does not use.

* docs(linux): use Tabs for the categories example

The other platform pages present a setting's CLI and pyproject forms in a
shared-groupId Tabs block, so the Linux page should too — it just had no
setting needing them until now. Adds the two imports the macOS, Android
and iOS pages already carry.

* docs(linux): generate the apt prerequisites from flet-cli

The package list on the Linux page was a hand-maintained copy of
flet_cli.utils.linux_deps, which is also what `flet --version --json`
reports and what CI installs. The two happen to agree today, but nothing
enforced it: adding a package for a new plugin updates CI and silently
leaves the docs wrong.

Generate the block instead, via a crocodocs partial that imports the list
inside the flet-cli environment — the same mechanism
cross-platform-permissions.mdx already uses, so discovery is automatic
from the page's import.

Present it in three tabs: the package list, and the equivalent CLI
command with and without jq, for setup scripts that should never go
stale. The generated list is alphabetical, as the source is, losing the
old loose grouping by purpose; the prose below it still explains what
each group of packages is for.

* fix(build): actually wire up --linux-categories

The option and its resolution never reached the previous commit — only
the docs and tests did — so the published resolution order described a
flag that did not exist, and `tool.flet.categories` was still consulted
despite being documented as removed.

Add the argparse option, resolve it ahead of `[tool.flet.linux].categories`,
and drop the platform-neutral key. Cover the wiring with tests that build
the real parser: asserting on `escape_desktop_categories` alone passes
whether or not the option is registered, which is exactly how this slipped
through.

* refactor(build): pass the app description under one template key

`description` duplicated `project_description` with the same value. It was
only ever reachable by a custom build template that declared the key in its
own cookiecutter.json — the shipped template declares and consumes only
`project_description` — so the second key protected a case that likely does
not exist, at the cost of two names for one value.

`flet create` is unaffected: its `description` key belongs to the separate
app templates, which do consume `cookiecutter.description`.

* refactor(build): name the desktop Exec context key for its platform

`desktop_exec` sat beside `linux_categories` but read as though it applied
to desktop platforms generally, when only the Linux desktop entry consumes
it. Rename it to `linux_desktop_exec`.

Its cookiecutter.json entry also defaulted to `{{ cookiecutter.artifact_name }}`
while being declared above `artifact_name`, so the default resolved to
nothing — invisible while `flet build` always passes an explicit value, but
wrong for anything rendering the template on its own. Declare both Linux
keys after `artifact_name`.

* refactor(build): name the desktop entry escapers for their platform

Rename escape_desktop_exec and escape_desktop_categories to
escape_linux_desktop_exec / escape_linux_desktop_categories, matching the
context keys they feed. Desktop entries are a Linux concern, and the bare
names read as though they applied to every desktop platform.

Also repairs the previous commit, which renamed the context key in the
template but left the test's render context on the old name, so the
desktop entry tests failed there.

* fix(build): keep the template pubspec parseable while unrendered

Escaping the description with `| tojson` left the value unquoted, and an
unquoted `{{` opens a YAML flow mapping — so the template pubspec.yaml
stopped parsing as YAML. `.github/scripts/patch_pubspec_version.py` loads
that file before cookiecutter ever runs, so the build_templates CI job
failed, and on a release tag the build template zip would not have been
produced at all. Verified by running the CI script: exit 1 on this branch,
exit 0 on main.

Quoting cannot be fixed in the template alone, because that script rewrites
the file with yaml.dump and normalizes quoting, so the escaping has to
match what survives the round-trip. Escape the value in Python for a
single-quoted scalar and interpolate it into the quotes the template
provides.

Also:
- resolve the Linux categories only for linux targets, so a malformed
  `[tool.flet.linux] categories` cannot fail an Android or web build;
- flatten control characters in both desktop-entry escapers, since a
  newline in Exec or Categories leaves a line with no `=` and the desktop
  environment discards the whole entry;
- render templates in tests with StrictUndefined, so a renamed context key
  raises instead of quietly rendering empty — the failure mode that let a
  broken commit pass.

Adds contract tests for both classes of bug that have now shipped: every
`cookiecutter.<key>` a template reads must be declared in cookiecutter.json
(cookiecutter drops the rest), and the pubspec must parse unrendered and
render to valid YAML for quotes, backslashes and newlines. Both were
confirmed to fail when their bug is reintroduced.

* docs(linux): add a Distributing section

`flet build linux` produces a bundle directory, which is not something an end
user can download and run — the page stopped one step short of a shippable
artifact. Add AppImage, .deb and .rpm recipes that build on the desktop entry
and hicolor icon the bundle now ships, so packaging is mostly relocation.

Each recipe keeps the bundle intact and rewrites Exec= to the install
location, since the executable resolves its libraries and Python runtime from
its own path.

Also records why fastforge (formerly flutter_distributor) is not the
recommended route: it rebuilds the app with `flutter build linux`, which
produces a bundle with no Python payload because that is staged by a separate
step under environment `flet build` sets, and it replaces the bundle's desktop
entry with one lacking StartupWMClass.

NOT YET VERIFIED on a Linux machine.

* docs(linux): fix admonition titles and explain the FUSE requirement

The new admonitions used `:::warning Title`, which Docusaurus only renders as
a titled admonition in its bracketed form — the rest of the docs use
`:::warning[Title]` 103 times against 5 spaced.

The FUSE aside also assumed the reader knew what FUSE 2 is and whether they
have it. Link the AppImage troubleshooting page, give the one-line check, and
note that Ubuntu 22.04+ dropped libfuse2 by default — which is exactly where
this bites — plus both ways out and the fact that it applies to the AppImage
you ship, not just to appimagetool.

* docs: repair admonitions that render without their title

Docusaurus only renders a custom admonition title in the bracketed form,
`:::note[Title]`; the spaced form drops the title into the body. Two pages
used the spaced form.

Also drops the aside explaining why fastforge is not the recommended
packaging route — the Distributing section stands on the recipes it gives
without arguing against a third-party tool by name.

* test(build): move the template contract tests to their own file

These assert invariants of the cookiecutter template itself — that every
context key a template reads is declared, and that pubspec.yaml parses
unrendered and renders to valid YAML — none of which is Linux-specific.
They only lived in the Linux file because that is where the bugs that
motivated them were found.

Each file keeps its own copy of the small render helper rather than
introducing a conftest, matching how the other suites here are written.

* docs(build): restore comments dropped from template_data

The three comments explaining why the description is carried under two keys
were lost while the docs work lived on a second branch. Nothing about the
behaviour changed; only the explanation of it went missing.

* test(build): keep per-class detail in the class docstrings

The Linux module docstring described what each group of tests covers, which
belongs on the classes themselves — a reader looking at one class should not
have to scroll to the top of the file for it. The module docstring now says
only what is true of the whole file.

Restores the same explanation to TestBuildTemplateContract, which lost it
earlier and had it nowhere.

* docs(linux): stop asserting which distributions ship FUSE 2

The FUSE note claimed Ubuntu 22.04 and later do not install libfuse2 by
default. An Ubuntu 22.04 arm64 machine reports it present, so the claim is
at best not universally true — it can arrive as another package's
dependency. The check was already the actionable part; keep it and drop the
distribution-specific assertion.

* docs(linux): show how to fetch and chmod appimagetool

The AppImage recipe said to get the tool "and make it executable" without
showing either step, and then hardcoded x86_64 in the invocation — which is
wrong on the arm64 machines this section is most likely to be tried on.

Give the download and chmod commands, derive the architecture from
`uname -m`, and use it consistently in all three places the tool is invoked.
Both release URLs verified to resolve.

* docs(linux): say where appimagetool has to live

The download step never said which directory to run it from, and the recipe
then invoked the tool by a bare relative path — so the two only worked
together if the reader happened to build the AppDir from the same directory
they downloaded into. Note that the tool can live anywhere and take its
location as a variable in the recipe.

* docs(linux): annotate the AppImage recipe

Follows the GitHub Actions section's annotation style so each step explains
itself, rather than leaving a reader to infer why the AppDir needs four
entries at its root or why Exec= is rewritten to a bare name.

Annotations force the line continuations out — a trailing `# (n)!` and a
trailing `\` cannot coexist — which is a good trade: a continuation silently
breaks when a paste inserts blank lines, and that is how the recipe failed
the first time it was run for real.

Also presents it as a script to save and run rather than a block to paste,
adds `set -euo pipefail` so a failed copy stops there instead of surfacing
as a confusing appimagetool error, and guards the bundle prerequisite.

* docs(linux): make the AppImage annotations say what they mean

Several were too compressed to act on: the bundle variable never said it
takes a path, the executable one never said it takes a bare filename rather
than a path, and "bundle", "AppDir" and "FHS" were used as though the reader
already knew them.

Each now says what kind of value it wants, where to find it, and what the
term means on first use.

* docs(linux): make each AppImage annotation stand on its own

Annotations are read one at a time, so "that directory" and "that icon's
own path" pointed at nothing for anyone opening those two on their own.
Name what they refer to instead.

Also carries the working tree's emphasis on save/edit/run in the intro.

* docs(linux): name the three variables the reader has to edit

"Edit the three variables at the top" left the reader to work out which of
the six they were. Name them inline, matching the three annotations marked
"Edit this".

* docs(linux): distinguish main from additional categories

The example passed two main categories, which is legal but means the entry
"may appear more than once in the menu" per the spec — appimagetool hints
about it, so the docs were teaching the thing a linter warns against. Use a
main category paired with an additional one instead, and explain the split.

Also drops the claim that desktop environments ignore unregistered values:
the spec does not say that. What is true is that no menu rule matches them,
so they have no effect on placement.

* docs(linux): annotate the deb and rpm recipes

Both were bare scripts: correct, but silent about every decision that
matters. Neither said why the bundle goes to /opt in one piece, why /usr/bin
gets a symlink rather than a wrapper (a wrapper resolves to /usr/bin, and the
app locates its Python runtime from the running executable's path), or why
rpmbuild's post-processing has to be switched off — left on, it byte-compiles
the bundled site-packages with the system Python.

Gives them the same treatment as the AppImage recipe: annotations on every
meaningful line, no line continuations, variables to edit called out by name,
and a verification step for the .deb. The rpm tab also gains the rpmbuild
invocation and tree setup, which it previously assumed.

* docs(linux): take the bundle path as a variable in the deb recipe

The AppImage recipe took BUNDLE as a variable while this one hardcoded
build/linux, so it could not package a bundle that lives anywhere else — a
downloaded artifact, or a build kept outside the project.

* docs(linux): renumber two annotation lists

Both render correctly today only because markdown renumbers ordered lists
for you. In the source the AppImage list skipped 4 and used 17 twice, so
from the fourth item on every number named a different line than the one it
annotated -- a trap for whoever edits it next.

* docs(linux): make the rpm /usr/bin symlink relative

rpm warns on any absolute symlink target, and rpmlint's default
UseRelativeSymlinks flags it as symlink-should-be-relative. From
/usr/bin, ../../opt/<name>/<bin> resolves to the same path, so the
installed package is unchanged.

Also document the stale build directory that wedges a retried rpmbuild
with a misleading "Bad file descriptor" errno.

* docs(linux): explain why deb and rpm symlinks differ

The two recipes point /usr/bin at /opt in opposite ways on purpose:
Debian Policy 10.5 asks for absolute links across top-level directories,
while rpmlint's default UseRelativeSymlinks flags exactly that. Note it
so the difference does not read as an oversight.

* docs(linux): say what an AppImage does not register

An AppImage installs no desktop entry anywhere the shell scans, so the app
grid has no entry for it and its icon has no name to show. Verified on a
22.04 VM: the same image showed the app id until an entry for that app id
existed, then the name.

Worth stating because the section otherwise reads as though the format were
interchangeable with the other two.

* docs(linux): let the AppImage recipe fetch its own appimagetool

The page said the tool "can live anywhere" and that the recipe "takes its
location as a variable", but APPIMAGETOOL was not among the three variables
the reader is told to edit -- it was hardcoded to $PWD. Anyone who saved the
script in their project and left the download in ~/Downloads, which the prose
explicitly allowed, got a failure. The script is also meant to be re-run on
every rebuild, and that only worked from the one directory holding the tool.

Fetching it from inside the script removes the path rather than documenting
it. Guarded on the file already being there, so a rebuild costs nothing and a
copy placed by hand -- from a mirror, or on a machine with no network -- is
used untouched.

Pinned to 1.9.1 rather than the rolling `continuous` tag, now that a build
script re-runs the download: a rebuild should not be able to pick up a
different tool without the reader choosing it. appimagetool does publish
versioned releases, and 1.9.1 ships the same asset names, so this is a
drop-in.

Verified both pinned URLs resolve, the guard's three states, and that the
extractor still pulls the recipe cleanly.

* docs(webview): add JavaScriptMode type page to fix unresolved xref

JavaScriptMode is exported from flet_webview but had no docs page, so the
:attr:`flet_webview.JavaScriptMode.UNRESTRICTED` reference in
WebView.set_javascript_mode rendered as raw reST text.

* docs(linux): stop the appimagetool guard destroying a hand-placed copy

An audit of the previous commit found the guard I had just written does the
opposite of what I documented it doing, in exactly the two cases the
annotation named.

`[ ! -x "$APPIMAGETOOL" ]` tests the execute bit, not existence, and every
ordinary way of getting the file -- wget, curl, a browser, an extracted
archive -- leaves it mode 644. So the guard fired on precisely the
hand-placed copies it promised to leave alone. And `wget -O` truncates its
destination before it connects, which I proved rather than assumed: a
37-byte file against an unreachable host came back 0 bytes. A reader on a
restricted network with their only copy next to the script lost it, and every
re-run failed identically with nothing to fall back on.

Now tests `-e`, downloads via `.part` and moves into place only on success,
and chmods outside the block so a hand-placed copy gets the bit it needs --
the instruction to set it by hand having been removed along with the manual
download. Exercised all four states against real wget: a mode-644 copy now
survives online and offline and ends up executable, and a failed download
leaves nothing that could pass for a working tool.

Also narrows the offline claim. appimagetool downloads the AppImage runtime
it embeds, so having the tool on disk is not enough to build without a
network; annotation 22 now says so and points at --runtime-file.

* docs: drop the dangling 1.0.0 announcement link

`/blog/flet-1-0` has no post behind it -- the blog declares
`introducing-flet-1-0-alpha` and `flet-1-0-beta`, and the stable announcement
is not written yet. Docusaurus fails the build on broken links, so this took
down the docs workflow and the Cloudflare deploy with it.

Every other entry links to a post that exists, so the line loses just the
announcement and keeps its changelog and breaking-changes links. Put it back
when the post lands.

Arrived in #6693 and is unrelated to the Linux icon work; it rides on this
branch only because that is where it was hit.

Verified with a full `crocodocs:generate && docusaurus build`: green, and no
other reference to that slug anywhere under website/.

* fix(`flet build`): double a literal percent in the desktop entry's Exec

`%` introduces a field code, so an artifact name containing one -- `Save 50%
Now` -- reached the desktop as `Save 50Now`, losing the percent and the
character after it, and the launcher then pointed at a path that does not
exist.

Found while reviewing the `flet pack` sibling, whose escaper was mirrored
from this one and inherited the same gap. Covered by two cases in the
existing parametrised test so the two implementations cannot drift apart
again.

* fix(`flet build`): stop None reaching the generated project files

Two defects from Copilot's review of #6799, both the same shape: a value
resolved to None and was then passed into the cookiecutter context, where it
overrode the default cookiecutter.json declares and rendered as the literal
string "None".

`project_description` had no final fallback, so a project with no description
in --description, project.description or tool.poetry.description produced
`content="None"` in web/index.html, `"description": "None"` in
web/manifest.json, and the same in the README. pubspec.yaml escaped it to ""
and was unaffected, which is why this went unnoticed.

`linux_categories` was None for every non-Linux target. Each target renders
the whole template, so a Windows or macOS build still wrote the desktop entry
-- with `Categories=None` instead of the declared `Utility;`. It now falls
back through the same escaper, and a test pins that fallback to the value in
cookiecutter.json so the two cannot drift.

Also drops the `raise` after `self.cleanup(...)` in the categories handler:
cleanup ends in sys.exit(), so it was unreachable.

The third point in that review -- a literal `%` in Exec= -- was already fixed
in the preceding commit, found while reviewing the `flet pack` sibling.

---------

Co-authored-by: Feodor Fitsner <feodor@appveyor.com>
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.

3 participants