Skip to content

ci(shorebird): move code push to the Divine org and wire it into Codemagic - #7202

Merged
rabble merged 20 commits into
mainfrom
chore/shorebird-divine-org
Aug 18, 2026
Merged

rabble merged 20 commits into
mainfrom
chore/shorebird-divine-org

Conversation

@NotThatKindOfDrLiz

@NotThatKindOfDrLiz NotThatKindOfDrLiz commented Aug 12, 2026 •

Copy link
Copy Markdown
Member

Description

Moves Shorebird code push to an app owned by the Divine organization and wires it into Codemagic. Store-bound Android and iOS artifacts are now built by shorebird release, so future Play and App Store releases can receive signed Dart-only patches.

The previous Shorebird app belonged to a personal-account experiment that was never used by CI and had no releases. Nothing already shipped was patchable, so there is no release migration.

What changes

Artifact Build command Patchable
Play AAB shorebird release android Yes
App Store IPA shorebird release ios Yes
Zapstore/GitHub per-ABI APKs flutter build apk --split-per-abi No, by decision
macOS DMG flutter build macos No
  • Adds manual ios-patch and android-patch workflows.
  • Requires CONFIRM_PATCH=YES, main, an exact release version, and the full release commit SHA.
  • Prints the complete commit/Dart diff since the release and blocks native, asset, dependency, or Shorebird-config changes.
  • Publishes patches to staging; promotion to stable remains a separate manual action.
  • Signs releases with the public key and patches with the private key. Release jobs never materialize the private key.
  • Fails closed when Shorebird release state cannot be read or the target version already exists.
  • Keeps patch/release dart-defines in one mode-0600 JSON file so the AAB, APK, IPA, and patches cannot silently diverge.
  • Pins Shorebird CLI behavior to the validated 1.6.117 version.

Flutter 3.44.9

This PR deliberately moves every repository and CI workflow pin from Flutter 3.44.0 to 3.44.9. The move is forced by the Shorebird engine: the 3.44.0 build crashed at launch in the Dart async FFI path used by cupertino_http, while Shorebird 1.6.117 ships the validated 3.44.9 engine. A repo-wide parity test now prevents local, package-CI, GitHub, Codemagic, and Shorebird release pins from drifting apart.

Release and patch safety

  • Store release commands are pinned by tests so they cannot be swapped back to plain Flutter builds unnoticed.
  • A release preflight rejects every existing status for the same platform/version before the long build. If shorebird release succeeds and a later upload step fails, delete that failed Shorebird release or bump the store build number before retrying.
  • Patch workflows fetch full history when necessary and reject anything except a full 40-character release commit SHA.
  • Shorebird signing variables must be stored as single-line PEM values with literal \n separators; CI validates the decoded envelope.
  • auto_update remains enabled, so installed store builds contact Shorebird at launch with app/release/patch state. The DPA is in flight; store data-sharing answers must not be finalized until it is executed and privacy review confirms coverage for all intended users, including minor users.

Merge sequencing

Divine 1.0.20 has been submitted on the previous release path. The sequencing gate is satisfied: the first store build after this merges is intentionally the Shorebird cutover.

Codemagic prerequisite

The shorebird_credentials group must contain:

  • SHOREBIRD_TOKEN (secure)
  • SHOREBIRD_PATCH_PUBLIC_KEY
  • SHOREBIRD_PATCH_PRIVATE_KEY (secure)

Setup and PEM encoding are documented as step 10 in codemagic.yaml and in mobile/docs/SHOREBIRD_CODE_PUSH.md.

Verification

  • Two real Codemagic release builds completed on the Shorebird 3.44.9 engine: iOS and Android.
  • Hardware smoke tests passed on both platforms.
  • python3 -m unittest discover -s .github/scripts/tests — 26 passed after the final hardening update.
  • mobile/scripts/check_codemagic_groups.sh — all five referenced groups documented.
  • codemagic.yaml parses with YAML aliases enabled.
  • No 3.44.0 workflow/release pin remains.
  • Patch commands, release listing, staging promotion, and preview flags were verified against Shorebird 1.6.117.
  • All Codemagic release and patch validation runs pass, including the patch dry-run path.

Relates to #7200.

Out of scope

@github-actions

This comment has been minimized.

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing this specifically against shipping a TestFlight build this afternoon, because it sits directly on that path. The work looks careful — the flavor-detection fix, the deliberate Zapstore carve-out, and the FLUTTER_VERSION pinning rationale are all well reasoned. Two timing concerns, neither about the code itself.

1. This adds shorebird_credentials to four workflows — the exact shape that took Codemagic down this morning

main was broken for every workflow until an hour ago with:

Codemagic.yaml references to unknown variable group(s): supporters_credentials

That was #6999 adding a variable group to six workflows before the group existed in Codemagic. Group resolution is config-level validation, so it doesn't degrade the affected workflow — it rejects the entire file and every workflow fails before running a step. #7191 fixed it by removing the reference.

This PR adds - shorebird_credentials to four workflows, and step 9 of your own setup comment says the group has to be created by hand in the Codemagic console with a SHOREBIRD_TOKEN. If this merges before that group exists, main goes straight back to the same total outage — on the afternoon we want to cut a build.

Not a criticism of the PR — you documented the prerequisite, which #6999 didn't. It's a sequencing constraint: the group has to exist in Codemagic before this lands, not after. Same rule that would have prevented this morning.

2. It changes the command that produces the TestFlight artifact

flutter build ipa → shorebird release ios, and flutter build appbundle → shorebird release android.

That's correct for what the PR is for — code push only works on binaries the updater is linked into, and you're right that nothing shipped so far is patchable. But it means the first build after merge is simultaneously:

  • the first Codemagic run after a total config outage,
  • on a newly created Shorebird app in a new org,
  • through a build command that has never produced a Divine artifact,
  • with a toolchain that provisions its own Flutter.

If that build fails, we won't know which of the four it was, and we'd be debugging it against a release deadline with no recent green run to diff against — the last known-good Codemagic build predates the outage entirely.

Suggestion

Ship this afternoon's TestFlight on the current known-good path, then land this immediately after, so its first run is a normal build rather than a release-day one. The value here is over-the-air patching of future releases; nothing about waiting one build cycle costs that.

If you'd rather it go in today, the order is: create shorebird_credentials in Codemagic → confirm a green run on this branch → merge. Doing the first two out of order repeats the morning.

No blocking findings in the diff itself. @rabble is at lunch; flagging here so the sequencing decision is visible rather than discovered.

@github-actions

This comment has been minimized.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

Addressed the Shorebird review cleanup on the latest head:

  • shorebird_credentials already exists in the Divine Codemagic project with SHOREBIRD_TOKEN marked secret.
  • Added the App Store Connect integration to ios-patch, matching ios-build before xcode-project use-profiles.
  • Removed dead FLUTTER_VERSION vars from both patch workflows.
  • Added fast RELEASE_VERSION shape validation to the patch confirmation step.
  • Switched Shorebird release/patch commands to a shared generated --dart-define-from-file so release/patch define parity is structural instead of copy-pasted.
  • Rebased onto current origin/main and pushed with lease.

Local verification:

  • codemagic.yaml parses with YAML aliases enabled.
  • git diff --check origin/main..HEAD passes.
  • .github/scripts/tests/test_codemagic_android_build_number.py passes.
  • Pre-push hook reported no merge conflicts with main; no Dart files changed.

