chore(ui)!: migrate to material_ui and cupertino_ui - #151
Conversation
Adopting a new Flutter stable and raising the published minimum are two different jobs that share the phrase "bump Flutter", and the second one is due on a fixed policy (minimum supported = latest stable - 1) rather than when something breaks. The skill separates them and records what this repo's CI actually checks, since most of the traps come from gaps between the jobs: - A floor raise moves two knobs, not three: `melos.yaml` (source of truth, propagated by `melos bs`) and `legacy_version_analyze.yml`. There is no `.fvmrc` here, so nothing pins a developer's toolchain to the floor. - Alchemist keys on `GITHUB_ACTIONS`, not the more common `CI`, so the obvious local invocation runs the untracked platform goldens and fails every golden test for reasons unrelated to the toolchain. - `package_analysis` analyzes `stream_core/lib` only, with `--fatal-warnings`. The beta canary and the N-1 job therefore say nothing about the Flutter packages, and nothing at all about the new infos that `--fatal-infos` turns into failures. - `all_lint_rules.yaml` is an explicit list, so a new SDK's rules never self-activate, and a removed rule surfaces as `undefined_lint` in that file rather than in `analysis_options.yaml`. - Raising the Dart constraint raises each package's language version, which wakes lints whose fix was not previously expressible. `dart fix` applies them mechanically and rewrites or deletes doc comments while doing so, so the diff needs reading in both directions. - Nothing in CI builds for Android or iOS, so platform build floors are invisible here and must not be reported as verified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flutter 3.47.0 went stable on 2026-08-12, so the "minimum supported = latest stable - 1" policy makes 3.44.0 (Dart 3.12.0) the new floor, up from 3.38.1 / 3.10.0. This is Track B only: CI already runs the new stable and is green on it, so no compat work was needed first. A floor raise is not breaking — existing code keeps compiling, older SDKs simply stop resolving the new version — so there is no `!` and the bullets go under a `🔄 Changed` heading. That heading had no entry in STYLE_GUIDE's list, which covered only features, fixes and removals; it is added there with the rule that a floor raise belongs under it rather than under `🛑 Breaking / Removals`. Two knobs move in lockstep: `melos.yaml`, which is the source of truth and propagates to all five package pubspecs on bootstrap, and `legacy_version_analyze.yml`, whose `flutter_version` is the floor's own regression test. That job was pinned to `3.38.10`, a patch of the old floor rather than the floor itself; it now names the floor exactly, so what CI proves is what the pubspecs claim. Raising the Dart constraint raises each package's language version, which activated `prefer_initializing_formals` on 24 previously-silent sites: Dart 3.12 legalised `this._privateField` as a named parameter, so the lint's suggested fix only became expressible now. Zero issues before the constraint moved, 24 after — caused by the floor, not by the new stable. Applied with `dart fix --apply --code=prefer_initializing_formals` and then audited by hand, since the fix is mechanical: - It rewrote five doc-comment references to the private field name (`[cdn]` -> `[_cdn]`), which would have leaked private names into public API docs while callers still pass the public name. Reverted; the diff now touches no comment line in either direction. - Every introduced `this._foo` replaced a parameter named exactly `foo`, so no public parameter was renamed. `melos run generate:all` produces no diff, confirming the generators still see the same names. Verified at both ends: `dart analyze --fatal-infos` is clean across all four packages on 3.47.0 (what CI resolves) and reports only one pre-existing info on 3.44.0 (the floor). `stream_core` 317 tests and `stream_thumbnail` 10 tests pass; `stream_core_flutter` fails the same 42 macOS-vs-Linux goldens as before the change, an identical set, so no golden was regenerated. Barrels and formatting pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flutter split Material and Cupertino out of the framework into the flutter.dev-published `material_ui` / `cupertino_ui` packages, both v1.0.0 and published on 2026-08-12, the same day as Flutter 3.47 stable. Both require `flutter: >=3.44.0`, which is exactly the floor raised in the parent commit — that is what unblocks this, and why it is stacked. Rewrites all 195 import directives across 194 tracked files: `package:flutter/material.dart` -> `package:material_ui/material_ui.dart`. Every occurrence was verified to be a plain import first — no export directives, no `show`/`hide` clauses, and no `package:flutter/cupertino.dart` import anywhere in the repo. Reordering moved the directive relative to its neighbours, so `directives_ordering` was reapplied via `dart fix`. Nothing imports Cupertino directly: the only `Cupertino` references are a doc-comment link and a private class name in `StreamSwitch`. So `cupertino_ui` arrives transitively through `material_ui` and gets no direct pubspec entry, which would otherwise be an unused dependency. This is breaking for consumers. `material_ui` ships its own implementations and Flutter 3.47's `material.dart` does not re-export them, so the two `ThemeData`/`ThemeExtension`/`ColorScheme` are unrelated types. An app that puts `StreamTheme` into Flutter's `ThemeData.extensions` no longer compiles; it must import `material_ui` and use its `MaterialApp`, which is also what supplies the `MaterialLocalizations` the components read. Two prefixed bridges remain, each for a dependency that has not migrated and each carrying an `// ignore: migrate_design_widgets`: - `StreamMessageText` restates the theme for `MarkdownStyleSheet.fromTheme`, mapping exactly the fields that factory reads so the rendering is unchanged. - The gallery builds a separate Flutter theme for Widgetbook's own chrome, over the same colors as before. `migrate_design_widgets` is enabled to hold the line — without it a new file can silently reintroduce the split. It is only recognised by the 3.47 analyzer, but that is safe at the 3.44 floor: `undefined_lint` is reported against the options file itself, and nothing in CI analyses the repo root, where `all_lint_rules.yaml` lives. Enabled, it reports exactly the two bridges above and nothing else, which is the proof the migration is complete. Two test-only adjustments, both consequences of the split rather than of anything this repo does differently: - `find.byTooltip` is bound to Flutter's `Tooltip`, so the bottom-nav tooltip test now matches `material_ui`'s by predicate. - Alchemist scaffolds golden tests with Flutter's `MaterialApp`, whose `MaterialLocalizations` the migrated `StreamBottomNavBar` cannot read. Its golden wrappers now provide `material_ui`'s delegates. Verified: `dart analyze --fatal-infos` clean across all four packages, barrels and formatting pass, `melos run generate:all` produces no diff, and `stream_core_flutter` fails the same 42 macOS-vs-Linux goldens as before the migration — an identical set, 362 passed either way. No golden was regenerated and no `.png` is touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
CI caught two golden tests the local run could not: the composer's reply and link-preview matrices throw `No MaterialLocalizations found` for the same reason `StreamBottomNavBar` did — alchemist scaffolds golden tests with Flutter's own `MaterialApp`, whose localizations are an unrelated type to `material_ui`'s. The local check missed them because both files were already in the 42-golden macOS-vs-Linux baseline, so comparing failure *sets* showed no change while the underlying failure had switched from a pixel mismatch to a thrown exception rendering an error box. Comparing exception text rather than failure sets finds them: the run now reports zero `No MaterialLocalizations found`, and all 42 remaining failures are plain `Pixel test failed` with no widget-library exception among them. The wrapper introduced for the nav bar is extracted to `test/utils/material_localizations.dart` and shared by all three files, since any component reading localizations will need it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## chore/flutter-3.44.0 #151 +/- ##
========================================================
+ Coverage 59.47% 60.94% +1.47%
========================================================
Files 192 192
Lines 7745 7755 +10
========================================================
+ Hits 4606 4726 +120
+ Misses 3139 3029 -110 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
`StreamMessageText` had no tests, so the hand-written bridge that adapts `material_ui`'s `ThemeData` for `MarkdownStyleSheet.fromTheme` was the one piece of new logic in the migration with no coverage at all — codecov reported 0% of the diff hit. Pins the fields the bridge has to carry: the heading and body text styles, inline code's 85%-of-body sizing, `cardColor`, `dividerColor`, and that an explicitly supplied style sheet still wins. Dropping any of them from the bridge silently falls back to a Material default rather than failing, which is exactly the kind of regression a golden diff on one component would not localise. Not asserted: `blockquoteDecoration`, which the pinned flutter_markdown_plus 1.0.7 hardcodes to `Colors.blue.shade100` and only derives from `colorScheme.surfaceContainerHighest` in 1.0.12. The bridge maps the colour scheme anyway, so a dependency bump stays correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each place the migration left something behind now names the upstream package it is waiting on, so none of them survives on inertia after the dependency catches up. Two are bridges with a clear deletion trigger — the markdown theme adapter (FLU-701, flutter_markdown_plus) and the gallery's Widgetbook theme (FLU-703). One is a test helper every future component reading localizations will need until alchemist migrates (FLU-702). The other two are not workarounds but silent degradation, which is why they are worth marking in the source rather than only in a ticket: `DeviceFrame` and the colour picker call Flutter's `Theme.of`, and Flutter's `Theme.of` falls back to a default rather than throwing when the ancestor is `material_ui`'s `MaterialApp`. They compile and render — just with stock Material colours (FLU-704). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…encies `material_ui` ships a supported migration utility for exactly the case this PR was hand-rolling: `MaterialUiCompatibilityBridge` reads the modern theme from context and installs a legacy `Theme` plus legacy `Localizations` below it, so packages still built on `package:flutter/material.dart` resolve correctly inside a modern tree. Step 3 of the package's own migration guide. It replaces both bridges written here, and reaches cases neither of them covered. `StreamMessageText` now wraps `MarkdownBody` rather than mapping a theme by hand. Two gains beyond deleting ~30 lines: the mapping is upstream's and covers the whole `ColorScheme` and `TextTheme` instead of the five slots `fromTheme` happens to read today, and `MarkdownBody` itself — a legacy Material widget — finally has a legacy theme and localizations to resolve against, which it never did after the migration. A selectable markdown body needs those for its context menu. The bridge maps `platform`, `visualDensity`, `colorScheme` and `textTheme` and nothing else, so the legacy-only scalars `fromTheme` also reads — `cardColor`, `dividerColor`, `primaryColor`, `cardTheme.color` — are copied on top. Without that, an app setting `cardColor` explicitly would lose its code-block background. Pinned by the existing tests, which caught it. The gallery bridges at the app root via `MaterialApp.builder`, the pattern the guide recommends, which covers every legacy dependency below it at once: the colour-picker dialog that was silently rendering with stock Material colours, and DeviceFrame. `buildWidgetbookTheme` is gone — Widgetbook installs its own `MaterialApp.router` below the bridge, so its chrome takes the bridged theme by value instead, which is a fuller mapping than the 13 colours that method restated by hand. The prefixed imports are `as legacy`, matching the modern/legacy naming `material_ui` uses in `migration_utility.dart` itself. Alchemist is unaffected and keeps its own wrapper: that failure runs the other way — a modern widget needing modern localizations inside a legacy test harness — which is not what the bridge does. Locally: analyze, barrels and formatting clean; the golden failure set is the same 42 macOS-vs-Linux baseline with zero widget-library exceptions and zero `No MaterialLocalizations found`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Submit a pull request
Linear: FLU-700
CLA
Description of the pull request
Flutter split Material and Cupertino out of the framework into the flutter.dev-published
material_ui/cupertino_uipackages — both v1.0.0, published 2026-08-12, the same day as Flutter 3.47 stable. Both requireflutter: >=3.44.0.Stacked on #150, which raises the floor to exactly that version — that is what unblocks this, so review #150 first and merge it first.
What changed
package:flutter/material.darttopackage:material_ui/material_ui.dart. Every occurrence was verified to be a plain import directive first — no export directives, noshow/hideclauses. Reordering moved the directive relative to its neighbours, sodirectives_orderingwas reapplied viadart fix.material_uiregistered inmelos.yaml(the workspace's single version registry) and added to the three pubspecs that import it:stream_core_flutter, the gallery, and thestream_thumbnailexample.migrate_design_widgetsenabled inall_lint_rules.yamlto hold the line.STYLE_GUIDE.mdand the gallery'sAGENTS.md— the import-convention code samples showed the old URI, which is what the next contributor would copy.cupertino_uigets no direct dependency — deliberatelyNothing in this repo imports
package:flutter/cupertino.dart; the onlyCupertinohits are a doc-comment link and a private_CupertinoSwitchDefaultsclass name inStreamSwitch. Socupertino_uiarrives transitively throughmaterial_ui, and adding it directly would be an unused dependency. If a widget starts importing it, add it then.Why this is breaking
material_uiships its own implementations and Flutter 3.47'smaterial.dartdoes not re-export them, soflutter/material'sThemeData/ThemeExtension/ColorSchemeare unrelated types tomaterial_ui's. Concretely, an app that doesno longer compiles against this package. It must import
material_uiand use itsMaterialApp— which is also what supplies theMaterialLocalizationsseveral components read.Two prefixed bridges remain
Each is for a dependency that has not migrated, and each carries an
// ignore: migrate_design_widgets. Neither changes rendering:StreamMessageTextflutter_markdown_plus(latest, 1.0.12) still takes Flutter'sThemeDatainMarkdownStyleSheet.fromTheme. The bridge maps exactly the fields that factory reads — the fivetextThemeslots,cardTheme.color,cardColor,primaryColor,dividerColor, and thecolorScheme— so the sheet it produces is unchanged.ThemeConfiguration.buildWidgetbookTheme(gallery)ThemeDatafor its own chrome. The Stream branding is restated over the same colors, so the gallery looks the same.None of the other Material-adjacent dependencies needed one —
alchemist,shimmer,flutter_svg,cached_network_image_ce,svg_icon_widget,device_frame_plusandflutter_colorpickereither take shared types (Color,TextStyle,Widget) or none at all.The lint is safe at the floor
migrate_design_widgetsis recognised by the 3.47 analyzer and not by 3.44's. That does not breaklegacy_version_analyze:undefined_lintis reported against the options file itself, and nothing in CI analyses the repo root whereall_lint_rules.yamllives —melos run analyzeruns per package, and the legacy job runs frompackages/stream_core/lib. Enabled, the rule reports exactly the two bridges above and nothing else, which is the proof the migration is complete.Test-only adjustments
Both are consequences of the split, not of anything this repo does unusually:
find.byTooltipis bound to Flutter'sTooltip. The bottom-nav tooltip test now matchesmaterial_ui's by predicate.MaterialApp, whoseMaterialLocalizationsthe migratedStreamBottomNavBarcannot read — it threwNo MaterialLocalizations foundand rendered an error box. Its two golden wrappers now providematerial_ui's delegates. Worth knowing for any future component that reads localizations: widget tests are fine (they wrap inmaterial_ui'sMaterialApp), golden tests are not.Testing
dart analyze --fatal-infos, 4 packages @ 3.47.0melos run check:barrelsmelos run format:verify(441 files)melos run generate:allstream_core_fluttertests (GITHUB_ACTIONS=true)stream_core/stream_thumbnailtestsOn the 42 golden failures: the pre-existing macOS-vs-Linux baseline (committed goldens are Linux-rendered), carried over unchanged from #150 —
commreturns empty in both directions against that branch's run. Zero introduced. Every golden that can be compared on macOS is unchanged; the 42 are the ones whose committed baselines are Linux-rendered, so they are equally unverifiable here before and after. This PR's own CI run on Linux is the check for those. No golden was regenerated,update_goldens.ymlwas not dispatched, and no.pngis touched by this diff.Screenshots / Videos
Not applicable — the migration is pixel-neutral, which is what the golden comparison above establishes.
🤖 Generated with Claude Code