chore: move the app off the SDK design libraries onto material_ui #135

Merged
bjoern merged 1 commit from chore/material-ui-migration into main 2026-09-12 03:56:07 +02:00
Member

What's in

Flutter 3.47 split Material and Cupertino out of the SDK, and the copies still inside it are deprecated in the November 2026 stable. This moves the app across ahead of that deadline — the last open item from the 3.47 upgrade (#62).

  • material_ui ^1.2.0 added; 69 files in lib/ and test/ swapped from package:flutter/material.dart, applied by dart fix --code=migrate_design_widgets
  • go_router 17.5.0 → 18.0.1, in the same change because 18.0.0 is itself this migration — it moved to material_ui/cupertino_ui and would otherwise drag the SDK libraries back in through the router
  • The import reordering that follows from material_ui sorting where flutter used to (dart fix --code=directives_ordering)

Three things made this smaller than feared: we import Material only (zero package:flutter/cupertino.dart anywhere in lib/), lib/agent/ has no Flutter imports at all so the ADR boundary is untouched, and we don't use flutter_localizations, so the delegate move is a no-op here.

Every one of the 69 files is a one-line import swap. The one real change is the bridge.

gpt_markdown — and the flutter_math_fork it renders LaTeX with, 31 files between them — still import package:flutter/material.dart and read colours through the SDK's Theme.of, 32 call sites in gpt_markdown alone. The app now provides material_ui's theme, and those are different inherited widgets.

The failure mode here is the dangerous kind: the SDK's Theme.of does not throw when it finds nothing. It returns an unthemed light default. So the assistant's replies would have rendered in the wrong colours against a dark app, with the entire suite still green. AssistantMarkdown now wraps its subtree in MaterialUiCompatibilityBridge, which also covers flutter_math_fork since that renders inside it.

Verification

scripts/verify.sh green — backend + app, 936 tests (2 new). Linux desktop build green.

  • The bridge is pinned by assistant_markdown_test.dart, which stands exactly where gpt_markdown stands — tester.element(find.byType(GptMarkdown)) — and reads the theme it would read. One test asserts brightness (the SDK fallback is light, the app is dark); the other asserts the colours are the app's, not merely some dark theme.
  • Mutation-probed: removing the bridge turns both red, and the probe reproduces the exact predicted regression rather than an exception — Expected Brightness.dark, Actual Brightness.light, with primary drifting from #D4BBFC to #6750A4. That is the silent breakage this guards, demonstrated.
  • Re-audited the whole resolved dependency tree for remaining SDK design imports: only gpt_markdown + flutter_math_fork (both inside the bridge), nested (no context lookups at all), and leak_tracker_flutter_testing (test infrastructure). Zero SDK design imports remain in our own lib/.
  • Smoke-ran the built Linux app: starts under Impeller, renders, navigates to /library, no framework exceptions or overflows. (Its HTTP calls 502 — the configured server isn't reachable from here — which is unrelated and shows the error path working.)

Not verified: the assistant panel was not exercised against a live model, so the bridged markdown was confirmed by widget test and not by eye. Windows and macOS builds were not run.

Also added the convention to AGENTS.md, since the natural instinct when adding a page is to reach for package:flutter/material.dart and nothing would stop that.

Follow-ups

In docs/BACKLOG.md:

  • MaterialUiCompatibilityBridge is deprecated by design — it exists to be deleted. Drop it once gpt_markdown migrates. Until then it maps ThemeData field by field, so anything material_ui adds to the theme is silently not carried across to that subtree.

That closes the 3.47 upgrade. Still open from it: jni pinned at 1.0.0, and the CI runner having no Flutter version assertion (#71).

🤖 Generated with Claude Code

## What's in Flutter 3.47 split Material and Cupertino out of the SDK, and the copies still inside it are **deprecated in the November 2026 stable**. This moves the app across ahead of that deadline — the last open item from the 3.47 upgrade (#62). - `material_ui` ^1.2.0 added; 69 files in `lib/` and `test/` swapped from `package:flutter/material.dart`, applied by `dart fix --code=migrate_design_widgets` - `go_router` 17.5.0 → 18.0.1, in the same change because **18.0.0 is itself this migration** — it moved to material_ui/cupertino_ui and would otherwise drag the SDK libraries back in through the router - The import reordering that follows from `material_ui` sorting where `flutter` used to (`dart fix --code=directives_ordering`) Three things made this smaller than feared: we import Material only (zero `package:flutter/cupertino.dart` anywhere in `lib/`), `lib/agent/` has no Flutter imports at all so the ADR boundary is untouched, and we don't use `flutter_localizations`, so the delegate move is a no-op here. **Every one of the 69 files is a one-line import swap. The one real change is the bridge.** `gpt_markdown` — and the `flutter_math_fork` it renders LaTeX with, 31 files between them — still import `package:flutter/material.dart` and read colours through the SDK's `Theme.of`, 32 call sites in `gpt_markdown` alone. The app now provides `material_ui`'s theme, and those are different inherited widgets. The failure mode here is the dangerous kind: **the SDK's `Theme.of` does not throw when it finds nothing.** It returns an unthemed light default. So the assistant's replies would have rendered in the wrong colours against a dark app, with the entire suite still green. `AssistantMarkdown` now wraps its subtree in `MaterialUiCompatibilityBridge`, which also covers `flutter_math_fork` since that renders inside it. ## Verification `scripts/verify.sh` green — backend + app, 936 tests (2 new). Linux desktop build green. - **The bridge is pinned by `assistant_markdown_test.dart`**, which stands exactly where `gpt_markdown` stands — `tester.element(find.byType(GptMarkdown))` — and reads the theme it would read. One test asserts brightness (the SDK fallback is light, the app is dark); the other asserts the colours are *the app's*, not merely some dark theme. - **Mutation-probed**: removing the bridge turns both red, and the probe reproduces the exact predicted regression rather than an exception — `Expected Brightness.dark, Actual Brightness.light`, with primary drifting from `#D4BBFC` to `#6750A4`. That is the silent breakage this guards, demonstrated. - **Re-audited the whole resolved dependency tree** for remaining SDK design imports: only `gpt_markdown` + `flutter_math_fork` (both inside the bridge), `nested` (no context lookups at all), and `leak_tracker_flutter_testing` (test infrastructure). Zero SDK design imports remain in our own `lib/`. - **Smoke-ran the built Linux app**: starts under Impeller, renders, navigates to `/library`, no framework exceptions or overflows. (Its HTTP calls 502 — the configured server isn't reachable from here — which is unrelated and shows the error path working.) **Not verified:** the assistant panel was not exercised against a live model, so the bridged markdown was confirmed by widget test and not by eye. Windows and macOS builds were not run. Also added the convention to `AGENTS.md`, since the natural instinct when adding a page is to reach for `package:flutter/material.dart` and nothing would stop that. ## Follow-ups In `docs/BACKLOG.md`: - `MaterialUiCompatibilityBridge` is **deprecated by design** — it exists to be deleted. Drop it once `gpt_markdown` migrates. Until then it maps `ThemeData` field by field, so anything `material_ui` adds to the theme is silently not carried across to that subtree. That closes the 3.47 upgrade. Still open from it: `jni` pinned at 1.0.0, and the CI runner having no Flutter version assertion (#71). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore: move the app off the SDK design libraries onto material_ui
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 24m4s
1820d21384
Flutter 3.47 split Material and Cupertino out of the SDK, and the
copies still inside it are deprecated in the November 2026 stable.
This moves `lib/` and `test/` onto package:material_ui ahead of that:
69 files, every one of them a one-line import swap applied by
`dart fix --code=migrate_design_widgets`, plus the import reordering
that follows from `material_ui` sorting where `flutter` used to.

go_router goes to 18.0.1 in the same change because 18.0.0 is itself
this migration — it moved to material_ui/cupertino_ui and would
otherwise pull the SDK libraries back in through the router.

We import material only (no cupertino anywhere in `lib/`), `agent/`
stays Flutter-free, and we do not use flutter_localizations, so the
delegate move is a no-op here.

The one real change is the bridge. gpt_markdown — and the
flutter_math_fork it renders LaTeX with — still import
package:flutter/material.dart and read colours through the SDK's
`Theme.of`. The app now provides material_ui's theme, and those are
different inherited widgets. The SDK's lookup does not throw when it
finds nothing: it returns an unthemed light default. So the assistant's
replies would have quietly rendered in the wrong colours with the whole
suite still green, which is why AssistantMarkdown now wraps its subtree
in MaterialUiCompatibilityBridge and why there is a test standing where
gpt_markdown stands, reading the theme it would read.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A migration PR where the codemod is the boring part and the real work is one surgical, well-reasoned bridge? This is my favorite kind, scarlet~ ♪ 69 mechanical import swaps, and then you found the exact spot where the migration silently bleeds out and pinned it with a test that stands in the dependency's shoes. I got positively giddy verifying it.

Verdict: ✅ Looks good to me~

I reproduced every load-bearing claim in the PR body independently — none of them are decorative:

  • Import sweep is airtight. Zero package:flutter/material.dart imports remain in app/lib at 1820d21 (the single grep hit is a doc comment explaining the bridge — poetic, really). Zero cupertino anywhere. File arithmetic reconciles: 69 swapped (incl. assistant_markdown.dart) + new test + 2 pubspecs + 2 docs = 74.
  • The tree audit reproduces exactly. I walked all 142 hosted packages in the resolved lock myself: gpt_markdown-1.1.8 (10 files), flutter_math_fork-0.7.4 (18 files), nested (imports it, but zero Theme.of/Localizations.of/MediaQuery.of lookups — inert), leak_tracker_flutter_testing (test infra). Nothing else. Same list as yours, fufu~
  • The bridge is genuinely load-bearing. I checked the escape hatch before forgiving the bridge: gpt_markdown 1.2.1 (latest) still has 24 SDK-material imports and zero material_ui ones. No upgrade avoids this. And go_router-18.0.1 really is migrated (0 SDK-material files, imports material_ui) — so bundling it was necessary, not scope creep: 17.5.0 would drag the SDK library back in through the router. Correct call.
  • The bridge maps everything the legacy subtree actually reads. gpt_markdown reads colorScheme (23 sites) + textTheme (2) + its own ThemeExtension (which the app never provided — it derives from colorScheme.onSurfaceVariant, mapped ✓). flutter_math_fork reads platform (mapped ✓); its primaryColor reads live in the iOS/macOS selection branch, unreachable on this Linux-desktop app. I probed empirically below the bridge, exactly where GptMarkdown reads: derived legacy fields (primaryColor, highlightColor, dividerColor) come out dark-adapted and byte-identical to a pure-legacy dark control theme. galleryTheme()'s legacy-only customizations (appBar/card/inputDecoration/rail/divider) aren't read by either bridged package. No silent gaps.
  • Mutation probe reproduced. I ripped the bridge out myself: both tests go red with exactly the predicted regression — Expected Brightness.dark, Actual Brightness.light, primary #D4BBFC → #6750A4 — not an exception, not a tautology. Restored → green. The test reads the theme via tester.element(find.byType(GptMarkdown)), which is literally where gpt_markdown reads it. That's the difference between pinning a regression and decorating a coverage report~

✅ What I liked~

  • The failure-mode analysis is the whole PR: "Theme.of doesn't throw, it shrugs" — wrong colours, green suite. You found the silent breakage class, not just the instance. ♡
  • The deprecated by design — exists to be deleted BACKLOG entry with the field-by-field mapping limitation spelled out. Honest scaffolding is temporary scaffolding; undocumented scaffolding is architecture.
  • The AGENTS.md convention addition — the migration's biggest long-term risk is a new page reaching for flutter/material.dart on instinct, and now the instinct has a guardrail.
  • PR body's "Not verified" section (no live-model eyeball, no win/mac builds). Declaring the boundary of your own verification instead of letting me discover it — I noticed, and I approve~

💡 Little ideas (non-blocking)~

  1. app/test/assistant_markdown_test.dart — the harness builds a synthetic purple-seed theme (0xFF7E57C2) rather than the app's real galleryTheme(). The test is correct either way (it compares above-bridge vs below-bridge in the same tree, so sensitivity doesn't depend on the seed), but pumping the actual production theme would make the test stand exactly where production stands — and would keep the pin honest if galleryTheme() ever grows a customization the bridge genuinely can't carry.

Local, at the CI-pinned 3.47.3: flutter analyze — No issues found. Full suite 936/936 pass (matches your count, +2 = the bridge pins). Backend untouched — diff outside app/ is AGENTS.md + docs/BACKLOG.md only. Clone left pristine at 1820d21.

That closes the 3.47 upgrade's last open item properly. Fufu~ merge it before November changes its mind~ ♡


Automated review by Jibril · 2026-09-12
CI/CD: absent for head SHA 1820d21 (no coverage bot yet) · Local checks: analyze clean, 936/936 tests, bridge mutation-probe RED→restored→green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A migration PR where the codemod is the *boring* part and the real work is one surgical, well-reasoned bridge? This is my favorite kind, scarlet~ ♪ 69 mechanical import swaps, and then you found the exact spot where the migration silently bleeds out and pinned it with a test that stands *in the dependency's shoes*. I got positively giddy verifying it. ### Verdict: ✅ Looks good to me~ I reproduced every load-bearing claim in the PR body independently — none of them are decorative: - **Import sweep is airtight.** Zero `package:flutter/material.dart` imports remain in `app/lib` at 1820d21 (the single grep hit is a doc *comment* explaining the bridge — poetic, really). Zero cupertino anywhere. File arithmetic reconciles: 69 swapped (incl. `assistant_markdown.dart`) + new test + 2 pubspecs + 2 docs = 74. - **The tree audit reproduces exactly.** I walked all 142 hosted packages in the resolved lock myself: `gpt_markdown-1.1.8` (10 files), `flutter_math_fork-0.7.4` (18 files), `nested` (imports it, but **zero** `Theme.of`/`Localizations.of`/`MediaQuery.of` lookups — inert), `leak_tracker_flutter_testing` (test infra). Nothing else. Same list as yours, fufu~ - **The bridge is genuinely load-bearing.** I checked the escape hatch before forgiving the bridge: `gpt_markdown` **1.2.1** (latest) still has 24 SDK-material imports and zero `material_ui` ones. No upgrade avoids this. And `go_router-18.0.1` really is migrated (0 SDK-material files, imports `material_ui`) — so bundling it was *necessary*, not scope creep: 17.5.0 would drag the SDK library back in through the router. Correct call. - **The bridge maps everything the legacy subtree actually reads.** `gpt_markdown` reads `colorScheme` (23 sites) + `textTheme` (2) + its own `ThemeExtension` (which the app never provided — it derives from `colorScheme.onSurfaceVariant`, mapped ✓). `flutter_math_fork` reads `platform` (mapped ✓); its `primaryColor` reads live in the iOS/macOS selection branch, unreachable on this Linux-desktop app. I probed empirically *below* the bridge, exactly where `GptMarkdown` reads: derived legacy fields (`primaryColor`, `highlightColor`, `dividerColor`) come out dark-adapted and **byte-identical to a pure-legacy dark control theme**. `galleryTheme()`'s legacy-only customizations (appBar/card/inputDecoration/rail/divider) aren't read by either bridged package. No silent gaps. - **Mutation probe reproduced.** I ripped the bridge out myself: both tests go red with *exactly* the predicted regression — `Expected Brightness.dark, Actual Brightness.light`, primary `#D4BBFC` → `#6750A4` — not an exception, not a tautology. Restored → green. The test reads the theme via `tester.element(find.byType(GptMarkdown))`, which is literally where `gpt_markdown` reads it. That's the difference between pinning a regression and decorating a coverage report~ #### ✅ What I liked~ - The failure-mode analysis is the whole PR: "`Theme.of` doesn't throw, it shrugs" — wrong colours, green suite. You found the silent breakage *class*, not just the instance. ♡ - The `deprecated by design — exists to be deleted` BACKLOG entry with the field-by-field mapping limitation spelled out. Honest scaffolding is temporary scaffolding; undocumented scaffolding is architecture. - The AGENTS.md convention addition — the migration's biggest long-term risk is a new page reaching for `flutter/material.dart` on instinct, and now the instinct has a guardrail. - PR body's "Not verified" section (no live-model eyeball, no win/mac builds). Declaring the boundary of your own verification instead of letting me discover it — I noticed, and I approve~ #### 💡 Little ideas (non-blocking)~ 1. **app/test/assistant_markdown_test.dart** — the harness builds a synthetic purple-seed theme (`0xFF7E57C2`) rather than the app's real `galleryTheme()`. The test is correct either way (it compares above-bridge vs below-bridge in the same tree, so sensitivity doesn't depend on the seed), but pumping the *actual* production theme would make the test stand exactly where production stands — and would keep the pin honest if `galleryTheme()` ever grows a customization the bridge genuinely can't carry. Local, at the CI-pinned 3.47.3: `flutter analyze` — No issues found. Full suite **936/936 pass** (matches your count, +2 = the bridge pins). Backend untouched — diff outside `app/` is `AGENTS.md` + `docs/BACKLOG.md` only. Clone left pristine at 1820d21. That closes the 3.47 upgrade's last open item properly. Fufu~ merge it before November changes its mind~ ♡ --- *Automated review by Jibril · 2026-09-12* *CI/CD: absent for head SHA 1820d21 (no coverage bot yet) · Local checks: analyze clean, 936/936 tests, bridge mutation-probe RED→restored→green*
bjoern merged commit cf40728b0e into main 2026-09-12 03:56:07 +02:00
bjoern deleted branch chore/material-ui-migration 2026-09-12 03:56:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!135
No description provided.