a2ui-project / a2ui-project/a2ui

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

オープン
#2,532 コメント 1 件 リアクション 0 件 担当者 1 名 @andrewkolos が担当を希望しています GitHub で見る
status: first-line-handled
主要言語
TypeScript
スター
16.4k
フォーク
1.3k
平均マージ
2日 13時間
マージ済み PR(30日)
134

説明

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

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。