Merged mobile PRs: swap the dead QRs for main's
Status#
Implemented. Was to be stacked on mobile-build-state (#2542), which merged
mid-flight — so this rides main and reuses its ensureBuildForPr /
installReady helpers. #2542's own merged body is the first real test
subject: it currently shows exactly the broken-QR state this fixes.
The problem#
Merging a mobile PR leaves its body actively lying:
- Cleanup (
mobile-pr-preview-cleanup.yml) deletes the PR's channel AND its QR image assets — so the body's QRs turn into broken images pointing at a channel that no longer exists. - The way onto latest main after testing a PR is hunting the merge commit's commit comment for main's QR section. Nobody does that from a phone.
Misha's ask, verbatim: "I'd like the qr codes to get collapsed on merge, and then when main's build completes a CI job should find the corresponding PR and put main's QR code(s) in the PR body — that will make it easier to get onto latest main without hunting for commit comments."
Design#
Three touches, all riding existing machinery (the managed
mobile-pr-preview body section via markdownAnnotator):
- On close (
cleanup-mobile-pr-preview.ts): after deleting channel/assets, rewrite the section. Merged → a placeholder: "merged — channel deleted; main's QRs land here when main's publish completes". Closed unmerged → "closed — preview channel deleted". Guard: never overwrite a section that is already main-flavored (the publisher may win the race with the close event). - On the merge push (
publish-mobile-update.ts): after publishing topreviewand ensuring main's install build, find the PR(s) the pushed commit belongs to (listPullRequestsAssociatedWithCommit), and for each MERGED one whose body carries the section, write the main-variant section — the same one commit comments get: OTA switch-to-main QR (usable immediately for JS-only merges) + main's install build (marked "build still running" when freshly triggered). - When the build completes (new
refresh-mobile-main-qr.ts, second workflow job): a native-change merge triggers main's build--no-wait, so step 2 writes a pending install link. This job polls the build until it finishes (bounded), then re-renders the same sections with the installable link. The publish job keeps the serializing concurrency group; this job runs outside it so a 20-minute build never queues later merges — concurrency moves from workflow-level to the publish job.
Checklist#
-
cleanup-mobile-pr-preview.ts: rewrite the body section on close (merged vs not), pure-planner tested, skip if already main-flavored -
publish-mobile-update.ts: find merged PRs for the pushed commit, write the main section into each body that has one — via a sharedsyncMainPreviewSectionin mobile-preview.ts that also owns the commit comment, so publisher and refresher write through one door -
refresh-mobile-main-qr.ts: poll main's install build to FINISHED, re-render the PR sections (and the commit comment) with the live link — bounded at 40m; a failed/canceled build logs loudly and leaves the pending link, whose page shows what happened -
mobile-eas-update.yml: concurrency to the publish job; add the refresh job (needs: publish,install_ready/build_idvia outputs; PR discovery re-derived from the sha rather than passed — idempotent) - Tests alongside the existing pure planners in
cleanup-mobile-pr-preview.test.ts - README: "Per-PR channels" lifecycle paragraph mentions the swap
Decisions made without asking#
- Step 2 writes immediately rather than waiting for the build: the OTA QR is
the common case (JS-only merges) and should not wait ~20 minutes for a
build only native merges need. The pending install link is honest
(
installReadyfrom #2542) and step 3 upgrades it. - Closed-unmerged PRs also get their section neutered — their QR images are deleted either way, and broken images are worse than a one-line note.
- Polling over EAS webhooks: no webhook infra exists, and a bounded poll in a non-serialized job is ~30 lines. Webhooks can replace it if polling ever bites.