a2ui-project / a2ui-project/a2ui

genui: media plugins in `genui.dart`'s import graph cost 10 pub.dev points and the Windows/Linux tags

Abierto
#2,532 1 comentario 0 reacciones 1 asignado Reclamado por @andrewkolos Ver en GitHub
status: first-line-handled
Lenguaje dominante
TypeScript
Estrellas
16.4k
Forks
1.3k
Merge medio
2 d 13 h
PR fusionados (30 d)
134

Descripción

## What it costs today

`https://pub.dev/api/packages/genui/score`, checked 2026-09-04:

- **130 / 160**, Platform support **10 / 20**
- tags: `platform:android`, `platform:ios`, `platform:macos`, `platform:web` — no `platform:windows`, no `platform:linux`, no `is:wasm-ready`
- 192 likes, 28,870 downloads in 30 days

I ran pana against copies of genui 0.10.2. The unmodified copy reproduces the pub.dev result exactly (130/160, same four tags), so the harness matches what pub.dev runs.

| variant of genui 0.10.2 | platform tags | `is:wasm-ready` | Platform support | total |
|---|---|---|---|---|
| unmodified | android, ios, macos, web | no | 10/20 | 130/160 |
| `video.dart` out of `genui.dart`'s import closure | **+ windows, linux** | no | 10/20 | **130/160** |
| `audio_player.dart` out | android, ios, macos, web | **yes** | 10/20 | **130/160** |
| **both out** | **+ windows, linux** | **yes** | **20/20** | **140/160** |

pana 0.23.19 (the version pub.dev runs); independently reproduced on 0.23.18. Every other report section is byte-identical across all four variants — conventions, docs, analysis 50/50, dependencies 20/40.

Reproduce: copy the package twice, make the edits in one copy, and run `dart pub global run pana --no-warning --json ` on each.

