diff --git a/.claude/skills/flutter-version-bump/SKILL.md b/.claude/skills/flutter-version-bump/SKILL.md new file mode 100644 index 00000000..b7c4701e --- /dev/null +++ b/.claude/skills/flutter-version-bump/SKILL.md @@ -0,0 +1,482 @@ +--- +name: flutter-version-bump +description: > + Adopt a new Flutter stable release in this monorepo — diagnose and fix the analyze, format, golden, and barrel + failures a new toolchain introduces, then raise the published minimum Flutter/Dart floor to the SDK's + "latest stable − 1" policy. Use when CI suddenly goes red after a Flutter release, when + `dart analyze --fatal-infos` reports diagnostics that did not exist before, when goldens drift after upgrading, + or when asked to "support Flutter X" or "bump the min Flutter version". +allowed-tools: + - Bash + - Read + - Edit + - Write + - Grep + - Glob +--- + +# flutter-version-bump + +Two different jobs share the phrase "bump Flutter". Decide which one you are doing **before** touching a file — +they produce different diffs, different review burdens, and only one of them changes what consumers can resolve. + +| Track | Goal | Scope | Consumer impact | +|---|---|---|---| +| **A — Compat** (first) | Make CI green on the new stable | Source fixes, lint config, goldens | None | +| **B — Floor raise** (policy-driven) | Move the minimum to latest − 1 | 6 pubspecs + `melos.yaml` + `legacy_version_analyze.yml` + CHANGELOGs + newly-activated lints | Apps below the floor stop resolving | + +A floor raise is **not** a breaking change: no `!` in the commit/PR title, and the CHANGELOG bullet does not go +under `### 🛑 Breaking / Removals`. Existing code keeps compiling; older SDKs simply stop resolving the new +version. Do not invent a `!` for it. + +**Do Track A first, always.** "CI broke after the new Flutter came out" is Track A, and it must land green before +Track B starts — otherwise you cannot tell a floor-raise failure from a new-stable failure. + +**Then check whether Track B is due.** The SDK's policy is **minimum supported = latest stable − 1**, so a new +stable makes the floor raise *routine, not exceptional*: when 3.47 shipped, the floor moved 3.38 → **3.44**. Pair +each Flutter minor with its Dart SDK (3.44 → Dart 3.12, 3.47 → Dart 3.13); `fvm releases` lists both, and the +authoritative list is +`https://storage.googleapis.com/flutter_infra_release/releases/releases_macos.json` (`fvm`'s cache goes stale). + +Track B belongs in a minor/major release rather than a hotfix, and must be its own commit — its diff has nothing +to do with the compat fixes. + +Only **two** version knobs move on a floor raise here, and they are both the *floor*, never the new stable: + +| Knob | Value | +|---|---| +| `melos.yaml` `command.bootstrap.environment` | the new floor — source of truth, `melos bs` propagates to every pubspec | +| `.github/workflows/legacy_version_analyze.yml` `env.flutter_version` | the new floor — this job *is* the floor's regression test | + +> The repo has **no `.fvmrc`** — local Flutter selection is not pinned here. Do not create one as a side effect of +> a version bump; that is its own decision. It does mean nothing forces a developer's local toolchain to be the +> floor, so newer-than-floor API usage is caught only by `legacy_version_analyze`, which analyzes +> **`stream_core/lib` only** (see below). Analyse against the floor by hand. + +## Why CI breaks the day a Flutter stable ships + +`.github/workflows/stream_core_flutter_workflow.yml` installs Flutter with + +```yaml +channel: ${{ env.FLUTTER_CHANNEL }} # stable — no version pin +``` + +so both jobs (`analyze` and `test`) **auto-adopt the new stable within hours of release**. +`legacy_version_analyze.yml` (the N-1 canary) pins a version and does *not* follow. So the first symptom is always +"CI went red and nobody changed anything", while a local machine on an older SDK still passes. + +`melos run analyze` runs `dart analyze --fatal-infos` (examples excluded). New SDKs ship new diagnostics as +**infos and warnings**, which `--fatal-infos` turns into hard failures. That is why an SDK bump hurts here more +than in a typical repo. + +### Check the canary first — it usually already told you + +`beta_version_analyze.yml` runs the `package_analysis` action against the **beta** channel every Monday and Slacks +on failure. Beta becomes stable roughly a quarter later, so this workflow reports the next release's analyzer +failures *months* early. Before investigating anything, read its history: + +```bash +gh run list --workflow=beta_version_analyze.yml --limit 10 +gh run view --log-failed | grep -E "warning -|info -|error" +``` + +Two traps when reading it, both because `.github/actions/package_analysis/action.yml` is a **one-package action**: + +```bash +cd packages/stream_core/lib && dart analyze --fatal-warnings . && cd .. && flutter test --exclude-tags golden +``` + +- **It only covers `stream_core`.** `stream_core_flutter`, `stream_thumbnail` and the gallery are never analysed + by the canary or by `legacy_version_analyze`. A green canary says nothing about the Flutter packages. +- **It is `--fatal-warnings`, not `--fatal-infos`,** and `lib/` only. New *infos* — the bulk of what a new SDK + ships — pass the canary and fail `melos run analyze`. + +If the canary has been red and unactioned for weeks, that is the most valuable finding in the exercise — report it +separately from the code fixes. + +## Step 1 — Branch off main + +Never branch a toolchain bump off a feature branch. Bootstrap rewrites lockfiles repo-wide. + +```bash +git fetch origin +git checkout -b chore/flutter- origin/main +``` + +## Step 2 — Install the new SDK side by side + +Keep the old one. Every claim below is an A/B comparison, and you cannot make one with a single toolchain. + +```bash +fvm install # e.g. 3.47.0 +fvm list # confirm old + new are both cached +NEW=~/fvm/versions/ +OLD=~/fvm/versions/ +``` + +On a floor raise you want **three**: the old floor, the new floor (must analyse clean — it is what +`legacy_version_analyze` will run) and the latest stable (what CI's `analyze` job actually resolves to). + +## Step 3 — Measure before you fix + +The single most important habit: **never attribute a failure to the new SDK without seeing the old SDK pass it.** +Repos accumulate drift; a feature branch may already be dirty; a local `build/` directory can inject hundreds of +phantom issues. Run each check under both toolchains and diff. + +### Format + +CI runs `melos run format:verify` → `dart format --set-exit-if-changed .` from the root, which walks untracked +trees too. Scope to tracked files so `build/` noise cannot pollute the comparison: + +```bash +git ls-files '*.dart' > /tmp/dartfiles.txt +for V in $OLD $NEW; do + echo "== $V" + $V/bin/cache/dart-sdk/bin/dart format --output=none --set-exit-if-changed $(cat /tmp/dartfiles.txt) 2>&1 | tail -3 +done +``` + +The root `analysis_options.yaml` sets `page_width: 120` and `trailing_commas: preserve`; `dart format` reads it +from the file nearest the target, so always format from the repo root, never from inside a package with its own +options file. + +Interpretation: + +- **Both 0 changed** → the formatter did not change. Do not touch formatting in this PR. +- **New > 0, old = 0** → `dart_style` changed. Apply it as an **isolated commit** touching nothing else, so the + real fixes stay reviewable. +- **Old > 0, new = 0** → the repo is already formatted for a newer formatter than the floor. Pre-existing drift, + harmless. Not yours to fix here — but mention it. + +### Analyze + +> **Run `melos bootstrap` first, and re-run it after every pubspec edit.** `dart analyze` reads the *language +> version* from `.dart_tool/package_config.json`, written by `pub get` — **not** from `pubspec.yaml`. A stale +> `.dart_tool` reports a confidently clean result that CI will not reproduce, and editing an SDK constraint +> without re-bootstrapping changes nothing at all. Verify with: +> +> ```bash +> python3 -c "import json;d=json.load(open('packages/stream_core_flutter/.dart_tool/package_config.json'));\ +> print([p.get('languageVersion') for p in d['packages'] if p['name']=='stream_core_flutter'])" +> ``` +> +> Despite the root `pubspec.yaml` being named `stream_core_flutter_workspace`, this is **not** a pub workspace — +> no package declares `resolution: workspace`. Melos bootstraps each package separately, so every package has its +> own `.dart_tool/package_config.json` and you must check the one you care about. + +Mirror `melos run analyze` (`--fatal-infos`, examples excluded) and **filter local build artifacts**, which are +not in CI and will otherwise bury the real signal: + +```bash +set -o pipefail # otherwise a matching grep masks an analyzer that crashed +for V in $OLD $NEW; do + echo "##### $V" + for p in packages/stream_core packages/stream_core_flutter packages/stream_thumbnail apps/design_system_gallery; do + echo "### $p" + # `|| true` so a package with no diagnostics is not reported as a failure + (cd "$p" && $V/bin/cache/dart-sdk/bin/dart analyze --fatal-infos . 2>&1 \ + | { grep -E "^\s+(info|warning|error)" | grep -v " build/" || true; }) + done +done +``` + +Everything present under `$NEW` and absent under `$OLD` is your work list. Everything in both is pre-existing — +leave it alone and say so. + +### Tests and goldens — `GITHUB_ACTIONS=true` is mandatory + +`packages/stream_core_flutter/test/flutter_test_config.dart` switches alchemist on **`GITHUB_ACTIONS` only** — +not the more common `CI`. Setting `CI=true` looks right and silently runs the *platform* variant instead: + +```dart +final isRunningInCi = Platform.environment.containsKey('GITHUB_ACTIONS'); +ciGoldensConfig: CiGoldensConfig(enabled: isRunningInCi), +platformGoldensConfig: PlatformGoldensConfig(enabled: !isRunningInCi), +``` + +**Only `goldens/ci/` is committed** (47 files). A bare `flutter test` on your machine runs the platform variant, +whose goldens do not exist in the repo, and fails every golden test for reasons that have nothing to do with the +new SDK. Always: + +```bash +for V in $OLD $NEW; do + (cd packages/stream_core_flutter && GITHUB_ACTIONS=true $V/bin/flutter test --reporter=compact > /tmp/t-$(basename $V).log 2>&1) +done +# compare the failure sets, not the counts — `\r` matters, the compact reporter uses it +for V in $OLD $NEW; do + tr '\r' '\n' < /tmp/t-$(basename $V).log | grep -E '\[E\]$' | sed 's|.*/test/|test/|' | sort -u \ + > /tmp/fail-$(basename $V).txt +done +comm -13 /tmp/fail-$(basename $OLD).txt /tmp/fail-$(basename $NEW).txt # caused by the new SDK +comm -12 /tmp/fail-$(basename $OLD).txt /tmp/fail-$(basename $NEW).txt # pre-existing, out of scope +``` + +Repeat for `packages/stream_core` (pure Dart, no goldens). + +Committed goldens are Linux-rendered, so some fail on macOS even on the old SDK. That baseline noise is exactly +what `comm` separates out. **Only the lines the new SDK adds are yours.** + +> **Never run `git checkout -- .` between runs.** Alchemist only writes untracked `failures/*.png` directories, +> so there is nothing to revert — and a blanket checkout silently destroys the source fixes you just made. Clean +> up with `git clean -fd -- '*/failures'` instead, and keep `git status --short` in view. + +### Barrels + +`melos run check:barrels` gates the PR alongside analyze. It is not toolchain-sensitive on its own, but a fix +that moves or adds a file under `packages/stream_core_flutter/lib/src/` breaks it. Run it in the same breath as +analyze. + +## Step 4 — Fix, by failure class + +Work the diff from Step 3. Known classes and this repo's chosen remedy: + +### New analyzer diagnostics (the usual bulk) + +New SDKs add diagnostics that `--fatal-infos` promotes to failures. Treat each as a real finding first — most of +them point at a genuine latent bug — and only suppress when the diagnostic is wrong about this code. + +| Remedy | When | +|---|---| +| Fix the code | Default. The diagnostic is usually right. | +| `// ignore: ` with a one-line reason above it | The diagnostic is correct in general but wrong here, or the fix belongs to an upstream package. Never a bare ignore — see `STYLE_GUIDE.md`. | +| Flip the rule to `false` in `analysis_options.yaml`, or delete it from `all_lint_rules.yaml` | The lint was **removed or renamed** by the SDK. An unrecognized rule name is itself a warning. | + +The lint config here is inverted relative to most repos: `all_lint_rules.yaml` is an **explicit list of every +rule**, included wholesale, and `analysis_options.yaml` then switches individual rules back to `false` with a +comment saying why. Two consequences: + +- **A new SDK's new rules do not activate on their own.** The list is static, so adopting them means adding the + names to `all_lint_rules.yaml` — a code-style decision with its own before/after numbers, and **always its own + PR**, never this one. +- **A removed or renamed rule fires `undefined_lint` in `all_lint_rules.yaml`, not in `analysis_options.yaml`.** + Delete it there; if `analysis_options.yaml` also disables it, delete that line too or it becomes the next + `undefined_lint`. + +To find removed/renamed/deprecated lints mechanically rather than by guessing, analyse the options files +themselves — `melos run analyze` never does, because both sit at the repo **root**, outside every melos package: + +```bash +for V in $OLD $NEW; do + echo "== $V"; $V/bin/cache/dart-sdk/bin/dart analyze --fatal-infos analysis_options.yaml all_lint_rules.yaml +done +``` + +`undefined_lint` means the rule was removed or renamed — a hard failure once the root is analysed. +`deprecated_lint` means it still parses but is on its way out — cheaper to drop now than to discover as +`undefined_lint` two releases later. Run it under both toolchains: a `deprecated_lint` that also fires on the old +SDK is pre-existing debt, not something this release introduced. + +### Framework deprecations + +New `deprecated_member_use` infos are fatal here. Prefer migrating to the replacement API. If the replacement does +not exist on the floor in `melos.yaml`, you cannot use it — suppress with a scoped ignore naming the reason, and +leave the migration for the release that raises the floor. + +### New runtime assertions + +Flutter adds asserts that only fire in tests, so they surface as widget-test failures, not analyzer output. Read +the assertion and fix the widget tree; do not silence the test. + +### Golden pixel drift + +Small diffs (well under 1%) across unrelated widgets mean the engine's rasterisation changed — legitimate, and the +goldens must be regenerated. Larger diffs confined to one widget family usually mean a real layout change; +investigate before regenerating. **On a Track B floor raise, goldens should not move at all** — CI still runs the +latest stable either way. If they do, that is a signal to investigate, not to regenerate. + +**Goldens are always regenerated by the CI workflow — never locally. No exceptions.** + +`melos run update:goldens` writes the *platform* variant on your machine; the committed `goldens/ci/*.png` are +Linux-rendered. Regenerating locally therefore produces macOS pixels the repo does not even track. Locally you may +**compare** (`GITHUB_ACTIONS=true flutter test`) to see which goldens moved — never write them. + +`update_goldens.yml` checks out with `secrets.BOT_SSH_PRIVATE_KEY` and commits `**/test/**/goldens/*.png` back to +whatever branch you dispatch against, so **push the branch first**. It has no inputs — it always regenerates +everything, on `ubuntu-latest`, with `flutter-version: "3.x"` (the new stable, automatically). + +> **Confirm with the user before running this.** It pushes a branch and dispatches a workflow that writes a commit +> to the remote. It is the one outward-facing action in this skill — never dispatch it unprompted, and expect the +> branch to stay red on goldens until it has run. + +```bash +git push -u origin chore/flutter- +gh workflow run update_goldens.yml --ref chore/flutter- +gh run watch $(gh run list --workflow=update_goldens.yml --limit 1 --json databaseId --jq '.[0].databaseId') +git pull # pick up the bot's "chore: Update Goldens" commit +``` + +Two consequences to state plainly when you report: + +- **The branch is not verifiable-green on macOS.** Even after regeneration, a local `GITHUB_ACTIONS=true` run + still shows the pre-existing Linux-vs-macOS baseline diffs from Step 3. Give the reviewer that number so a + non-zero local failure count is not read as "the fix did not work". +- `legacy_version_analyze.yml` never runs golden tests, so regenerating against the new stable cannot break the + N-1 canary. + +### Files the toolchain rewrites underneath you + +`melos bootstrap` and `flutter pub get` both edit tracked files. Anything they rewrite that you do not commit +fails the **format** job, since `melos run format:verify` runs after bootstrap in CI. + +- **`pubspec.lock`** — the workspace has a single root lock. Commit it. +- **`analyzer.exclude` blocks injected into an app-type `analysis_options.yaml`** — Flutter 3.47's `pub get` + writes `build/`, `android/`, `ios/`, … exclusions into packages that have a `flutter:` SDK dep *and* platform + directories (here: `apps/design_system_gallery`, `packages/stream_thumbnail/example`). It is tool-authored + config, not ours. **Surface it, do not decide it yourself** — and note that `pub get` *merges* its full list + into any existing `exclude:`, so a hand-trimmed version is not a stable fixed point. +- **`test_api: any` / `flutter_test: any` appended to `dev_dependencies`** — melos injects these around bootstrap + and normally strips them again. **Never commit them**: `flutter_test` has no pub.dev version, so committing it + makes the next `melos bootstrap` fail version solving outright. + +There are **no Android/iOS build jobs** in this repo's CI — nothing compiles the gallery or the thumbnail example +for a device. So Gradle/AGP/Kotlin/Xcode floors never gate a PR here, and a new Flutter's raised build-tool floors +are invisible until someone builds locally. Say that rather than implying the platform projects are verified. + +## Step 5 — Verify like CI does + +```bash +melos bootstrap +melos run analyze +melos run check:barrels +melos run format:verify +GITHUB_ACTIONS=true melos run test:all +git status --short # expect only your intended edits +git clean -fdn -- '*/failures' # review, then drop -n to remove alchemist's diff images +``` + +Two things will still look wrong locally and are not: + +- `melos run analyze` surfaces `build/` noise if you have ever built the gallery. Compare against Step 3's + baseline instead of expecting a clean zero. +- `GITHUB_ACTIONS=true melos run test:all` still fails the pre-existing Linux-vs-macOS goldens. Compare failure + **sets**, not counts. + +Also re-check the floor, since `legacy_version_analyze.yml` gates the PR — and remember it covers `stream_core` +only, so extend it by hand to the packages it misses: + +```bash +for p in packages/*/; do (cd "$p/lib" && $FLOOR/bin/cache/dart-sdk/bin/dart analyze --fatal-infos .); done +``` + +A fix that relies on syntax newer than the floor passes on `$NEW` and fails that job. + +## Step 6 — Track B: raise the floor to latest − 1 + +Keep this out of the Track A commit — it changes what consumers can resolve and needs to be reviewable on its own. + +Version-carrying files, all of which must move together. **Derive the list by grep, do not trust this one** — it +is accurate as of the 3.44 raise: + +```bash +git ls-files '*pubspec.yaml' | xargs grep -ln 'sdk: \^3\.' +``` + +- `melos.yaml` — `command.bootstrap.environment.{sdk,flutter}` (the source of truth; `melos bs` propagates) +- `pubspec.yaml` (root workspace) — `environment.sdk` only; the root carries no `flutter` constraint +- `packages/stream_core/pubspec.yaml` — `sdk` only. It is **pure Dart and carries no `flutter` constraint — do + not add one.** +- `packages/stream_core_flutter/pubspec.yaml` — `sdk` + `flutter` +- `packages/stream_thumbnail/pubspec.yaml` and `packages/stream_thumbnail/example/pubspec.yaml` +- `apps/design_system_gallery/pubspec.yaml` +- `.github/workflows/legacy_version_analyze.yml` — `env.flutter_version`. Set it to the **new floor** (never the + new stable): this job exists to prove the floor still analyses clean. Note the existing value may be a *patch* + of the floor (`3.38.10` for a `>=3.38.1` floor) — pick one convention, say which in the commit message. + +Confirm melos actually propagated rather than assuming it: + +```bash +melos bootstrap && git diff --stat -- '*pubspec.yaml' +``` + +Melos only rewrites keys that already exist, so a pubspec missing a `flutter:` key stays missing one — which is +what you want for `stream_core`, and what you must fix by hand anywhere it is wrong. + +### The floor raise activates dormant lints — budget for it + +This is the step that surprises people. Raising the Dart constraint raises each package's **language version**, +and lints stay silent while their suggested fix is not yet expressible. Raise the floor and they all fire at once, +in code nobody touched. + +Concretely, the Dart 3.10 → 3.12 raise activated `prefer_initializing_formals` on 24 sites across +`stream_core`, `stream_core_flutter` and the gallery, because Dart 3.12 legalised `this._privateField` as a named +parameter. Zero issues before, 24 after — none of it caused by the new *stable*, all of it caused by the *floor*. + +So: **`melos bootstrap` and re-analyse immediately after editing the constraints**, before you write the +CHANGELOG. Then let the tooling do the mechanical work: + +```bash +for p in packages/*/ apps/design_system_gallery; do (cd $p && dart fix --dry-run); done +# then, per lint, once you have decided the fix is right: +(cd && dart fix --apply --code=prefer_initializing_formals) +``` + +Two things to check by hand afterwards — `dart fix` is mechanical, not thoughtful: + +- **Doc comments get mangled *and silently deleted*.** It rewrites `[logger]` to `[_logger]` in the doc above the + constructor — leaking a private name into public API docs, when callers still pass the *public* name + (`logger:`, underscore stripped). It also drops any `///` comment attached to the parameter it rewrites. Review + every comment line the refactor touched, in both directions: + + ```bash + git diff -- '*.dart' | grep -E "^[-+]\s*(///|//)" + ``` + + Checking only added lines misses the deletions — that is how a lost doc comment survives review. +- **Confirm no public parameter was renamed.** For every `this._foo` it introduced, the parameter it replaced must + have been named exactly `foo`. A mismatch is a silent breaking change for callers — and in this repo a public + widget or theme constructor is API that downstream SDKs (`stream-chat-flutter`, `stream-video-flutter`) depend + on. + +This repo generates heavily (`json_serializable`, `theme_extensions_builder`). Re-run `melos run generate:all` +afterwards and confirm the generated call sites are unchanged — the generators read constructor parameters, so a +renamed parameter would silently change `.g.dart` / `.g.theme.dart`. + +**Always `dart format` after build_runner.** Generated files carry a `// dart format width=80` marker and are +emitted at 80 columns while the repo's `analysis_options.yaml` sets `page_width: 120`, so a regen can dirty +generated files with pure reflow. Format before concluding codegen "changed" anything. Note the root analyzer +`exclude` covers `packages/*/lib/**/*.*.dart`, so generated files are not analysed — but they *are* formatted. + +Then, per `STYLE_GUIDE.md`, one short bullet under `## Upcoming` in each published package's CHANGELOG: + +```md +- Raised minimum Flutter to `>=X.Y.Z` and Dart SDK to `^A.B.C`. +``` + +`stream_core` is Dart-only — its bullet mentions the Dart SDK only. `apps/design_system_gallery` is not published +and has no CHANGELOG. The style guide's heading list (`### ✨ Features`, `### 🐛 Bug Fixes`, +`### 🛑 Breaking / Removals`, `### ⚠️ Deprecations`) has no "changed" bucket; a floor raise is **not** breaking, so +use `### 🔄 Changed` and update `STYLE_GUIDE.md` if you introduce it. + +Two CI gates read the CHANGELOG and will bite: + +- `semantic_changelog_update` in `pr_title.yml` maps scopes `llc`/`ui`/`thumb` to packages. A `chore(repo):` PR is + outside that map, so it is not *required* to touch a changelog — but if you do touch one, `changelog_placement` + requires the entries to be under `## Upcoming`, not under an already-published version heading. +- A package that was just released may have **no `## Upcoming` section at all**. Add one at the top rather than + filing the bullet under the released version — that is precisely what `changelog_placement` fails on. + +Finish with `melos bootstrap` and commit the resulting root `pubspec.lock`. + +## Step 7 — Changelog and PR + +Track A changes that are user-visible (a widget swapped, a deprecation migrated) get a CHANGELOG bullet in the +affected package. Pure CI/tooling/golden churn does not. + +PR title follows Conventional Commits with a **required scope** from `llc` / `ui` / `repo` / `thumb` +(`pr_title.yml` enforces it): + +- Track A → `chore(repo): support Flutter ` +- Track B → `chore(repo): bump min Flutter to and Dart SDK to ` + +## Report back with attribution + +When summarising, always separate the three buckets — it is the difference between a reviewable PR and a mystery: + +1. **Caused by the new SDK** (present on new, absent on old) — what this PR fixes. +2. **Pre-existing** (present on both) — explicitly out of scope, named so nobody re-investigates. +3. **Local-only noise** (`build/` artifacts, platform goldens without `GITHUB_ACTIONS=true`) — never appears in + CI, never fix. + +Say plainly what you could not verify. Here that is a short list — there are no build jobs — but +`legacy_version_analyze` covering only `stream_core` means "the canary is green" is a much weaker claim than it +sounds. diff --git a/.github/workflows/legacy_version_analyze.yml b/.github/workflows/legacy_version_analyze.yml index 32374d25..76940ddf 100644 --- a/.github/workflows/legacy_version_analyze.yml +++ b/.github/workflows/legacy_version_analyze.yml @@ -3,7 +3,7 @@ name: legacy_version_analyze env: # Note: The versions below should be manually updated after a new stable # version comes out. - flutter_version: "3.38.10" + flutter_version: "3.44.0" on: push: diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index d124e397..4efd5389 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -1352,12 +1352,21 @@ sub-headings: - Fixed a crash when opening the media viewer with an empty attachments list. +### 🔄 Changed + +- Raised the minimum Flutter version to `>=3.44.0` and the Dart SDK to `^3.12.0`. + ### 🛑 Breaking / Removals - Removed `StreamCoreMessageComposer`. Use `StreamMessageComposer` from `stream_chat_flutter` instead. ``` +`### 🔄 Changed` covers what is neither new API nor a fix and does not break +existing code — a raised minimum Flutter/Dart version, a tightened dependency +constraint, a changed default. A raised floor is **not** breaking: code keeps +compiling, older SDKs simply stop resolving the new version. + Prefer **one short bullet** per entry, describing the functional change. Longer entries are acceptable for user-visible multi-facet features where the extra context matters to someone deciding whether to upgrade — but avoid sub-bullets, diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml index 0fb9e4b5..02cbce02 100644 --- a/all_lint_rules.yaml +++ b/all_lint_rules.yaml @@ -7,6 +7,7 @@ linter: - always_use_package_imports - annotate_overrides - annotate_redeclares + - async_return_with_no_await - avoid_annotating_with_dynamic - avoid_bool_literals_in_conditional_expressions - avoid_catches_without_on_clauses @@ -71,15 +72,18 @@ linter: - document_ignores - empty_catches - empty_constructor_bodies + - empty_container_bodies - empty_statements - eol_at_end_of_file - exhaustive_cases - file_names - flutter_style_todos + - future_sync_value - hash_and_equals - implementation_imports - implicit_call_tearoffs - implicit_reopen + - initialize_in_field_declaration - invalid_case_patterns - invalid_runtime_check_with_js_interop_types - join_return_with_assignment @@ -91,16 +95,19 @@ linter: - lines_longer_than_80_chars - literal_only_boolean_expressions - matching_super_parameters + - migrate_design_widgets - missing_code_block_language_in_doc_comment - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list - no_default_cases - no_duplicate_case_values + - no_dynamic_casts - no_leading_underscores_for_library_prefixes - no_leading_underscores_for_local_identifiers - no_literal_bool_comparisons - no_logic_in_create_state - - no_runtimeType_toString + - no_raw_types + - no_runtimetype_tostring - no_self_assignments - no_wildcard_variable_uses - non_constant_identifier_names @@ -161,6 +168,7 @@ linter: - remove_deprecations_in_breaking_versions - require_trailing_commas - secure_pubspec_urls + - simple_directive_paths - simplify_variable_pattern - sized_box_for_whitespace - sized_box_shrink_expand @@ -186,6 +194,7 @@ linter: - unnecessary_brace_in_string_interps - unnecessary_breaks - unnecessary_const + - unnecessary_const_in_enum_constructor - unnecessary_constructor_name - unnecessary_final - unnecessary_getters_setters @@ -202,12 +211,15 @@ linter: - unnecessary_nullable_for_final_variable_declarations - unnecessary_overrides - unnecessary_parenthesis + - unnecessary_primary_constructor_body - unnecessary_raw_strings - unnecessary_statements - unnecessary_string_escapes - unnecessary_string_interpolations - unnecessary_this + - unnecessary_this_alias - unnecessary_to_list_in_spreads + - unnecessary_type_name_in_constructor - unnecessary_unawaited - unnecessary_underscores - unreachable_from_main @@ -215,6 +227,7 @@ linter: - unsafe_variance - use_build_context_synchronously - use_colored_box + - use_declaring_parameters - use_decorated_box - use_enums - use_full_hex_values_for_flutter_colors @@ -235,4 +248,5 @@ linter: - use_to_and_as_if_applicable - use_truncating_division - valid_regexps + - var_with_no_type_annotation - void_checks diff --git a/analysis_options.yaml b/analysis_options.yaml index 0ba28266..197691d6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -39,6 +39,14 @@ linter: # Single quotes are easier to type and don't compromise on readability. prefer_double_quotes: false + # Conflicts with `unnecessary_await_in_return`, which stays enabled. + # The `await` only matters inside a `try`, and no site returns one there. + async_return_with_no_await: false + + # Enabled once the packages move to `material_ui`; until then it fires on + # every `package:flutter/material.dart` import in the repo. + migrate_design_widgets: false + # Conflicts with `omit_local_variable_types` and other rules. # As per Dart guidelines, we want to avoid unnecessary types to make the code # more readable. diff --git a/apps/design_system_gallery/lib/config/theme_configuration.dart b/apps/design_system_gallery/lib/config/theme_configuration.dart index 733359e7..3e162514 100644 --- a/apps/design_system_gallery/lib/config/theme_configuration.dart +++ b/apps/design_system_gallery/lib/config/theme_configuration.dart @@ -7,8 +7,8 @@ import 'package:stream_core_flutter/core.dart'; /// exact naming conventions from [StreamColorScheme]. class ThemeConfiguration extends ChangeNotifier { ThemeConfiguration({ - Brightness brightness = Brightness.light, - }) : _brightness = brightness { + this._brightness = Brightness.light, + }) { _rebuildTheme(); } diff --git a/apps/design_system_gallery/pubspec.yaml b/apps/design_system_gallery/pubspec.yaml index c3fb8d38..d9af0fac 100644 --- a/apps/design_system_gallery/pubspec.yaml +++ b/apps/design_system_gallery/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.10.0 - flutter: ">=3.38.1" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: device_frame_plus: ^1.0.0 diff --git a/melos.yaml b/melos.yaml index 17b97ecb..adda26e6 100644 --- a/melos.yaml +++ b/melos.yaml @@ -12,9 +12,9 @@ command: bootstrap: # Dart and Flutter environment used in the project. environment: - sdk: ^3.10.0 + sdk: ^3.12.0 # We are not using carat '^' syntax here because flutter don't follow semantic versioning. - flutter: ">=3.38.1" + flutter: ">=3.44.0" # List of all the dependencies used in the project. dependencies: diff --git a/packages/stream_core/CHANGELOG.md b/packages/stream_core/CHANGELOG.md index 7e039175..3a90f784 100644 --- a/packages/stream_core/CHANGELOG.md +++ b/packages/stream_core/CHANGELOG.md @@ -4,6 +4,10 @@ - Added `teams` field to `User` class. +### 🔄 Changed + +- Raised the minimum Dart SDK to `^3.12.0`. + ## 0.4.0 ### 💥 BREAKING CHANGES diff --git a/packages/stream_core/lib/src/api/stream_core_dio_error.dart b/packages/stream_core/lib/src/api/stream_core_dio_error.dart index fb0d3455..1767ea83 100644 --- a/packages/stream_core/lib/src/api/stream_core_dio_error.dart +++ b/packages/stream_core/lib/src/api/stream_core_dio_error.dart @@ -25,7 +25,7 @@ extension StreamDioExceptionExtension on DioException { final apiErrorResult = runSafelySync( () => switch (response?.data) { final Map data => StreamApiError.fromJson(data), - final String data => StreamApiError.fromJson(jsonDecode(data)), + final String data => StreamApiError.fromJson(jsonDecode(data) as Map), _ => null, }, ); diff --git a/packages/stream_core/lib/src/api/system_environment_manager.dart b/packages/stream_core/lib/src/api/system_environment_manager.dart index 086e497c..96986bdc 100644 --- a/packages/stream_core/lib/src/api/system_environment_manager.dart +++ b/packages/stream_core/lib/src/api/system_environment_manager.dart @@ -8,8 +8,8 @@ import 'system_environment.dart'; class SystemEnvironmentManager { /// {@macro systemEnvironmentManager} SystemEnvironmentManager({ - required SystemEnvironment environment, - }) : _environment = environment; + required this._environment, + }); /// Returns the Stream client user agent string based on the current /// [environment] value. diff --git a/packages/stream_core/lib/src/attachment/uploader/attachment_uploader.dart b/packages/stream_core/lib/src/attachment/uploader/attachment_uploader.dart index a6603068..a02b1182 100644 --- a/packages/stream_core/lib/src/attachment/uploader/attachment_uploader.dart +++ b/packages/stream_core/lib/src/attachment/uploader/attachment_uploader.dart @@ -53,8 +53,8 @@ class AttachmentUploadException implements Exception { class StreamAttachmentUploader { /// Creates a [StreamAttachmentUploader] with the specified [cdn] client. const StreamAttachmentUploader({ - required CdnClient cdn, - }) : _cdn = cdn; + required this._cdn, + }); // The CDN client used for upload operations. final CdnClient _cdn; diff --git a/packages/stream_core/lib/src/user/token_manager.dart b/packages/stream_core/lib/src/user/token_manager.dart index 288f0d4f..aa25ac07 100644 --- a/packages/stream_core/lib/src/user/token_manager.dart +++ b/packages/stream_core/lib/src/user/token_manager.dart @@ -32,8 +32,8 @@ class TokenManager { /// The [tokenProvider] is used to load tokens when needed. TokenManager({ required this.userId, - required TokenProvider tokenProvider, - }) : _tokenProvider = tokenProvider; + required this._tokenProvider, + }); /// The unique identifier of the user whose tokens are managed. final String userId; diff --git a/packages/stream_core/lib/src/ws/client/engine/stream_web_socket_engine.dart b/packages/stream_core/lib/src/ws/client/engine/stream_web_socket_engine.dart index 7a705969..f702ff6f 100644 --- a/packages/stream_core/lib/src/ws/client/engine/stream_web_socket_engine.dart +++ b/packages/stream_core/lib/src/ws/client/engine/stream_web_socket_engine.dart @@ -34,11 +34,9 @@ class StreamWebSocketEngine implements WebSocketEngine { /// Creates a new instance of [StreamWebSocketEngine]. StreamWebSocketEngine({ WebSocketProvider? wsProvider, - WebSocketEngineListener? listener, - required WebSocketMessageCodec messageCodec, - }) : _wsProvider = wsProvider ?? _createWebSocket, - _messageCodec = messageCodec, - _listener = listener; + this._listener, + required this._messageCodec, + }) : _wsProvider = wsProvider ?? _createWebSocket; final WebSocketProvider _wsProvider; final WebSocketMessageCodec _messageCodec; diff --git a/packages/stream_core/lib/src/ws/client/reconnect/connection_recovery_handler.dart b/packages/stream_core/lib/src/ws/client/reconnect/connection_recovery_handler.dart index eb89c0dd..55c4a0af 100644 --- a/packages/stream_core/lib/src/ws/client/reconnect/connection_recovery_handler.dart +++ b/packages/stream_core/lib/src/ws/client/reconnect/connection_recovery_handler.dart @@ -39,12 +39,11 @@ class ConnectionRecoveryHandler extends Disposable { required StreamWebSocketClient client, NetworkStateProvider? networkStateProvider, LifecycleStateProvider? lifecycleStateProvider, - bool keepConnectionAliveInBackground = false, + this._keepConnectionAliveInBackground = false, List? policies, RetryStrategy? retryStrategy, }) : _client = client, _reconnectStrategy = retryStrategy ?? RetryStrategy(), - _keepConnectionAliveInBackground = keepConnectionAliveInBackground, _policies = [ ...?policies, WebSocketAutomaticReconnectionPolicy( diff --git a/packages/stream_core/lib/src/ws/client/web_socket_health_monitor.dart b/packages/stream_core/lib/src/ws/client/web_socket_health_monitor.dart index 940eb7b8..f4299c32 100644 --- a/packages/stream_core/lib/src/ws/client/web_socket_health_monitor.dart +++ b/packages/stream_core/lib/src/ws/client/web_socket_health_monitor.dart @@ -41,10 +41,10 @@ abstract interface class WebSocketHealthListener { class WebSocketHealthMonitor { /// Creates a new instance of [WebSocketHealthMonitor]. WebSocketHealthMonitor({ - required WebSocketHealthListener listener, + required this._listener, this.pingInterval = const Duration(seconds: 25), this.timeoutThreshold = const Duration(seconds: 3), - }) : _listener = listener; + }); /// The interval between ping requests for health checking. final Duration pingInterval; diff --git a/packages/stream_core/pubspec.yaml b/packages/stream_core/pubspec.yaml index aaf62940..c28d1a61 100644 --- a/packages/stream_core/pubspec.yaml +++ b/packages/stream_core/pubspec.yaml @@ -16,7 +16,7 @@ repository: https://github.com/GetStream/stream-core-flutter # 2. Add it to the melos.yaml file for future updates. environment: - sdk: ^3.10.0 + sdk: ^3.12.0 dependencies: collection: ^1.19.0 diff --git a/packages/stream_core_flutter/CHANGELOG.md b/packages/stream_core_flutter/CHANGELOG.md index 88ef9c01..a5cd3c8a 100644 --- a/packages/stream_core_flutter/CHANGELOG.md +++ b/packages/stream_core_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +### 🔄 Changed + +- Raised the minimum Flutter version to `>=3.44.0` and the Dart SDK to `^3.12.0`. + ## 0.5.0 ### ✨ Features diff --git a/packages/stream_core_flutter/lib/src/components/common/stream_intrinsic_flex.dart b/packages/stream_core_flutter/lib/src/components/common/stream_intrinsic_flex.dart index 95f30402..1b9e9905 100644 --- a/packages/stream_core_flutter/lib/src/components/common/stream_intrinsic_flex.dart +++ b/packages/stream_core_flutter/lib/src/components/common/stream_intrinsic_flex.dart @@ -494,28 +494,20 @@ class _RenderStreamIntrinsicFlex extends RenderBox ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { _RenderStreamIntrinsicFlex({ - required Axis direction, - required MainAxisAlignment mainAxisAlignment, - required MainAxisSize mainAxisSize, - required double spacing, + required this._direction, + required this._mainAxisAlignment, + required this._mainAxisSize, + required this._spacing, required CrossAxisAlignment crossAxisAlignment, - required TextBaseline? textBaseline, - required TextDirection? textDirection, - required VerticalDirection verticalDirection, - required Clip clipBehavior, + required this._textBaseline, + required this._textDirection, + required this._verticalDirection, + required this._clipBehavior, }) : assert( crossAxisAlignment != CrossAxisAlignment.stretch, 'StreamIntrinsicFlex does not support $crossAxisAlignment.', ), - _direction = direction, - _mainAxisAlignment = mainAxisAlignment, - _mainAxisSize = mainAxisSize, - _spacing = spacing, - _crossAxisAlignment = crossAxisAlignment, - _textBaseline = textBaseline, - _textDirection = textDirection, - _verticalDirection = verticalDirection, - _clipBehavior = clipBehavior; + _crossAxisAlignment = crossAxisAlignment; Axis get direction => _direction; Axis _direction; diff --git a/packages/stream_core_flutter/lib/src/components/common/stream_safe_area.dart b/packages/stream_core_flutter/lib/src/components/common/stream_safe_area.dart index 59ec42bf..b19b0485 100644 --- a/packages/stream_core_flutter/lib/src/components/common/stream_safe_area.dart +++ b/packages/stream_core_flutter/lib/src/components/common/stream_safe_area.dart @@ -73,8 +73,8 @@ class StreamSafeArea extends StatelessWidget { /// clamped to `[0, 1]`. const StreamSafeArea.driven({ super.key, - required ValueListenable listenable, - EdgeInsets to = EdgeInsets.zero, + required ValueListenable this._listenable, + this._to = EdgeInsets.zero, this.left = true, this.top = true, this.right = true, @@ -83,8 +83,7 @@ class StreamSafeArea extends StatelessWidget { this.margin = EdgeInsets.zero, this.maintainBottomViewPadding = false, required this.child, - }) : _listenable = listenable, - _to = to; + }); /// Whether to avoid system intrusions on the left ([minimum] and [margin] apply either way). final bool left; diff --git a/packages/stream_core_flutter/lib/src/components/common/stream_tap_target_padding.dart b/packages/stream_core_flutter/lib/src/components/common/stream_tap_target_padding.dart index 29d65790..993552d6 100644 --- a/packages/stream_core_flutter/lib/src/components/common/stream_tap_target_padding.dart +++ b/packages/stream_core_flutter/lib/src/components/common/stream_tap_target_padding.dart @@ -84,14 +84,11 @@ class StreamTapTargetPadding extends SingleChildRenderObjectWidget { class _RenderTapTargetPadding extends RenderShiftedBox { _RenderTapTargetPadding({ - required Size minSize, - required AlignmentGeometry alignment, - required TextDirection? textDirection, + required this._minSize, + required this._alignment, + required this._textDirection, RenderBox? child, - }) : _minSize = minSize, - _alignment = alignment, - _textDirection = textDirection, - super(child); + }) : super(child); Size get minSize => _minSize; Size _minSize; diff --git a/packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart b/packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart index c972d6bd..2bf2717f 100644 --- a/packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart +++ b/packages/stream_core_flutter/lib/src/components/sheet/stream_sheet.dart @@ -656,9 +656,9 @@ class StreamSheetRoute extends PageRoute { super.requestFocus, required this.builder, this.backgroundColor, - Color? barrierColor, + this._barrierColor, this.barrierLabel, - String? barrierOnTapHint, + this._barrierOnTapHint, this.shape, this.borderRadius, this.constraints, @@ -672,8 +672,7 @@ class StreamSheetRoute extends PageRoute { this.onDragEnd, this.parentSheet, this.capturedThemes, - }) : _barrierColor = barrierColor, - _barrierOnTapHint = barrierOnTapHint; + }); /// Builds the primary contents of the sheet. The provided [ScrollController] /// should be attached to the topmost scrollable widget inside the sheet. diff --git a/packages/stream_core_flutter/lib/src/factory/stream_component_factory.dart b/packages/stream_core_flutter/lib/src/factory/stream_component_factory.dart index b332a687..6c215d19 100644 --- a/packages/stream_core_flutter/lib/src/factory/stream_component_factory.dart +++ b/packages/stream_core_flutter/lib/src/factory/stream_component_factory.dart @@ -668,8 +668,8 @@ class StreamComponentBuilders with _$StreamComponentBuilders { final class StreamComponentBuilderExtension { /// Creates a builder extension for a component with Props type [T]. const StreamComponentBuilderExtension({ - required StreamComponentBuilder builder, - }) : _builder = builder; + required this._builder, + }); // The internal builder function that creates the widget from the context and props. final StreamComponentBuilder _builder; diff --git a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart index 3f8ad7d3..1308b05e 100644 --- a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart +++ b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_core/stream_core.dart' show Standard; import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; -import '../../theme/primitives/stream_colors.dart'; import '../primitives/internal/tokens/dark/stream_tokens.dart' as dark_tokens; import '../primitives/internal/tokens/light/stream_tokens.dart' as light_tokens; +import '../primitives/stream_colors.dart'; part 'stream_color_scheme.g.theme.dart'; diff --git a/packages/stream_core_flutter/pubspec.yaml b/packages/stream_core_flutter/pubspec.yaml index 7a0be962..968e8990 100644 --- a/packages/stream_core_flutter/pubspec.yaml +++ b/packages/stream_core_flutter/pubspec.yaml @@ -4,8 +4,8 @@ version: 0.5.0 homepage: https://github.com/GetStream/stream-core-flutter environment: - sdk: ^3.10.0 - flutter: ">=3.38.1" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: cached_network_image_ce: ^4.9.0 diff --git a/packages/stream_thumbnail/CHANGELOG.md b/packages/stream_thumbnail/CHANGELOG.md index 60732342..6f01f714 100644 --- a/packages/stream_thumbnail/CHANGELOG.md +++ b/packages/stream_thumbnail/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +### 🔄 Changed + +- Raised the minimum Flutter version to `>=3.44.0` and the Dart SDK to `^3.12.0`. + ## 0.1.0 * Initial release. diff --git a/packages/stream_thumbnail/example/pubspec.yaml b/packages/stream_thumbnail/example/pubspec.yaml index e0e6f9cf..8175fce8 100644 --- a/packages/stream_thumbnail/example/pubspec.yaml +++ b/packages/stream_thumbnail/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ^3.10.0 - flutter: ">=3.38.1" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: flutter: diff --git a/packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart b/packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart index ed0f8f39..6e42fb56 100644 --- a/packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart +++ b/packages/stream_thumbnail/lib/src/stream_thumbnail_method_channel.dart @@ -154,7 +154,7 @@ class MethodChannelStreamThumbnail extends StreamThumbnailPlatform { try { final result = await methodChannel.invokeMethod('files', reqMap); if (result != true) { - _resolveFuture(callId, result); + _resolveFuture(callId, result as Object); } } catch (_) { // Drop the pending completer so it doesn't linger in `_futures`. @@ -231,7 +231,7 @@ class MethodChannelStreamThumbnail extends StreamThumbnailPlatform { try { final result = await methodChannel.invokeMethod('data', reqMap); if (result != true) { - _resolveFuture(callId, result); + _resolveFuture(callId, result as Object); } } catch (_) { _futures.remove(callId); diff --git a/packages/stream_thumbnail/pubspec.yaml b/packages/stream_thumbnail/pubspec.yaml index 2cd583de..b2183f9c 100644 --- a/packages/stream_thumbnail/pubspec.yaml +++ b/packages/stream_thumbnail/pubspec.yaml @@ -5,8 +5,8 @@ homepage: https://github.com/GetStream/stream-core-flutter repository: https://github.com/GetStream/stream-core-flutter environment: - sdk: ^3.10.0 - flutter: ">=3.38.1" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: cross_file: ^0.3.4+2 diff --git a/pubspec.lock b/pubspec.lock index 1843812b..00115924 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -458,4 +458,4 @@ packages: source: hosted version: "2.2.3" sdks: - dart: ">=3.10.0 <4.0.0" + dart: ">=3.12.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6e9eabda..098769b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_core_flutter_workspace environment: - sdk: ^3.10.0 + sdk: ^3.12.0 dev_dependencies: code_builder: ^4.10.1