fix(macos): guard the DMG architecture, name installers Intel / Apple-Silicon, and stop the startup Keychain prompt - #225
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
EtienneLescot
force-pushed
the
release/v1.8.0
branch
from
August 1, 2026 13:42
82d5e96 to
58ba61d
Compare
EtienneLescot
force-pushed
the
fix/macos-dmg-arch-guard
branch
2 times, most recently
from
August 1, 2026 13:52
92279b6 to
3fdfe43
Compare
…achine
`npx electron-builder --mac --${matrix.arch} --dir` does not restrict the
architecture: the `arch` list in electron-builder.json5's `mac.target` names
both x64 and arm64, and the config wins over the CLI flag. Every job therefore
produces both bundles — x64 in release/<ver>/mac/, arm64 in
release/<ver>/mac-arm64/ — and the step that picked one did
find "release/${VERSION}" -maxdepth 4 -name "*.app" | head -n1
which takes whichever directory comes first in readdir order. electron-builder
walks the target list in the order it is declared, so `mac/` (x64) is created
first and wins the race. The arm64 job could hand the x64 bundle to `hdiutil`
and publish it as `Openscreen-Mac-arm64-<ver>.dmg`. Nothing downstream ever
compared the name against the contents, so it would have shipped silently.
v1.8.0-rc.5 happens to be correct — its arm64 DMG really is arm64 — so this is
a latent defect, not an incident. Fixed by selecting the arch directory
explicitly, and guarded by a `lipo -archs` assertion that fails the job when
the bundle does not match `matrix.arch`. The guard is the part that matters:
selection can drift again, an assertion cannot pass while wrong.
The rename is the same defect seen from the user's side. `x64` reads to most
people as "the normal 64-bit one" and `arm64` as the exotic variant, which is
backwards on every Mac sold since 2020 — and choosing wrong is not a cosmetic
mistake: an Intel bundle on Apple Silicon runs the compositor, the encoder and
whisper.cpp under Rosetta 2, which is slow enough to make the app unusable.
The DMGs are now named `Openscreen-macOS-Apple-Silicon-<ver>.dmg` and
`Openscreen-macOS-Intel-<ver>.dmg`, matching what About This Mac shows.
update-homebrew-cask.yml needed one change to follow. Its "Find macOS DMG
assets" step already matched `apple[-_. ]?silicon` and `intel`, but the
"Wait for release DMG assets" step ahead of it compared exact filenames, so
after the rename it would have polled for its full 12-minute timeout and
warned, on a release whose assets were present the whole time. Both steps now
match on the same patterns and cannot disagree; old `-Mac-arm64-` /
`-Mac-x64-` names still match, so past releases keep working.
The headline table reads as a before/after and is not one. Its `WebCodecs in Chromium | 79 | removed — was the previous export path` row is the web pipeline *after* the Canvas2D compositor rebuild — an intermediate state of the 1.8.0 cycle that no release ever contained. "The previous export path" invites the reader to compare 126 against it and conclude ~1.6x. What users actually upgrade from is v1.7.0, which is the `webcodecs-legacy` arm: ~8 fps at M1, 9.8 fps under Gate G0. The tree settles it — `src/lib/exporter/frameRenderer.ts` has no `shadowCache` in v1.7.0 and carries it throughout by v1.8.0-rc.4, so the ~2x rebuild landed inside the 1.8.0 window, not before it. 1.8.0 therefore compounds two changes against what a user had, and the real delta is an order of magnitude rather than 1.6x. This was not hypothetical: the v1.8.0-rc.4 release notes were drafted with the 1.6x figure straight off this table before the tree was checked. The note also says not to quote a precise multiple across these runs, because the same arm on this machine has measured 44.0, 36.8, 32.3, 31.8, 22.2 and 11.9 fps across sessions — the magnitude survives that drift, an exact ratio does not.
EtienneLescot
force-pushed
the
fix/macos-dmg-arch-guard
branch
from
August 1, 2026 13:59
3fdfe43 to
3bcfa2b
Compare
`LlmConfigStore`'s constructor does two sync readFileSync plus a `safeStorage` decrypt. On macOS `safeStorage` is backed by a Keychain item, so building the store during `registerIpcHandlers` meant every single launch reached the Keychain — including for the majority of users who never open the AI layer and have no credentials stored at all. The store is now built on first use and memoised. Making the handlers-side binding lazy was not enough on its own: `registerNativeBridgeHandlers` resolved it eagerly with `llmConfig: context.getAiEditionLlmConfig()` while wiring the service, so the laziness had to be carried through to `AiEditionService`, whose option is now a factory rather than an instance. Every one of its thirteen uses already sat behind a method the renderer has to call first, so nothing else moved. The service memoises too — `llmGetSnapshot` alone reads the store once per provider definition, which would otherwise have called the factory nine times per snapshot. The single-instance guarantee the old comment argued for is preserved: the memoisation is what keeps a second store from racing the first, and the DocumentService rationale next to it (a per-instance save queue whose duplicate destroyed two real project files) is untouched and still eager. This does not fix the prompt repeating, only its appearing at startup. The item's ACL binds to a code signature, and an unsigned or ad-hoc-signed build has no stable identity for it to trust, so the prompt returns on every launch that reaches the store. Signing is the other half, and is not this function's business. Guarded by a test, because the failure mode is invisible: reintroducing the eager form breaks no behaviour, passes every other test, and shows up only as a Keychain prompt on a machine the author may not have.
It still called the compositor "Direct3D 11", listed MP4 export on macOS and Linux as unstarted, and framed "porting it off Windows" as the biggest open item — while v1.8.0-rc.5 was already publishing DMGs and Linux packages built on the Metal and WGSL backends. #18 (software encoder fallback) shipped with them too, as an automatically-selected CPU backend rather than the encoder flag the entry anticipated. Marked all three shipped, and replaced them with the two gaps that are actually open: Linux export is software-encoded (the capture helper has a hardware H.264 encoder, the export pipeline does not), and every performance number on record still comes from one passive-iGPU laptop, so nothing is measured on the hardware most users have.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four fixes found while investigating why the packaged macOS build was unusably slow on an Apple Silicon Mac. The first two are the direct cause and its guard; the last two are things the investigation surfaced.
1. The arm64 job could package the x64 bundle
npx electron-builder --mac --${{ matrix.arch }} --dirdoes not restrict the architecture — thearchlist inelectron-builder.json5'smac.targetnames bothx64andarm64, and the config wins over the CLI flag. Every job therefore produces both bundles (release/<ver>/mac/for x64,release/<ver>/mac-arm64/), and the step that picked one did:head -n1takes whichever directory comes first in readdir order, and electron-builder walks the target list in declaration order, somac/(x64) is created first and wins. The arm64 job could hand the x64 bundle tohdiutiland publish it asOpenscreen-Mac-arm64-<ver>.dmg.v1.8.0-rc.5happens to be correct — I verified its published arm64 DMG is genuinely arm64 (lipo -archs→arm64, helpers indarwin-arm64,Mach-O thin (arm64)). So this is a latent defect, not an incident.Fixed by selecting the arch directory explicitly, and guarded by a
lipo -archsassertion that fails the job when the bundle does not matchmatrix.arch. The guard is the part that matters: selection can drift again, an assertion cannot pass while wrong.2. The installers are named after the instruction set, not the machine
x64reads to most people as "the normal 64-bit one" andarm64as the exotic variant, which is backwards on every Mac sold since 2020. This is not cosmetic: an Intel bundle on Apple Silicon runs the compositor, the encoder and whisper.cpp under Rosetta 2, which is slow enough to make the app unusable — this PR exists because that happened to a maintainer, who then had to be told which of two similarly-named files to pick.3. Every launch prompted for Keychain access
LlmConfigStore's constructor does two syncreadFileSyncplus asafeStoragedecrypt, and on macOSsafeStorageis backed by a Keychain item. Building it insideregisterIpcHandlersmeant every launch reached the Keychain — including for the majority of users who never open the AI layer and have no credentials stored at all.The store is now built on first use and memoised. Making the handlers-side binding lazy was not enough on its own:
registerNativeBridgeHandlersresolved it eagerly withllmConfig: context.getAiEditionLlmConfig()while wiring the service, so the laziness is carried through toAiEditionService, whose option is now a factory rather than an instance. All thirteen of its uses already sat behind a method the renderer must call first, so nothing else moved. The single-instance guarantee the old comment argued for is preserved by the memoisation.This does not fix the prompt repeating, only its appearing at startup. The item's ACL binds to a code signature, and an unsigned or ad-hoc-signed build has no stable identity for it to trust. Signing is the other half of that, and is out of scope here.
4. The roadmap described a Windows-only engine
ROADMAP.mdstill called the compositor "Direct3D 11", listed MP4 export on macOS and Linux as unstarted, and framed "porting it off Windows" as the biggest open item — while rc.5 was already publishing DMGs and Linux packages built on the Metal and WGSL backends. #18 shipped with them, as an automatically-selected CPU backend rather than the encoder flag the entry anticipated.Marked shipped, and replaced with the two gaps that are actually open: Linux export is software-encoded, and every performance number on record comes from one passive-iGPU laptop.
Also in this area —
rendering-performance.md's headline table reads as a before/after and is not one. ItsWebCodecs in Chromium | 79 | removed — was the previous export pathrow is the web pipeline after the Canvas2D rebuild, an intermediate state of the 1.8.0 cycle that no release ever contained. What users upgrade from is v1.7.0, thewebcodecs-legacyarm at ~8 fps.src/lib/exporter/frameRenderer.tssettles it: noshadowCacheat all inv1.7.0, carried throughout byv1.8.0-rc.4, so the ~2× rebuild landed inside the 1.8.0 window. The real delta is an order of magnitude, not 1.6×. This was not hypothetical — the rc.4 release notes were drafted with the 1.6× figure straight off this table before the tree was checked.Related issue
Refs #18 — the software-encoder fallback it asks for is marked shipped here.
Type of change
Release impact
The DMG filenames change.
publish-releaseuploads viafind artifacts -type f, so nothing there is affected.update-homebrew-cask.yml's "Find macOS DMG assets" step already matchedapple[-_. ]?siliconandintel, but its "Wait for release DMG assets" step compared exact filenames — after the rename it would have polled for its full 12-minute timeout and warned, on a release whose assets were present the whole time. Both steps now match on the same patterns and cannot disagree. Old-Mac-arm64-/-Mac-x64-names still match, so past releases keep working.Desktop impact
Screenshots / video
n/a — CI, main-process wiring and docs.
Testing
npx tsc --noEmitclean.vitest --run: 1466 passed, 6 failed — all six inelectron/recording/webm-seek-index.test.ts, which this branch does not touch and which fails identically onorigin/release/v1.8.0(verified by checking out the base and re-running that file alone).biome checkclean on the four changed TypeScript files.Laziness is covered by a new test,
aiEditionService.lazyLlmConfig.test.ts, because the failure mode is invisible: reintroducing the eager form breaks no behaviour, passes every other test, and shows up only as a Keychain prompt on a machine the author may not have. It asserts the factory is not called at construction, not called by non-LLM work, and called exactly once on first LLM use.No macOS runner available locally, so the workflow logic was exercised directly rather than by running it:
ruby -ryaml -e 'YAML.load_file(...)'on both workflows.run:blocks extracted and passed throughbash -n.caseproducesOpenscreen-macOS-Apple-Silicon-1.8.0.dmgandOpenscreen-macOS-Intel-1.8.0.dmg.-Mac-arm64-/-Mac-x64-names.arm64job and is refused by anx64job; a universalx86_64 arm64bundle passes anarm64job.npm run docs:check→ OK (22 files); the new anchors and the newROADMAP.mdlink resolve.The first macOS build after merge is the real test of the guard — and if it fails a job, it will have done its work.