Remaining merge gate is the real Codemagic branch run for ios-build and android-build so we prove config resolution plus the Shorebird release path before merge. @rabble please re-review when you have a minute.

@github-actions

This comment has been minimized.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

Follow-up: refreshed GitHub checks on head 21405b8c3f are green, including Mobile CI, all test shards, analyze, format, generated files, semantic PR, mergeability, and the web preview build.

I do not have CM_API_TOKEN / CM_APP_ID available in this shell, so I cannot start the required Codemagic branch runs from here. The remaining pre-merge gate is still to run ios-build and android-build for chore/shorebird-divine-org in Codemagic.

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed this read-only and am routing findings rather than pushing: release automation and CI config are a protected path under PR_REVIEW.md, so a reviewer takeover isn't mine to do here. The engineering is careful — the flavor-detection fix, the Zapstore carve-out, the explicit --allow-*-diffs prohibition, and the fail-fast ordering in confirm_patch are all right, and the reasoning in the inline comments is unusually good. Two things I'd hold it on, both verified against the branch.

BLOCKING 1 — the new dart-define step will fail every store build

codemagic.yaml:170-177 aborts when any of seven vars is absent:

missing = names.reject { |name| ENV.key?(name) }
unless missing.empty?
  abort("missing Shorebird dart-define environment variables: #{missing.join(', ')}")
end

Two of the seven are SUPPORTERS_API_BASE_URL and FF_DIVINE_SUPPORTERS. #7191 (e84be55c38, merged yesterday) is titled "drop the supporters variable group that does not exist yet", and the groups: lists on this branch are zendesk_credentials, proofmode_credentials, shorebird_credentials — no supporters group. So both vars are unset and the step aborts.

It also contradicts a check running earlier in the same workflow: mobile/scripts/check_supporters_config.sh:47 says "An unset URL is fine on its own: the feature is inert" and exits 0. One step blesses that state, the next kills the build for it. Affects ios-build (660), android-build (771), ios-patch (864), android-patch (921).

Fix: ENV.fetch(name, '') for the two supporters vars — matching the empty-define semantics the existing --dart-define=X=$X lines already ship — and keep the hard abort for the Zendesk/proofmode vars that do come from real groups. Or define both in Codemagic first, which makes this a second merge-order prerequisite.

BLOCKING 2 — patches can ship from any branch

confirm_patch (:224-244) validates CONFIRM_PATCH == YES and that RELEASE_VERSION matches ^[0-9]+\.[0-9]+\.[0-9]+\+[0-9]+$. That is the entire gate. There is no branch check, and Shorebird builds the Dart source from whatever branch the Codemagic run selects.

SHOREBIRD_CODE_PUSH.md step 1 says patches ship from main; nothing enforces it. As written, anyone with Codemagic build-start permission can put arbitrary unreviewed Dart on every production install within about one app launch — no PR, no CI, no store review, and no artifact in GitHub recording what shipped.

Four lines at the top of confirm_patch:

if [ "$CM_BRANCH" != "main" ]; then
  echo "Refusing to patch: patches ship from main only (got '$CM_BRANCH')."
  exit 1
fi

That restores the "everything users run passed review and CI" invariant every other path in this repo holds.

SHOULD-FIX

Patch signing is available and not enabled (:355-360, :361-368). Verified against the installed CLI (Shorebird 1.6.67): release takes --public-key-path, patch takes --private-key-path/--public-key-path. Without them the updater accepts any patch Shorebird's servers deliver for app_id 73254661-…. This is the one control that bounds who can push executable code independently of account security — which is the PR's own central claim. Ordering matters: the public key is baked in at release time, so a release cut without it can never be retro-signed; deciding later costs a full store cycle.

Unpinned curl | bash (:152-156) installs from install/main onto a runner holding the Android keystore, the ASC key, and SHOREBIRD_TOKEN. AGENTS.md treats a movable ref as an unconditional failure for Dart dependencies; same hazard, now on the toolchain producing both store artifacts. Assert a known version after install, or pin a tag.

Patches always go to stable (:245-255, :361-368). --track defaults to stable; staging and beta exist. A TRACK input defaulting to staging would give "no rollback window" an actual pre-production step.

RELEASE_VERSION is interpolated into shell before it is validated (:239, and unquoted at :254/:366). Only escalates an already-privileged Codemagic user, but that user is one holding the keystore and ASC key.

The credential is still personal. #7200 is open; the app moved to the Divine org (mobile/shorebird.yaml:8), the authority to patch did not. Worth an explicit call on merging ahead of #7200 rather than letting the title imply it's settled.

New third-party call on every launch, undisclosed. auto_update defaults on, so every install will contact Shorebird at launch with app id and patch state. Nothing in the PR, the runbook, or the App Store data-collection answers covers it. Worth a conscious decision for a privacy-positioned app.

Secrets to a plaintext workspace file (:182-185). PROOFMODE_SIGNING_SERVER_TOKEN lands in build/shorebird/dart_defines.json. Blast radius is contained — /build/ is gitignored, no artifacts: glob or cache_paths covers it, publish_github_release globs only ipa/apk/dmg, and the step never prints the file, with the abort listing names not values. Still worth chmod 600 and mktemp -d outside build/.

No --no-confirm on the patch commands (:254, :366). Shorebird likely auto-detects CI; if it doesn't, the workflow hangs to the 60-minute cap. CONFIRM_PATCH is already the human gate, so it's defensively free.

NIT

  • build_apk (:194-202) still copy-pastes its defines while the AAB uses the JSON, so Play and sideload builds can silently diverge.
  • "Never swap those steps back" is doc-only. test_codemagic_android_build_number.py is the existing precedent for pinning a codemagic.yaml invariant by string match.

Clean