Downstream, `genui_gen` is at 150/160 with the same four tags and no `is:wasm-ready`; pana names the chain through `package:genui/genui.dart`. Measured against a fixed genui resolved by path, its Platform section goes 10/20 → 20/20 with all six tags plus `is:wasm-ready`. (That run's *total* reads 140/160 only because pana flags a path dependency in a publishable package, −20 in Analysis; against a hosted fixed genui that section stays 50/50, so 160/160 — **inferred**, not measured.) Disclosure: `genui_gen` is my package.

## The two chains

They are separate defects with separate costs.

**1. `video_player` costs the Windows and Linux tags.** pana's verbatim explanation, identical for both denials:

> `package:genui/genui.dart` that imports: `package:genui/src/catalog.dart` that imports: `package:genui/src/catalog/basic_catalog.dart` that imports: `package:genui/src/catalog/basic_catalog_widgets/video.dart` that imports: `package:video_player/video_player.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.

**2. `audioplayers` costs `is:wasm-ready`,** via `audioplayers/src/audioplayer.dart` → `path_provider` → `path_provider_platform_interface` → `package:platform` → `dart:io`.

Two things about this that are easy to get backwards, and both are checkable in one command:

- **`audioplayers` is not the platform-tag culprit.** It declares all six platforms and ships `audioplayers_linux` / `audioplayers_windows`; pub.dev tags it `android, ios, windows, linux, macos, web`. `video_player` is the only dependency of genui missing Linux and Windows.
- **The 10 points are not the platform count.** pana's `lib/src/report/multi_platform.dart` scores the Platform section 0 with no tags, **10 if `hasWebPlatform && !isWasmReady`**, 20 otherwise. The number of supported platforms only feeds the description string. `stream_chat_flutter` is 6-of-6 and still scores 10/20 for this reason.

That is why the middle two rows of the table both read 130/160. **Fixing only what pana's message names moves the score by zero.** Both files have to leave `genui.dart`'s closure.

Two mechanical details that shape any fix:

- pana computes platform and wasm tags from the transitive closure of **one** library — `lib/.dart`, when it exists (`pana/lib/src/tag/tagger.dart`). Other public libraries in `lib/` are never inspected. genui already relies on this with `lib/parsing.dart` and `lib/test.dart`. **So nothing has to be deleted and no dependency has to be dropped** — I measured a variant that keeps both widget files *and* all three plugin deps in `pubspec.yaml`, moving only the exports into a second public library, and it scores the same 140/160 with all six tags and `is:wasm-ready`.
- pana follows **imports**, syntactically, with no tree-shaking. `video.dart` is the proof: it is already absent from `lib/src/catalog.dart`'s export list and still costs both tags, purely through `basic_catalog.dart`'s import. So "stop exporting them" alone is a no-op, and a `@Deprecated` forwarder left on `BasicCatalogItems` would re-pull the plugin and undo the fix.

## Not covered by the in-flight work

- **#1877** — its design text assigns the BasicCatalog widgets to `packages/genui`, and its core split already landed in flutter/genui#974 without touching `video_player`.
- **#996** — edits `video.dart` for `Video.posterUrl` but does not touch `packages/genui/pubspec.yaml`. It is open, ~100 files, and touches the same file, so anything here should land after it.
- **#1863** ("ready to use" vs "requires configuration") is the closest fit. `BasicCatalogItems.asNoAssetCatalog()` is already its runtime half; this is the compile-time half of the same line. **I'd rather this be folded into #1863 than tracked separately** if you see it that way too.

## Options

**A. Add a top-level `platforms:` block to `packages/genui/pubspec.yaml`.** Non-breaking, three lines. Restores the Windows and Linux tags, recovers **0 points** — the declaration prunes the platform graph but not the wasm graph. Not measured on genui, but `stream_chat_flutter`, `flutter_quill_extensions` and `cached_video_player_plus` all depend on `video_player` from their primary library and hold six tags this way. Reasonable stopgap for downstream discoverability; not the fix.

**B. Move both widget files behind a second public library (`package:genui/media.dart`), removing the items from `asCatalog()`.** **Measured: 140/160, six tags, `is:wasm-ready`, dependencies unchanged.** Breaking: `BasicCatalogItems.audioPlayer`, `BasicCatalogItems.video` and the top-level `audioPlayer` move; `asCatalog()` returns 16 items rather than 18. That last part is a runtime change, not a compile error — an agent that emits `Video` hits `CatalogItemNotFoundException` and renders a `FallbackWidget`. It also leaves `asCatalog()` advertising `basicCatalogId` while omitting two components of that catalog.

**C. (recommended) Same move, but keep `Video` and `AudioPlayer` in `asCatalog()`** with their schemas and `exampleData`, backed by a **plugin-free default builder** — the "video playback is not supported on this platform" card already in `video.dart`, retargeted to "no media backend registered; import `package:genui/media.dart`". `package:genui/media.dart` exports the plugin-backed implementations, and hosts opt in with `asCatalog().copyWith(newItems: MediaCatalogItems.all)` — `Catalog.copyWith` already merges by `item.name`, so registration overwrites the placeholders. Advertised component set stays 18/18, `core_catalog_validation_test.dart` keeps covering both items, and the degraded state is an explicit card instead of a fallback error. **The score outcome here is inferred, not measured** — it follows from the mechanism (no restricted package remains in `genui.dart`'s closure), but I have only measured the delete-outright and the plain-move shapes. Measuring it is step one of the PR.

**D. Satellite `genui_media` package.** Same measured score as B. Its one distinct benefit is real: native plugin registration follows the resolved pubspec, not the Dart import graph, so only D drops `audioplayers*`, `path_provider`, `video_player*` from every non-media app's build (`dev_tools/composer` registers `audioplayers_linux` today and has no audio UI). I have not measured that binary-size delta. Against it: release coupling on a package that broke API in 0.8, 0.9 and 0.10 — `genui_firebase_ai` is still pinned to `genui: ^0.7.0` from 2026-05-04.

**E. Re-back both widgets on `media_kit`.** Every widget class in both files is private (`_AudioPlayerWidget`, `_VideoPlayerWidget`, `_CenterPlayButton`, `_BottomControlBar`), so this is a **zero-public-API-change** route to the same tags and points, and the only option where Video actually plays on Linux. `media_kit` and `media_kit_video` both carry six tags plus `is:wasm-ready`. Cost: real engineering and libmpv binary weight. Not measured.

**Recommendation: C** if the goal is the score with the smallest behavioural change, **E** if the goal is platform support that is true rather than tagged. A is fine as an interim for the tags as long as it isn't mistaken for the fix. Your call — I have no stake in which.

## Caveats I'd want on the record

- **The Linux tag is not Linux playback.** `video_player` has no Linux implementation, and `video.dart`'s `_isVideoSupported` already returns false there. Under B/C/D the tag becomes honest because the default library contains no media plugin, not because Video starts working.
- **`is:wasm-ready` is a pana rule about the import graph, not a build failure.** `dart:io` resolves under dart2wasm to a throwing shim (`support_conditional_import: false` in the SDK's `libraries.json`), and audioplayers guards its `path_provider` call with `!kIsWeb`, so `flutter build web --wasm` works on a genui app today. The 10 points are still 10 points; I don't want to oversell what they represent.
- **The gain is easy to give back.** It holds only while `lib/genui.dart` exists as the primary library and nothing re-imports a media widget into its closure. I measured the revert: delete `lib/genui.dart` and every `lib/*.dart` becomes a top library and the tags disappear. Worth a pana assertion in CI.
- **Conditional imports can't help.** pana's only declared variables are `dart.library.*`, and Android, iOS, Linux, macOS and Windows all share `Runtime.flutterNative` — there is no variable that separates Windows from Android.

## PR offer

Happy to write it, for whichever option you pick. For C that would be:

1. The pana A/B for this exact shape, reported in the PR description with the command to re-run it.
2. New `lib/media.dart` + `lib/src/media_catalog.dart` exposing `MediaCatalogItems.audioPlayer`, `.video`, `.all`.
3. `basic_catalog.dart`: media items keep name/schema/`exampleData`, default builders become the plugin-free placeholder; plugin imports move out.
4. `lib/src/catalog.dart`: drop the `audio_player.dart` export (it moves to `media.dart`). `format_duration.dart` stays and is shared.
5. `pubspec.yaml` unchanged — `audioplayers`, `video_player`, `video_player_win` all stay, so Windows playback does not regress.
6. `examples/simple_chat` and `dev_tools/composer` migrated to the opt-in import.
7. Tests: `test/catalog/core_widgets/audio_player_test.dart` updated (it is the only test naming either symbol), plus a registration test asserting `asCatalog().copyWith(newItems: MediaCatalogItems.all)` yields the full 18 with the same `catalogId`. Note `video.dart` has no test of its own today; its only coverage is the auto-generated schema check, which option C preserves.
8. A pana step in CI asserting `platform:windows`, `platform:linux`, `is:wasm-ready`.
9. CHANGELOG entry naming both moved symbols.

Per `docs/contributing/triage.md` I'll wait for a priority label or an assignment before opening anything.

## Two unrelated notes, mentioned so they don't get bundled in here

- genui's Dependencies section is 20/40 because "compatible with dependency constraint lower bounds" scores 0/20 — downgrade analysis fails with `UNDEFINED_CLASS 'SchemaRegistry'` at `lib/src/engine/surface_controller.dart:65` and 9 similar. That is a `dart pub upgrade --tighten` away and is worth **twice** what this issue is worth. Happy to file it separately.
- genui 0.10.2's published pubspec still sets `issue_tracker: https://github.com/flutter/genui/issues`, which is disabled, so reporters get bounced before they reach this tracker.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.