Written from inside a shipping Mac app
The LLM releases of April 2026, absorbed by a consumer Mac app with zero new builds
Most write-ups of this month’s model churn are scorecards: who shipped, when, at what price, against which benchmark. This one is different. It follows a single 10-line function inside a shipping Mac app and shows how new Claude models reach users the same day they land, without App Store review, Sparkle updates, or a single line of Swift changing.
What actually shipped in April 2026
A tight recap, because every other guide on this topic covers it in depth and there is no reason to repeat the benchmark charts. Anthropic’s public lineup as of April 22, 2026 is Claude Sonnet 4.6 for everyday use, Claude Opus 4.7 as the new GA ceiling (same pricing as Opus 4.6 at $5 input and $25 output per million tokens), and Claude Mythos (codename Capybara) behind the Project Glasswing preview program. Inside Claude Apps the new arrival is Claude Design, but that is a product on top of existing models, not a model itself.
The more interesting question, for anyone writing consumer software on top of these models, is not which model is best. It is: when Anthropic pushes a new ID to the SDK on a Tuesday at 10am, how long does it take for an end user with a bundled Claude-powered app to actually pick it?
The anchor fact
Fazm’s entire model catalog is 0 rows of Swift
Not a hard-coded array of model IDs. A 4-row modelFamilyMap that substring-matches whatever the Claude SDK returns. New Sonnet. New Opus. New Haiku. They all route through the same 4 rows. An ID the map has never seen still shows up in the picker the same day, just at the bottom with its raw API name. There is no version of this that requires a build.
The 10-line function that does the absorbing
Everything else in this guide is a footnote to this one function. It lives in the ACP bridge, which is a bundled Node subprocess that Fazm spawns on boot to talk to the Claude agent SDK. The bridge is where the raw availableModels field first surfaces, and it is also where the decision about what to ship to Swift gets made.
Three moving parts. A filter (the default pseudo-model is dropped because it is a server-side alias, not a user-selectable row). A diff (JSON stringify compared against a memoized string, which is the cheapest order-preserving compare we could write). And one send call, which is the bridge’s only way of talking back to the Swift app. If nothing changed, nothing crosses the wire.
From SDK response to picker, in one diagram
The two call sites in the bridge are session/new (fresh session) and the resume fallback (when a previously saved session ID is still valid). Both funnel their result.models?.availableModels into the same function, and from there exactly one IPC message goes out per real change.
Model list fan-in to the picker
The 4 rows that name the models
Fazm does not call Sonnet, Sonnet. It calls it Fast. Opus is Smart. Haiku is Scary. That relabel is not string-interpolation over a hard-coded version list; it is a substring match against a 4-row table in ShortcutSettings.swift.
What this unlocks: Anthropic can ship claude-sonnet-4-7 tomorrow and users get a picker entry labeled “Fast (Sonnet, latest)” without any help from us. They can ship claude-opus-5 next week and it shows up as “Smart (Opus, latest)”. Both live at the top of the picker. Both are selectable. Both route through the exact same ACP prompt path the old models used.
The 4 rows, one at a time
haiku → Scary
Every model ID whose string contains 'haiku' is labeled Scary (Haiku, latest) and placed at order 0 in the picker. The name is a nod to how fast Haiku 4.5 answers: quicker than a typical human can read the question.
sonnet → Fast
The everyday default. Sonnet 4.6 in April 2026. Order 1. 'Fast (Sonnet, latest)'. The entire main chat, the floating bar, and the observer all run against this by default.
opus → Smart
Order 2. 'Smart (Opus, latest)'. Available per-turn. In April 2026 this resolves to Opus 4.7 via the SDK, but the family map does not care about the minor version. It cares that the string contains 'opus'.
default → Smart
The SDK's pseudo-model. Filtered out in the bridge, but mapped to order 2 on the Swift side so stored legacy selections still resolve cleanly. This is the line that kept Opus users on Opus when Anthropic renamed the ID.
Everything else → order 99
If a new model ID arrives that contains none of the above substrings, line 191 uses the raw API name and assigns sort order 99. The model is selectable day zero. A later build can drop a nicer label into the map to move it up.
One full lifecycle, from Anthropic’s changelog to your picker
Seven steps between a new model landing in the Claude SDK and you clicking on it inside the floating bar. None of them involve the Fazm codebase changing.
Model absorption, end to end
Anthropic ships a new model ID to the SDK
The model lands inside the Claude agent SDK's availableModels response. Nothing in Fazm's codebase changes. The ACP bridge is a subprocess bundled inside the app, but it is not the bit of code that hard-codes the catalog.
Next app launch: bridge calls session/new
preWarmSession in acp-bridge/src/index.ts runs on boot for main, floating, and observer. Every session/new response includes the current models.availableModels. The bridge reads it off result.models?.availableModels at line 1347.
emitModelsIfChanged filters, diffs, sends
Line 1271 of the bridge. Drops the 'default' pseudo-model. Stringifies the remaining list. Compares to lastEmittedModelsJson. If different, fires a single IPC message of shape { type: 'models_available', models: [...] }.
Swift receives models_available over stdout
ACPBridge.swift line 1131 decodes the message and calls onModelsAvailable with a parsed array of (modelId, name, description?) tuples. No more than one call per real change, because the bridge already deduped.
ShortcutSettings.updateModels rebuilds the picker
At ShortcutSettings.swift line 180, each returned model is matched against modelFamilyMap. Known families get a human label like 'Fast (Sonnet, latest)'. Unknowns fall to the raw API name with order 99. The sorted list overwrites @Published availableModels.
Every surface re-renders together
The floating bar picker, the Settings tab picker, and the keyboard shortcut list all subscribe to availableModels on the same ObservableObject. One publish. One render pass. One coherent picker across the app.
Selected model migrates if its family changed
If the user's saved selectedModel is no longer in the new list, updateModels runs normalizeModelId and upgrades. Legacy 'opus' migrates to 'default' because the SDK v0.29+ uses 'default' for Opus. If even that does not resolve, the code logs a warning and leaves the selection alone so the user can pick manually.
What this replaced
Before the 2.4.0 release on April 20, 2026, Fazm’s model catalog lived inside the Swift binary. Every Claude release was an app update. The defaultModels array at ShortcutSettings.swift line 151 is still there, but only as the fallback list shown until ACP reports back. The new pipeline is strictly additive.
Then vs. now when Claude ships a new model
Before 2.4.0
Hard-coded model IDs in Swift. New Claude release = new app build. App Store review + Sparkle update + user restart. Days, sometimes a week.
After 2.4.0 (April 20, 2026)
Model list arrives at runtime from the Claude agent SDK. emitModelsIfChanged fires one IPC on change. updateModels rebuilds the picker. New model visible on the next app launch.
Net effect on the user
The picker updates the same day. The only thing the user has to do is relaunch Fazm. The selectedModel preference migrates automatically if its family changed.
“New Claude models populate dynamically from the agent, so newly released IDs appear without an app update.”
CHANGELOG.json, version 2.4.0, dated 2026-04-20
Watching a new model land in the log file
The bridge writes to /tmp/fazm-dev.log on dev builds. When Anthropic pushes a new Claude ID to the SDK and you restart Fazm, this is roughly what you see. Nothing here is invented — the log strings come from logErr calls inside emitModelsIfChanged and the preWarm pipeline.
The first interesting thing is that the raw list from the SDK includes default. By the time the IPC message crosses stdout it is gone: the filter on line 1274 already dropped it. The second is the label. The picker does not show claude-opus-4-7. It shows Smart (Opus, latest), because the family map substring-matched opus inside the ID.
The one gotcha: the Opus-to-default migration
When the Claude agent SDK upgraded to v0.29, the model ID for Opus on the SDK side changed from the familiar opus alias to the pseudo-model name default. Fazm users who had previously picked Opus still had selectedModel = "opus" saved to UserDefaults. After an app update that string pointed at a model the SDK no longer exposed. The fix is tiny and sits at line 170 of ShortcutSettings.swift.
A three-line if-chain that reconciles a vocabulary change between SDK versions. This is the kind of seam you only discover when you are shipping a desktop app that survives multiple Claude agent protocol upgrades in a single month.
The timeline, played out
T−24h
Why this matters for anyone building on top of Claude this month
Model churn is going to stay high. April 2026 alone shipped Sonnet 4.6 and Opus 4.7, with a gated preview still in flight. If your app’s model catalog is a Swift enum, every month is a release month whether you wanted one or not. The cheaper alternative is to stop owning the catalog at all.
- Let the agent SDK be the source of truth for which models exist.
- Run the filter, diff, and send on your side in one place so there is exactly one IPC per real change.
- Label by family substring, not by full version. “opus” wins over “claude-opus-4-7” because the substring survives a Tuesday release.
- Fall unknowns to sort order 99 with the raw name. Users see the new model day zero; a future build can give it a nicer label if you want one.
- Keep a small normalize function for the version in UserDefaults. The SDK will rename things. Your stored preferences will not.
The short version of all of this
April 2026’s headline LLM releases are Claude Sonnet 4.6 and Claude Opus 4.7. Inside Fazm, neither one triggered a model-catalog code change. A 10-line bridge function filters and forwards whatever the SDK returns; a 4-row Swift table labels the results for humans; a 3-line normalize function keeps old preferences pointing at the right model after an SDK rename. That is the whole mechanism behind “new Claude models appear without an app update” in the changelog.
Want to see a new Claude model land without shipping a build?
Book 20 minutes and we will screenshare the log file next to the picker while Fazm restarts. You watch the IPC cross the wire and the model appear in the UI.
Questions
What are the headline LLM releases from April 2026?
Anthropic is the loudest voice in April 2026: Claude Sonnet 4.6 became the everyday default for general use, and Claude Opus 4.7 reached general availability on April 22, 2026 at the same $5 input / $25 output per million tokens as 4.6. A third Anthropic model, codename Capybara (Claude Mythos), remains gated behind Project Glasswing. Outside Anthropic, the month's recurring themes were longer context, cheaper Sonnet-class usage, and more model-picker churn inside consumer apps. The interesting story for shipping apps is not which model won, but how quickly end users could actually select it.
What does Fazm do specifically when a new Claude model ships?
Almost nothing that involves the Fazm codebase. When a new Anthropic model appears in the Claude agent SDK, the ACP bridge (a bundled subprocess) sees it inside the availableModels array returned by session/new. Fazm's bridge emits one IPC message, models_available, over stdout. The Swift app receives it, substring-matches each model ID against a 4-row table, rebuilds the picker, and notifies the UI. No App Store review, no Sparkle update, no version bump on fazm.ai. The whole round trip on launch is under a second.
Where in the source does the model absorption actually happen?
Two places worth reading. First, acp-bridge/src/index.ts around line 1271 — the emitModelsIfChanged function that takes the raw availableModels from the SDK, drops the default pseudo-model, JSON-diffs against lastEmittedModelsJson, and fires send({ type: 'models_available', models: filtered }) exactly once per change. Second, Desktop/Sources/FloatingControlBar/ShortcutSettings.swift around line 180 — updateModels, which sorts the returned list through a 4-row modelFamilyMap (haiku→Scary, sonnet→Fast, opus→Smart, default→Smart) and overwrites the @Published availableModels property. That is all the glue it takes.
Why does Fazm filter out a model whose ID is literally 'default'?
The Claude agent SDK exposes a default pseudo-model that resolves to whatever Anthropic has picked for best-at-the-moment. Surfacing it as a selectable row in the picker confuses users because the label depends on a server-side decision that changes without notice. Fazm drops it in the bridge (index.ts line 1274: filtered = availableModels.filter(m => m.modelId !== 'default')) but migrates any stored 'opus' selection to 'default' on the Swift side so existing Opus users keep getting routed to Opus 4.7 through the SDK's own fallback. The migration lives in normalizeModelId at ShortcutSettings.swift line 170.
What happens when Anthropic ships a model the Fazm code has never heard of?
It still appears in the picker, just with a less friendly name. The modelFamilyMap at ShortcutSettings.swift line 159 only contains haiku, sonnet, opus, and default. If a new model ID does not contain any of those substrings, the else branch at line 189 falls through to the raw API name and assigns it sort order 99, which pushes it to the bottom of the picker. Users see the unfamiliar name, can select it, and their chat uses that model ID on every subsequent session/prompt. A future build can add a new row to modelFamilyMap to give it a nicer label, but the feature itself works day zero.
Why is lastEmittedModelsJson a string and not a hash or a Set?
Because emitModelsIfChanged gets called from every session warmup, every resume, every new session, and sometimes several of those in the same second. A JSON string compare is the cheapest, order-preserving diff we could write. It treats [sonnet, opus] and [opus, sonnet] as different lists, which matters because the SDK's order is how the picker orders models when the family map rank ties. The Set-based version we prototyped silently dropped the SDK's intended ordering and made Sonnet show up above Haiku in some runs.
Does the floating bar show the same model list as the main chat?
Yes. availableModels is a single @Published property on the shared ShortcutSettings singleton, read by both the floating bar's model picker (AIResponseView.swift line 1131) and the main Settings panel's picker (SettingsPage.swift line 2025). One source of truth. The very same array also drives the keyboard shortcut list for quick model switching. When the SDK reports a new model, all three surfaces update in the same render tick.
What about non-Anthropic models — can I use GPT-5 or Llama through this pipeline?
Not today. The model list pipeline is bound to the Claude agent SDK because that is what the ACP bridge talks to. If Anthropic exposes a wider set of models through the SDK in a future release, Fazm will pick them up the same way it picks up a new Claude. For other providers, the architecture still fits (a second bridge subprocess and a second emitModelsIfChanged), but no such bridge ships today.
How do I actually verify a new model landed on my machine?
Tail the dev log at /tmp/fazm-dev.log and look for the lines 'Raw models from ACP SDK' and 'Emitted models_available'. The first shows exactly what the SDK returned. The second shows what made it past the default filter and the dedupe check. If you restart Fazm right after Anthropic announces a new model, you should see the new ID appear in both log lines, and then the picker in the floating bar updates without any other action from you.
Other corners of the release month, written up from inside a shipping Mac app
More on April 2026
Anthropic new model April 2026
The 4-row substring map that relabels Claude model IDs as Scary, Fast, and Smart so Opus 4.7 arrives day zero.
Anthropic Claude release notes, April 2026
Every Claude release note from April 2026, translated into the exact code paths they touched inside a shipping Mac app.
Anthropic April 2026 news
One function, one config file, zero app updates. The rewiring story behind a full month of Anthropic releases.