No secrets committed — the only literal is app_id, which Shorebird documents as non-secret. No .github/workflows/* changes, so no interaction with the CI-workflow ruleset. Triggers correct: all 8 workflows keep triggering: {events: []}, nothing auto-fires, no fork-secret exposure. submit_to_app_store: false, submit_as_draft: true unchanged. No disclosure-rule violations. Shorebird flag syntax verified against the real CLI — --flutter-version, --dart-define-from-file, --release-version, --export-options-plist are all first-class and need no -- separator, contradicting the general docs. A bad SHOREBIRD_TOKEN fails loudly, not silently.

Could not verify

  • Whether shorebird_credentials exists in Codemagic with SHOREBIRD_TOKEN secret. An undefined group is config-level: Codemagic rejects the whole file and all 8 workflows fail before step 1. Your assertion in the comments is the merge gate here.
  • Whether the two supporters vars were added as project-level vars since #7191. If they were, BLOCKING 1 evaporates — worth 30 seconds in the console before acting on it.
  • Whether app_id 73254661-… is genuinely Divine-org-owned, and the org's member list. Needs Shorebird console access.
  • Rollback semantics for a bad patch. The PR says "no rollback"; I did not confirm whether Shorebird supports patch deactivation vs roll-forward-only, and that distinction changes how bad the worst case is.
  • No build was run. All static reading plus local shorebird --help; the branch run you asked for is the first real proof.

@rabble

rabble commented Aug 13, 2026

Copy link
Copy Markdown
Member

Ran an automated review pass over this branch. These findings have not been human-verified — please check them before acting. I deliberately skipped everything already covered in the existing reviews (supporters dart-defines, branch gate on patches, patch signing keys, unpinned curl | bash, the stable track default, unquoted RELEASE_VERSION). The Shorebird flags, the flavor-detection fix, the Zapstore carve-out and the fail-fast confirm_patch ordering all check out against the CLI source.

Three things left:

1. shorebird release makes the store pipeline non-idempotent — codemagic.yaml:358

shorebird release refuses to re-release a version whose platform status is already active, but BUILD_NUMBER is derived from App Store Connect / Play — not from Shorebird's release history.

ios-build run #1: ASC latest = 247 → BUILD_NUMBER=248; shorebird release ios succeeds and marks 1.0.9+248 active. A later step fails — upload_dsyms_to_crashlytics (:373-425) has three separate exit 1 paths — so the publishing: block is skipped and nothing reaches TestFlight. Run #2: ASC latest is still 247, so BUILD_NUMBER=248 again. shorebird release ios builds the whole IPA (~40 min), then ensureVersionIsReleasable → ensureReleaseIsNotActive throws "It looks like you have an existing ios release for version 1.0.9+248."

The pipeline can't produce a store artifact until someone deletes the release in the Shorebird console or bumps the pubspec version — and because the check runs after the build, each attempt burns a full build against the 60-minute cap. Same applies at :222 whenever the Play floor exceeds PROJECT_BUILD_NUMBER. This failure mode didn't exist before the PR (flutter build ipa is idempotent) and isn't mentioned in SHOREBIRD_CODE_PUSH.md.

2. Patches ship everything merged since the release — codemagic.yaml:366

The patch workflows take RELEASE_VERSION but nothing pins the source commit, so shorebird patch ios compiles the current checkout.

Release 1.0.9+247 is cut from commit A and promoted. Two weeks of unrelated Dart work merges. A crash fix lands; operator runs ios-patch with RELEASE_VERSION=1.0.9+247. The patch delivered to every production install of 1.0.9+247 now contains two weeks of Dart that was never reviewed as a release, never went through store review, and has no rollback — within about one app launch. Shorebird's native/asset-diff guard doesn't catch this; pure-Dart drift is exactly what it permits.

This is distinct from the branch gate raised in review — CM_BRANCH != main → exit 1 wouldn't prevent it, because main itself is ahead of the release. SHOREBIRD_CODE_PUSH.md:84-85 acknowledges "a patch is built from the current checkout" but frames it only as "the fix must be merged first" and never warns that everything else merged since ships too.

A RELEASE_COMMIT/tag input checked out before the patch step — or a step asserting git merge-base --is-ancestor plus an explicit diff summary against the release commit — would close it.

3. $HOME/.shorebird isn't cached — codemagic.yaml:645 (nit)

shorebird_install (:152-156) plus --flutter-version=3.44.0 (which differs from the version the CLI bundles) forces a full Flutter revision install under $HOME/.shorebird on every run — locally that's 1.0 GB, 671 MB of it the Flutter clone. None of the four affected workflows caches it: ios-build caches only CocoaPods and .pub-cache (:645-647), android-build only .gradle/caches and .pub-cache (:759-761), the two patch workflows mirror them (:849, :910).

Every iOS release build pays a fresh ~1 GB clone on top of an already ~40-minute build against max_build_duration: 60. Adding - $HOME/.shorebird to those four cache_paths lists is the whole fix.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

Rabble, I pushed the Shorebird hardening follow-up at 56cd204. GitHub checks are green now.

Can you confirm the Codemagic validation can proceed on this branch? The run needs the new shorebird_credentials group/vars to be present before starting ios-build and android-build:

  • SHOREBIRD_TOKEN
  • SHOREBIRD_PATCH_PUBLIC_KEY
  • SHOREBIRD_PATCH_PRIVATE_KEY

I could not start those Codemagic runs locally because CM_API_TOKEN / CM_APP_ID are not available in my shell.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

Update: Liz confirmed the Shorebird credentials are already present in Codemagic. So the remaining ask is just to confirm/start the Codemagic branch validation for ios-build and android-build on this PR head.

@NotThatKindOfDrLiz
NotThatKindOfDrLiz force-pushed the chore/shorebird-divine-org branch from 56cd204 to 9a34f8e Compare August 13, 2026 19:56
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@NotThatKindOfDrLiz
NotThatKindOfDrLiz marked this pull request as draft August 13, 2026 21:48
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@NotThatKindOfDrLiz
NotThatKindOfDrLiz force-pushed the chore/shorebird-divine-org branch from de472d5 to 7608641 Compare August 14, 2026 21:05

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against PR_REVIEW.md. Draft, so this is feedback-only — no changes pushed.

The gating design is genuinely good and I want to say so first. I parsed codemagic.yaml at head with Ruby YAML and confirmed anchors resolve: ios-patch/android-patch both have triggering: {events: []}, CONFIRM_PATCH defaulting to "NO", and Confirm patch to production → Verify patch source as scripts 1 and 2. There is no path to an automatic patch on merge to main — three independent gates plus --track=staging, so even a successful patch isn't live until a separate manual promote. No secret or credential is hardcoded; app_id is the only identifier and its own header says it's not secret. The private key doesn't reach any artifact glob and gets chmod 0600.

Two blocking problems, both verified against live sources rather than inferred.

Blocking

1. codemagic.yaml:801,923,377,493 — Shorebird has no Flutter 3.44.9

git ls-remote --tags https://github.com/shorebirdtech/flutter.git returns 3.44.0 – 3.44.6 only. flutter/flutter goes to 3.44.9. So shorebird release android/ios --flutter-version=3.44.9 fails at both store workflows — this PR, whose purpose is to make store artifacts patchable, cannot produce a store artifact at all.

The version it removes (3.44.0) is supported. Highest usable today is 3.44.6.

Worse: test_shorebird_flutter_version_uses_current_supported_engine asserts assertNotIn("3.44.0", ...), so the test actively blocks reverting to a known-supported pin.

2. codemagic.yaml:297,341 — the duplicate-release preflight is dead code that fails open

shorebird releases has exactly one subcommand, get-apks. I ran the exact command in the YAML:

$ shorebird releases info --release-version=… --json
exit 64: Could not find an option named "--release-version"

So the if always takes the else branch and prints No existing Shorebird release found; continuing. Two things hide it: the usage error goes to stdout, so it lands in release_info.json and release_info.err is empty; and neither file is ever printed.

Net effect: re-running a release against an existing active version proceeds silently — the exact failure the step exists to prevent. test_shorebird_release_commands_are_signed_and_preflighted asserts the string "already exists and is active" is in the YAML, which can never be reached at runtime, so it passes on unreachable code.

Separately from the missing subcommand, the structure is wrong: any nonzero exit (auth failure, network, rate limit, CLI drift) is treated as "no such release".

3. SDK pin drift — codemagic.yaml moves to 3.44.9 in all ten workflows (including macos-build, e2e-smoke-ios, e2e-smoke-android, which have nothing to do with Shorebird) while mobile/mise.toml:2 and mobile_ci.yaml:78,263,287,313 stay on 3.44.0. Every analyze/test/golden gate validates one SDK; every shipped artifact is built on another; no CI job covers the difference. Whatever version #1 settles on, bump all three in the same commit — and consider splitting the SDK bump out of the Shorebird change.

4. SHOREBIRD_CODE_PUSH.md — two runbook commands don't exist. shorebird releases list isn't a subcommand (only get-apks), and shorebird patches set-track isn't (shorebird patches has only promote). An operator mid-OTA follows step 2, gets a usage error, and has no documented way to find the release version. shorebird patches promote --release-version <v> --patch-number <n> is correct — verified against --help, both flags mandatory.

Non-blocking

  • codemagic.yaml:196-210 — write_shorebird_signing_keys hard-requires both PEMs and writes the private key to disk in ios-build/android-build, which only pass --public-key-path. Those jobs also hold GITHUB_TOKEN, the Play service account and the ASC key. Split into public-only for releases, private-only for patches.
  • codemagic.yaml:157 — curl … install.sh | bash off shorebirdtech/install@main, unpinned and unchecksummed, in the same job that materializes the patch-signing private key. The CLI version is therefore unpinned too, so patch behaviour can change with no commit here.
  • codemagic.yaml:226-260 — nothing cross-checks RELEASE_COMMIT against RELEASE_VERSION. A commit newer than the one that cut the release makes the blocklist scan a near-empty range and pass vacuously. (Too-old is safe — it over-scans.) Shorebird's own native-diff detection is still the backstop, but the guard reads stronger than it is.
  • codemagic.yaml:411,502 — shorebird patch isn't passed --no-confirm, which the help describes as intended for scripts. If it doesn't auto-skip on a non-TTY runner, the job blocks until max_build_duration: 60.
  • codemagic.yaml:228 — only git fetch --quiet origin main before git cat-file -e. A shallow clone makes a legitimate release commit report "not a commit in this checkout".
  • codemagic.yaml:29-36 — the setup docs don't state the expected encoding. gsub('\\n', "\n") un-escapes literal \n; a base64 or genuinely multiline secure variable yields an invalid PEM with no diagnostic.
  • caption_generator/android/build.gradle.kts:52 — add("debugCompileOnly", …) drops the Flutter embedding jar from the release compile classpath, so a standalone ./gradlew :caption_generator:assembleRelease no longer compiles. Nothing pins this; the only signal is a Codemagic release build, which PR CI doesn't run.
  • test_codemagic_android_build_number.py:111 — assertLess(android_patch, macos_build) pins the definition order of two workflows in the file. No behaviour breaks if that order changes; it can't fail for a reason anyone cares about. Drop it.
  • Same file — every assertion is a substring grep. Nothing asserts the file is valid YAML or that anchors resolve, which is the cheapest, highest-value check on a +505-line YAML change.

Verified

Ran python3 -m unittest discover -s .github/scripts/tests against the PR's YAML — 11 OK. Wiring those tests into CI as ci-config-tests is a real gain; they never ran before. Confirmed the Shorebird CLI surface against locally installed 1.6.67 (--flutter-version, --track, --allow-native-diffs, --no-confirm, patches promote all valid; releases info and patches set-track not). Confirmed Android build-number logic is unchanged in substance and cannot regress below the Play floor. Confirmed mobile/shorebird.yaml is in assets: and correctly in the patch blocklist. Confirmed no prior main build used shorebird release, so changing app_id orphans no existing patchable install.

Uncertain

Whether the latest (unpinned) CLI adds releases info or 3.44.7–3.44.9 support — my CLI evidence is 1.6.67, but the Flutter-version finding is independent of CLI version, it's a live tag query. Also unverified: whether SUPPORTERS_API_BASE_URL / FF_DIVINE_SUPPORTERS resolve identically in patch vs release workflows — they're in no group named in codemagic.yaml. If they're group-scoped rather than project-scoped, a patch ships different dart-defines than its release, which is the silent-repointing failure the doc warns about for DEFAULT_ENV. Worth confirming where those two are defined.

@rabble
rabble force-pushed the chore/shorebird-divine-org branch from 7608641 to 6756db4 Compare August 16, 2026 10:47
@rabble

rabble commented Aug 16, 2026

Copy link
Copy Markdown
Member

@NotThatKindOfDrLiz — I rebased this branch onto current main (9f3f13d751) and force-pushed with a lease. Rabble asked for the rebase specifically; I have not touched the draft status, requested review, or changed any of your intent. All checks green, watched to completion.

7608641e6b → 6756db4623. All 13 commits preserved, still authored by you.

Flagging this because three conflicts needed a judgment call about your intent, and if I misread one you're the person who'd spot it.

The conflicts — all the same shape

Every one was "main added a thing, you added a parallel thing." I kept both sides in each case rather than picking a winner:

1. codemagic.yaml — both added a checklist item numbered 9.
main added google_play_credentials as 9, plus a paragraph about the group-checklist invariant. You added shorebird_credentials as 9. I kept google_play_credentials at 9, renumbered yours to 10, and moved main's invariant paragraph to the end of the list where it governs all items rather than just item 9.

Worth knowing if you hadn't seen it: that paragraph landed after an undocumented group took the pipeline down for ~21 hours (#7203), and the guard it describes is exactly what requires shorebird_credentials to be documented. Your change and that invariant are complementary — yours is what keeps the new group legal.

2. .github/workflows/mobile_ci.yaml — both added a job.
main added codemagic-groups, you added ci-config-tests. Kept both job definitions, and both entries in the mobile-ci aggregator's needs:. Dropping either would let a red guard merge, since the required check is the aggregator.

3. .github/ci-timing-budgets.json — one budget entry each.
Kept both. Re-parsed the file to confirm it's still valid JSON.

Verified after rebasing

  • 13 commits present, authorship intact — checked explicitly rather than trusting "Successfully rebased"; a full disk on this machine has silently dropped commits mid-rebase before
  • check_codemagic_groups.sh → OK, 5 referenced groups all documented
  • Both codemagic.yaml and mobile_ci.yaml parse
  • python3 -m unittest discover -s .github/scripts/tests → 11 tests pass
  • CI green on the pushed head, including both conflict jobs — Codemagic Variable Groups and CI Configuration Tests each pass, which is what tells me the merge kept both sides working and not merely syntactically valid

One thing I did NOT change

ci-config-tests uses actions/checkout@v4; the other nine jobs in that file use @v7. That's worth aligning, but silently changing it inside a conflict resolution would smuggle an unrequested edit into a rebase, so I left it as you wrote it. Your call.

Not addressed

This was a rebase only — I haven't reviewed the Shorebird design, the patch-safety gates, or the Android Flutter-jar commits. Still a draft, still unreviewed.

Sequencing note: the plan is to ship 1.0.20 to the App Store first, then switch to Shorebird. So this shouldn't land until that submission is in — 1.0.20 will be the last unpatchable build, and Shorebird starts covering 1.0.21 onward.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

@rabble please rereview when you have a minute. Keeping this PR in draft intentionally so it cannot be merged accidentally.

Update on the Shorebird review items:

  • Verified real Codemagic Shorebird release builds on the PR branch before the rebase: iOS Build 6a7f9677cb986e08353e44fa published Shorebird release 1.0.20+841; Android Build 6a7f9ef5c8777b27a3bc3720 published Shorebird release 1.0.20+719.
  • Both builds installed Shorebird 1.6.117 and built with Flutter 3.44.9 (0cae2fc0a4), so the earlier 3.44.9 support concern is stale.
  • Pushed follow-up commit 1e7201c on current head to harden the duplicate-release preflight: it now lists releases via the 1.6.117 JSON envelope, fails closed on CLI/error/JSON-shape issues, and blocks active platform releases before the expensive build.
  • Added regression coverage for active-release blocking, error envelopes, YAML alias parsing, SDK pin parity, multiline shorebird patch flag detection, and workflow-local Shorebird cache assertions.
  • Aligned mobile/mise.toml, GitHub Mobile CI, and Codemagic Flutter pins to 3.44.9; also aligned ci-config-tests to actions/checkout@v7.
  • Fixed the caption_generator standalone release compile by using compileOnly for the Flutter embedding jar; verified :caption_generator:assembleRelease with the repo Gradle wrapper.

Local verification run:

  • python3 -m unittest discover -s .github/scripts/tests
  • .codex/scripts/test-config.sh
  • git diff --check
  • live Shorebird preflight check blocks active Android release 1.0.20+719 and allows a fake new iOS version
  • cd mobile/android && JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.12/libexec/openjdk.jdk/Contents/Home FLUTTER_ROOT=/Users/lizsw/flutter ./gradlew :caption_generator:assembleRelease

GitHub checks are green on current head 1e7201c, including Mobile CI, all test shards, Analyze, Generated Files, CI Configuration Tests, Codemagic Variable Groups, and Caption Generator CI.

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Shorebird wiring itself reads well — preflight before release, staging track on patches, signing keys written to disk rather than passed as args, patches refusing to run when the release commit has no Dart delta, and macOS explicitly excluded with a reason. The two new ios-patch / android-patch workflows are cleanly additive. The Ruby preflight and its pytest coverage are a nice touch for a CI-only change.

Two things below are not defects in the change; they're facts about when it can land. Flagging them because neither is visible from the title.

This converts the store release path itself, so it must not merge before 1.0.20 ships

ios-build and android-build are not left alone. They now consume the Shorebird anchors:

ios-build:  … - *write_shorebird_signing_keys
            … - *preflight_shorebird_ios_release
            … - *build_ios          # &build_ios → shorebird release ios --flutter-version=…
android-build: … - *build_aab       # &build_aab → shorebird release android --flutter-version=…

So the moment this is on main, the next App Store build is cut by shorebird release — built against Shorebird's engine fork rather than stock Flutter — instead of flutter build ipa / appbundle.

That is correct and unavoidable for Shorebird (a patch can only target a release shorebird release produced, as your own comment in the file says). It just directly inverts the plan of shipping 1.0.20 to the App Store before switching to Shorebird. If this merges first, 1.0.20 is the Shorebird cutover rather than the release preceding it.

I'm not asking for a change — the sequencing is Rabble's call. But it should be a decision rather than a side effect of merge order, so please hold this until 1.0.20 is submitted, or say explicitly that we're cutting over with it.

The repo-wide Flutter bump is doing a lot of work quietly

-flutter = "3.44.0"
+flutter = "3.44.9"

mise.toml is the pin every local dev, both git hooks, and CI resolve through, so this moves the toolchain for the whole repo inside a PR titled ci(shorebird). I assume it's forced — Shorebird only supports specific Flutter revisions, and --flutter-version=$FLUTTER_VERSION has to match one. If so, worth one line in the description saying that, because "CI change" and "everyone's compiler changes" read very differently to whoever approves it, and a patch-level Flutter bump landing days before a store submission is its own risk.

Smaller notes

shorebird.yaml app_id changes (5a90f08c… → 73254661…). That's the org move, so it's the point of the PR — just noting for the record that patches cut against the new app can't target releases made under the old id. Harmless now because nothing shipped on the old one; would not be harmless later.

shorebird_release_preflight.rb fails open on a shape it doesn't recognise. If the release exists but platform_statuses isn't a Hash, status stays nil and the script prints "continuing". Same if the version simply isn't found. For a duplicate-release guard that's a defensible default, but the failure mode is "preflight passed" rather than "preflight couldn't tell", which is the less obvious of the two. Consider distinguishing "no such release" (fine, continue) from "release found, statuses unreadable" (abort).

flutterEmbeddingJar → flutterDebugEmbeddingJar in caption_generator/android/build.gradle.kts is a rename with no path change — it still points at bin/cache/artifacts/engine/android-arm64/flutter.jar. If the new name is asserting that artifact is the debug engine, is that right for compileOnly on a release build? Genuine question, not a claim.

Not verified

I reviewed this statically. I have not run a Codemagic build from this branch, so I can't confirm the anchors resolve, the preflight parses real shorebird releases list --json output, or that 3.44.9 builds the app clean. The pytest additions cover the preflight logic but not the YAML wiring.

@NotThatKindOfDrLiz

Copy link
Copy Markdown
Member Author

@rabble Holding this PR (in draft so it doesn't accidentally get merged) until next release. Branch has been built on Codemagic for iOS and Android and both releases have been tested on real hardware. No issues found. Say the word and I'll flip the PR to review.

@NotThatKindOfDrLiz
NotThatKindOfDrLiz force-pushed the chore/shorebird-divine-org branch from 46a8b25 to a46851e Compare August 18, 2026 05:24
@github-actions

Copy link
Copy Markdown

Mobile PR Preview

Preview refreshed for a46851e

Last refresh: a46851e at 2026-08-18 05:28:44 UTC (preview run)

Property Value
Preview URL https://bcb2feaf.openvine-app.pages.dev
Pages project openvine-app
Preview branch pr-7202
PR branch chore/shorebird-divine-org
Commit a46851e

@rabble
rabble added this pull request to the merge queue Aug 18, 2026

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass over head 46a8b25. These findings have not been human-verified — check them before acting. I skipped everything already covered in the resolved threads.

What I actually ran: checked out the head in a scratch worktree and ran python3 -m unittest discover -s .github/scripts/tests → 26 passed, matching the description. All GitHub checks green, including the new CI Configuration Tests (11s). All 9 inline review threads resolved. I did not verify anything needing the Shorebird CLI or a Codemagic run — no shorebird releases list JSON shape, no --force installer behaviour, no real build.

Two things blocking merge that aren't code problems

1. @realmeylisdev's CHANGES_REQUESTED (Aug 17) is still on record — that's why mergeStateStatus is BLOCKED. Their blocker was the warm-cache install no-op. It reads as addressed by 46a8b25: shorebird_install now version-checks the cached binary, reinstalls with bash -s -- --force, and re-validates after, with test_shorebird_install_reuses_only_the_expected_cached_version pinning it. Their three smaller items (presence-vs-empty on dart-defines, the pin scan passing on a deleted pin, the runbook lines) also look fixed — the scan now flags an empty version set. @realmeylisdev, this needs a re-look rather than another fix.

2. This PR is what switches on the Shorebird phone-home for every store install. shorebird_code_push and auto_update: true both predate it, but the updater only runs in an engine built by shorebird release — so until now no store build ever contacted Shorebird. The description flags this and the doc's privacy section is genuinely good. It still means merging creates a new externally-visible data flow (install identifier, release/patch versions, platform, arch) for App Store and Play users before the DPA is executed. That's a decision to make explicitly, not a merge side effect.

Findings

RELEASE_COMMIT is unverified against RELEASE_VERSION, and fails open. The whole patch-safety story — print the diff since the release, block native/asset/dependency changes — rests on the operator typing the right commit. A too-old commit is safe (over-blocks). A too-new one (e.g. current main) makes BLOCKED_PATCH_PATHS empty and every gate in prepare_patch_source pass vacuously, while the patch still carries everything since the real release. Shorebird's own native/asset refusal is still the backstop, so this isn't catastrophic — but a gate advertised as a gate silently becomes a no-op. Cheap partial fix: cross-check the build name in git show $RELEASE_COMMIT:pubspec.yaml against the 1.0.20 half of RELEASE_VERSION. Better: have build_aab/build_ios push a tag at the release commit so the patch workflow resolves it instead of trusting input.

Patch dart-defines come from current env-group values, not the release's. The doc warns about DEFAULT_ENV ("the easiest thing to get wrong") but the same silent-drift risk applies to every other key in dart_defines.json — and those have no operator input at all, so nobody gets a chance to notice. FF_DIVINE_SUPPORTERS is read via bool.fromEnvironment in build_configuration.dart:52: flipping it in the Codemagic group means the next "Dart-only bug fix" patch also ships a feature flip to production. Same shape for PROOFMODE_SIGNING_SERVER_ENDPOINT/_TOKEN and SUPPORTERS_API_BASE_URL. Suggest widening that doc warning past DEFAULT_ENV, and ideally recording a SHA-256 of the release's dart_defines.json so the patch run can print a mismatch.

Zapstore APK and Play AAB are now built on different engines, undocumented. build_apk is stock Flutter 3.44.9; build_aab is Shorebird's 3.44.9 fork. This PR's own justification for the pin — the 3.44.0 Shorebird engine crashing in the cupertino_http async FFI path — is proof the delta is not theoretical. A Shorebird-engine-specific crash reproduces on Play and not on Zapstore, and vice versa. The doc table says the APKs aren't patchable but not that they're a different engine; that's the line a future triage will need.

curl … install.sh | bash from a mutable main now sits on the store-signing path. Before this PR, Shorebird wasn't installed in ios-build/android-build at all. The EXPECTED_SHOREBIRD_VERSION check validates identity, not integrity — a tampered installer reports whatever it likes. Pinning the URL to a commit SHA instead of main is cheap. Related: $HOME/.shorebird is now cached into both release workflows, so a poisoned cache would also pass the version check. Dropping the cache entirely costs a multi-GB engine re-download, so that's a real tradeoff rather than a free fix — but caching only ~/.shorebird/bin/cache/flutter while reinstalling the CLI binary each run gets most of the speed with less trust surface.

Nits

  • test_codemagic_android_build_number.py now covers Flutter pin parity, Shorebird gating, YAML parsing and runbook prose. The filename is actively misleading; rename it.
  • ci-config-tests goes into the results array rather than beside the explicit always-required codemagic-groups check. It works — a failure is caught in both the app-ci-required true and false branches — but it's inconsistent with the comment that exists specifically to explain why ungated always-required guards get their own check.
  • Pre-existing but sharper now: build_apk uses $PROJECT_BUILD_NUMBER while build_aab uses the Play-floor number, so the GitHub-release APK and the Shorebird release version from one run can differ. One line in the doc table would prevent a triage mistake.
  • caption_generator/android/build.gradle.kts: flutterRoot throws a clear GradleException when unset, but flutterDebugEmbeddingJar has no existence check — a missing jar degrades to an unresolved-reference compile error rather than a named failure.

What's good

The gating design holds up: triggering: {events: []} on both patch workflows, CONFIRM_PATCH defaulting to "NO" as script #1, --track=staging with assertNotIn("--track=stable") pinning it, and the public/private key split so release jobs never materialize the signing key (pinned by test_release_jobs_never_materialize_patch_private_key). The preflight fails closed on a bad envelope, a non-array data.releases, and a non-Hash platform_statuses — I read all seven of its tests and they assert the right things. Moving from a dozen --dart-define= args to a mode-0600 JSON file is also a quiet secret-hygiene win. Zero app-code changes in the diff.

Merged via the queue into main with commit 2504f4d Aug 18, 2026
80 checks passed
@rabble
rabble deleted the chore/shorebird-divine-org branch August 18, 2026 06:08

@rabble rabble left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes. This supersedes my APPROVED from earlier today — two items below need an owner decision before this ships, and neither is a code defect.

Automated review pass over head 46a8b25. These findings have not been human-verified — check them before acting. I skipped everything already covered in the resolved threads.

What I actually ran: checked out the head in a scratch worktree and ran python3 -m unittest discover -s .github/scripts/tests → 26 passed, matching the description. All GitHub checks green, including the new CI Configuration Tests (11s). All 9 inline review threads resolved. I did not verify anything needing the Shorebird CLI or a Codemagic run — no shorebird releases list JSON shape, no --force installer behaviour, no real build.

Two things blocking merge that aren't code problems

1. @realmeylisdev's CHANGES_REQUESTED (Aug 17) is still on record — that's why mergeStateStatus is BLOCKED. Their blocker was the warm-cache install no-op. It reads as addressed by 46a8b25: shorebird_install now version-checks the cached binary, reinstalls with bash -s -- --force, and re-validates after, with test_shorebird_install_reuses_only_the_expected_cached_version pinning it. Their three smaller items (presence-vs-empty on dart-defines, the pin scan passing on a deleted pin, the runbook lines) also look fixed — the scan now flags an empty version set. @realmeylisdev, this needs a re-look rather than another fix.

2. This PR is what switches on the Shorebird phone-home for every store install. shorebird_code_push and auto_update: true both predate it, but the updater only runs in an engine built by shorebird release — so until now no store build ever contacted Shorebird. The description flags this and the doc's privacy section is genuinely good. It still means merging creates a new externally-visible data flow (install identifier, release/patch versions, platform, arch) for App Store and Play users before the DPA is executed. That's a decision to make explicitly, not a merge side effect.

Findings

RELEASE_COMMIT is unverified against RELEASE_VERSION, and fails open. The whole patch-safety story — print the diff since the release, block native/asset/dependency changes — rests on the operator typing the right commit. A too-old commit is safe (over-blocks). A too-new one (e.g. current main) makes BLOCKED_PATCH_PATHS empty and every gate in prepare_patch_source pass vacuously, while the patch still carries everything since the real release. Shorebird's own native/asset refusal is still the backstop, so this isn't catastrophic — but a gate advertised as a gate silently becomes a no-op. Cheap partial fix: cross-check the build name in git show $RELEASE_COMMIT:pubspec.yaml against the 1.0.20 half of RELEASE_VERSION. Better: have build_aab/build_ios push a tag at the release commit so the patch workflow resolves it instead of trusting input.

Patch dart-defines come from current env-group values, not the release's. The doc warns about DEFAULT_ENV ("the easiest thing to get wrong") but the same silent-drift risk applies to every other key in dart_defines.json — and those have no operator input at all, so nobody gets a chance to notice. FF_DIVINE_SUPPORTERS is read via bool.fromEnvironment in build_configuration.dart:52: flipping it in the Codemagic group means the next "Dart-only bug fix" patch also ships a feature flip to production. Same shape for PROOFMODE_SIGNING_SERVER_ENDPOINT/_TOKEN and SUPPORTERS_API_BASE_URL. Suggest widening that doc warning past DEFAULT_ENV, and ideally recording a SHA-256 of the release's dart_defines.json so the patch run can print a mismatch.

Zapstore APK and Play AAB are now built on different engines, undocumented. build_apk is stock Flutter 3.44.9; build_aab is Shorebird's 3.44.9 fork. This PR's own justification for the pin — the 3.44.0 Shorebird engine crashing in the cupertino_http async FFI path — is proof the delta is not theoretical. A Shorebird-engine-specific crash reproduces on Play and not on Zapstore, and vice versa. The doc table says the APKs aren't patchable but not that they're a different engine; that's the line a future triage will need.

curl … install.sh | bash from a mutable main now sits on the store-signing path. Before this PR, Shorebird wasn't installed in ios-build/android-build at all. The EXPECTED_SHOREBIRD_VERSION check validates identity, not integrity — a tampered installer reports whatever it likes. Pinning the URL to a commit SHA instead of main is cheap. Related: $HOME/.shorebird is now cached into both release workflows, so a poisoned cache would also pass the version check. Dropping the cache entirely costs a multi-GB engine re-download, so that's a real tradeoff rather than a free fix — but caching only ~/.shorebird/bin/cache/flutter while reinstalling the CLI binary each run gets most of the speed with less trust surface.

Nits

  • test_codemagic_android_build_number.py now covers Flutter pin parity, Shorebird gating, YAML parsing and runbook prose. The filename is actively misleading; rename it.
  • ci-config-tests goes into the results array rather than beside the explicit always-required codemagic-groups check. It works — a failure is caught in both the app-ci-required true and false branches — but it's inconsistent with the comment that exists specifically to explain why ungated always-required guards get their own check.
  • Pre-existing but sharper now: build_apk uses $PROJECT_BUILD_NUMBER while build_aab uses the Play-floor number, so the GitHub-release APK and the Shorebird release version from one run can differ. One line in the doc table would prevent a triage mistake.
  • caption_generator/android/build.gradle.kts: flutterRoot throws a clear GradleException when unset, but flutterDebugEmbeddingJar has no existence check — a missing jar degrades to an unresolved-reference compile error rather than a named failure.

What's good

The gating design holds up: triggering: {events: []} on both patch workflows, CONFIRM_PATCH defaulting to "NO" as script #1, --track=staging with assertNotIn("--track=stable") pinning it, and the public/private key split so release jobs never materialize the signing key (pinned by test_release_jobs_never_materialize_patch_private_key). The preflight fails closed on a bad envelope, a non-array data.releases, and a non-Hash platform_statuses — I read all seven of its tests and they assert the right things. Moving from a dozen --dart-define= args to a mode-0600 JSON file is also a quiet secret-hygiene win. Zero app-code changes in the diff.

realmeylisdev pushed a commit that referenced this pull request Aug 18, 2026
The block said the Skia/Impeller profiling was taken on 3.44.0, "the
same pin this branch builds on". #7202 moved mise.toml, codemagic and
every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so
that clause was false and the surrounding "not a re-evaluation after an
engine upgrade" argument read stronger than the facts support.

States the pin move instead, and that the profiling predates it. Both
cited upstream issues are still closed, so the rest of the paragraph
stands. Comment-only: the comment-stripped element tree is unchanged.
realmeylisdev added a commit that referenced this pull request Aug 18, 2026
The block said the Skia/Impeller profiling was taken on 3.44.0, "the
same pin this branch builds on". #7202 moved mise.toml, codemagic and
every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so
that clause was false and the surrounding "not a re-evaluation after an
engine upgrade" argument read stronger than the facts support.

States the pin move instead, and that the profiling predates it. Both
cited upstream issues are still closed, so the rest of the paragraph
stands. Comment-only: the comment-stripped element tree is unchanged.
hm21 pushed a commit that referenced this pull request Aug 19, 2026
The block said the Skia/Impeller profiling was taken on 3.44.0, "the
same pin this branch builds on". #7202 moved mise.toml, codemagic and
every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so
that clause was false and the surrounding "not a re-evaluation after an
engine upgrade" argument read stronger than the facts support.

States the pin move instead, and that the profiling predates it. Both
cited upstream issues are still closed, so the rest of the paragraph
stands. Comment-only: the comment-stripped element tree is unchanged.
NotThatKindOfDrLiz pushed a commit to MartinMontero/divine-mobile that referenced this pull request Aug 24, 2026
…ivinevideo#7794)

* docs(android): record the measured field cost of the Impeller switch

The manifest justifies re-entering Impeller as a traded risk but never
says what the trade actually cost, so the next reader has no way to
judge it except by re-running the analysis. Field measurement across
the 1.0.17 -> 1.0.19 boundary puts it at +2.9 points of slow frames
(95% CI [+0.5, +5.3]), with iOS flat under the same estimator.

Also records why reverting is not the answer: the engine already logs
the opt-out as deprecated and slated for removal, so the feed has to be
fast on Impeller regardless of what the chroma-key preview is worth.

* docs(android): correct the stale Flutter pin in the Impeller comment

The block said the Skia/Impeller profiling was taken on 3.44.0, "the
same pin this branch builds on". divinevideo#7202 moved mise.toml, codemagic and
every workflow to 3.44.9 on 2026-08-18, before this branch was cut, so
that clause was false and the surrounding "not a re-evaluation after an
engine upgrade" argument read stronger than the facts support.

States the pin move instead, and that the profiling predates it. Both
cited upstream issues are still closed, so the rest of the paragraph
stands. Comment-only: the comment-stripped element tree is unchanged.

---------

Co-authored-by: Meylis <meylis@divine.video>
NotThatKindOfDrLiz pushed a commit that referenced this pull request Sep 8, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
NotThatKindOfDrLiz pushed a commit that referenced this pull request Sep 8, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
NotThatKindOfDrLiz pushed a commit that referenced this pull request Sep 8, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
NotThatKindOfDrLiz pushed a commit that referenced this pull request Sep 8, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
hm21 added a commit that referenced this pull request Sep 9, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
NotThatKindOfDrLiz pushed a commit that referenced this pull request Sep 9, 2026
Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in #7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.
SSMG64 pushed a commit to IBackupStuff/divine-mobile that referenced this pull request Oct 22, 2026
… breaks (divinevideo#8915)

* refactor(state): replace the freezed state models with plain Equatable classes

Dart 3.13 stopped accepting `final` on a constructor parameter, and that is
exactly what freezed 3 emits. The four generated files under lib/state/ no
longer compile:

  lib/state/video_feed_state.freezed.dart:234:35:
  Error: Can't have modifier 'final' here.

`flutter analyze` never saw it — `**/*.freezed.dart` is excluded in
analysis_options.yaml — so it only surfaced once dart format could not parse
the files.

freezed 4.0.0 fixes the generator, but it requires analyzer >= 13 and there is
no hive_ce_generator build for analyzer 13 (1.11.2 caps at ^12, 1.11.3 jumps to
^14). analyzer 14 is out of reach because flutter_test pins test_api 0.7.12,
which forces test 1.31.1, which requires analyzer < 14. Dropping package:test
is not an option either: 28 workspace packages use it, several of them pure
Dart, where flutter_test would add a Flutter runtime dependency the package
boundary guard rejects.

Since these are the only four freezed classes in the repo, and
.claude/rules/state_management.md prescribes Equatable for state anyway, they
are hand-written instead. That takes freezed out of the dependency graph
entirely rather than working around the version conflict.

One behavioural detail worth the extra file: freezed's copyWith clears a
nullable field when the caller passes an explicit null, which three call sites
rely on to reset `error` on refresh. A plain `error ?? this.error` would have
turned those into silent no-ops, so copy_with_sentinel.dart restores the
distinction between "argument omitted" and "argument set to null".

* fix(analysis): resolve the findings Dart 3.13 newly reports

The 3.13 analyzer adds two lints that the app trips 24 times, and CI runs
`flutter analyze` as a blocking job, so the upgrade cannot land without them.

`unawaited_return_in_try_block` (12 sites) is the one that mattered: a
`return someFuture();` inside a try does not route the future's failure
through the catch, so every one of these had a catch block that logs and
returns a fallback which could never fire. Adding the missing `await` makes
the handler cover what it reads as covering.

`unnecessary_unawaited` (11 sites) is the opposite direction — Flutter 3.47
annotated Navigator.push and Scrollable.ensureVisible with `@awaitNotRequired`,
so the `unawaited(...)` wrappers are now redundant. Two `dart:async` imports
became unused along with them. Plus one unused catch-stack variable and one
conditional expression the analyzer wants as an if-element.

The typedef move is a consequence of the same upgrade rather than a cleanup:
the 3.13 formatter wraps function-type typedefs differently, which grew
auth_service.dart and curated_list_service.dart by two lines each. Both are
frozen by check_service_god_file_ceiling.sh, which has no sanctioned raise
path, so the callback typedefs move to lib/models/ — where auth_service.dart
already re-exports auth_result, auth_state and their siblings from — and both
services are re-exported from there. No call site changes, and both files now
sit below their ceiling (4516/4536 and 1841/1847).

* chore(deps): pin the toolchain to Flutter 3.47.2

Moves every pin from Flutter 3.44.9 / Dart 3.12 to 3.47.2 / Dart 3.13: 71
workflow pins, 12 in codemagic.yaml, mise.toml, and the SDK constraints in
mobile/pubspec.yaml. The 3.44 line stopped receiving patches when 3.47 went
stable, so the repo was sitting on a frozen toolchain.

3.47.2 is the second patch on the line, twelve days old, and Shorebird
publishes a matching engine for it — the store builds pass FLUTTER_VERSION
straight to `shorebird release`, so a version Shorebird does not carry would
break them. The engine provenance recorded in SHOREBIRD_CODE_PUSH.md is
updated to the 3.47.2 engine.

Also folded in, because they were drifting or would have gone stale:

  - The 16 packages still declaring `flutter: ^3.41.1`, left behind by the
    previous bump, now match the app.
  - README, CONTRIBUTING and the dependabot comment still named ^3.44.0 while
    the real pin had moved to 3.44.9 back in divinevideo#7202.
  - The AndroidManifest comment explaining why Impeller was re-enabled dates
    its profiling against the pin it predates.
  - freezed and freezed_annotation are gone from pubspec.yaml — the state
    models no longer use them.

The regenerated l10n files and the analyzer excludes the 3.47 tool writes into
each analysis_options.yaml are committed so `flutter pub get` leaves a clean
tree, which the Generated Files check requires.

Android needs no native changes: compileSdk, targetSdk and the NDK version are
identical between 3.44.9 and 3.47.2, and AGP 8.11.1 / Gradle 8.14 / Kotlin
2.2.20 all sit inside the supported window.

* style: reformat for the Dart 3.13 formatter

Mechanical, no behaviour change. `dart format` in Dart 3.13 changes two
things, and CI's Format job fails on every file that still has the old shape:

  - A function-type typedef keeps its name and return type on one line and
    wraps the parameter list, instead of breaking after the `=`.
  - Import groups are separated by a blank line, so a `dart:` block followed
    directly by a `package:` block gains one.

Produced by `dart format lib test integration_test tools packages` on 3.47.2 —
nothing here was edited by hand.

* test(goldens): regenerate the two button references for the 3.47.2 engine

`divine_button_types` and `divine_button_sizes` failed by 103px and 73px —
0.11% and 0.10%. The isolated diffs contain nothing but faint arcs along the
rounded ends of the buttons, and the master and test images are
indistinguishable side by side: the 3.47.2 engine rasterizes those curves
slightly differently.

Nothing in this branch touches how the buttons draw. The only divine_ui
changes are the analyzer excludes the 3.47 tool writes into
analysis_options.yaml and the `flutter:` constraint in its pubspec.

Regenerated on the Ubuntu runner via the update_goldens workflow dispatch, not
locally — macOS Skia antialiases differently, so a local reference would fail
the very check it is meant to satisfy. `divine_snackbars` came back
byte-identical and is left alone.

* fix(state): preserve Freezed model contracts

* fix(release): align Shorebird with Flutter 3.47

* chore(deps): finish Flutter upgrade cleanup

* style: format rebased chroma key cubit

* style: format rebased workflow contract test

* style: format rebased support center test

* chore(analyzer): relock async safety baseline

* test(nostr_sdk): stabilize reconnect schedule coverage

* fix(state): stop unrelated state changes from rebuilding collection watchers

The handwritten replacements for the four Freezed state models hand out an
`UnmodifiableListView`/`SetView`/`MapView` on every read of a collection
field. Those views inherit identity `==` from `dart:collection`, so two reads
of a field that never changed do not compare equal, and a watcher that selects
one of them — `curationProvider.select((state) => state.editorsPicks)` is the
one in the tree today — is told the picks changed every time anything else in
that state changes, a loading flag included.

Freezed did not behave that way. It shipped `EqualUnmodifiableListView` and
its two siblings, whose `==` compares the wrapped source, precisely so a
selector could tell "replaced" from "same list, new wrapper". This restores
those three wrappers next to the state models that need them, so the
comparison a selector makes means what it did before the migration.

`copyWith` now also passes the raw field instead of the wrapped getter. It was
feeding each view back into the next constructor, so a state accumulated one
more layer of delegating view per `copyWith` for as long as it lived — on the
feed's video list, for the length of a session. `props` reads the raw fields
for the same reason; Equatable compares collections deeply either way, so
state equality is unchanged.

* test(state): make the collection contract tests able to fail

Four of the tests added with the Freezed migration built their fixtures from
`const` collection literals. A const collection is already unmodifiable, so
`expect(state.editorsPicks.clear, throwsUnsupportedError)` passed whether or
not the getter wrapped anything, and the pair of `const SeenVideosState`
values that was meant to prove "distinct instances compare deeply" was
canonicalised by the compiler into one instance. They could not fail for the
reason they claimed to check.

The fixtures now come from mutable collections, and four new tests cover the
equality half of the contract: two reads of the same field compare equal, a
`copyWith` that does not touch a collection keeps it equal, five of them in a
row still do, and replacing the collection makes it unequal.

* chore(baseline): relock the service size ceilings for the rebased tree

Rebasing onto main moved three of the five tracked services, and the branch's
Dart 3.13 reformatting removes a few more lines from each. Every entry is
lower than the one on main, so this only locks in the reduction.

* style: format rebased audio and thumbnail fixes

---------

Co-authored-by: Liz Sweigart <127434495+NotThatKindOfDrLiz@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants