iOS: tint glass with the node's background; left-align fill_width rows - #78
Merged
Conversation
Two renderer bugs, both reproduced on an iPhone (iOS 26.5.2) and an iPhone 17 simulator. 1. `mobBoxBackground(node:)` never read `node.backgroundColor` on the glass path, so under any theme with `glass: true` every Box background colour was discarded on iOS 26+. Selected/active states vanished (a `background: :primary` chip rendered identically to `:surface_raised`) and semantic fills all collapsed to the same grey. Android ignores `glass:` and keeps solid fills, so the same app stayed legible there. Fixed with `glassEffect(.clear.tint(fill), in: shape)` — `Glass.tint/1` takes an Optional, so an unresolvable background still yields plain clear glass. The pre-iOS-26 `.ultraThinMaterial` fallback puts the fill behind the material so it reads as frosted colour. 2. The row case applied `.frame(maxWidth: .infinity)` with no `alignment:`, so SwiftUI defaulted to `.center`. Compose's Row uses `horizontalArrangement = Start`, so `fill_width` rows were left-aligned on Android and centred on iOS — headers drifted, checkbox/radio rows each centred independently and read as ragged. The column case already passed `.topLeading`. Now `alignment: .leading`. `Mob.Renderer.inject_theme_flags/3` uses `Map.put_new/3` instead of `Map.put/3` so an explicit `glass:` prop on a Box overrides the theme in either direction. `glass: false` is the escape hatch a glass theme needs where translucency costs legibility; `glass: true` opts one Box in without a glass theme. `mob_nif.m` already decodes the prop, so no native surface is added — the theme's unconditional `put` was the only thing blocking it. This supersedes the stale `material-liquid-glass` branch's per-node `material: :glass` opt-in; see the decision record for why that branch is closed rather than merged. Swift rendering is not host-testable; the tint and the row alignment need device verification. The Elixir contracts are covered in test/mob/renderer_test.exs.
This was referenced Aug 11, 2026
Resolves the lib/mob/theme.ex conflict, which was pure textual adjacency:
this branch adds an explanatory comment above `glass: false`, while master
appended `fonts: %{}` and `font_fallback: []` after it. Kept both.
Rebased-tree checks: format, compile --warnings-as-errors, credo --strict,
clang-format and swiftlint all clean; 1114 tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Uh oh!
There was an error while loading. Please reload this page.
GenericJam added a commit
that referenced
this pull request
Aug 28, 2026
…eadlock fix Carries three merged changes since 0.7.31: - #94 intrinsic Sheet detents ([:content] / [{:content, max_height: n}]) with canonical validation through Mob.UI.normalize_sheet_detents!/1 re-applied at the renderer boundary, iOS content measurement with capping, internal scrolling and geometry re-clamping, composite Box accessibility (label, explicit button role, disabled semantics), and Sheet in both platform tag manifests. - #74 the iOS launch deadlock behind an App Store rejection — safe_area()'s unbounded dispatch_sync on the boot path could block the BEAM thread forever so the app never finished launching. - #78 iOS glass tint from the node background, and left-aligned fill_width rows. The changelog records two known limitations rather than leaving them to be discovered: a scrollable child inside a content detent expands instead of scrolling independently (it reports intrinsic height), and a content sheet presents at :medium for one frame before resizing, since content height is only knowable after presentation. Preflight: mix format, credo --strict, compile --warnings-as-errors, 1119 tests, git diff --check, and the native frame-registry harness all clean. The Sheet work was additionally adversarially reviewed and device-verified on an iPhone 17 Pro simulator (hug, cap, internal scroll, safe-area clearance) and the Box/Android half on a physical Moto G. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 freeto 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.
Two iOS renderer bugs, both reproduced and then verified fixed on a physical iPhone SE 3rd gen (iOS 26.5.2) and an iPhone 17 simulator, against a real 61-component app.
1. Glass discarded every Box background colour
mobBoxBackground(node:)never readnode.backgroundColoron the glass path:Under any theme with
glass: trueon iOS 26+, every Box background was discarded. Selected/active state vanished (abackground: :primarychip rendered identically to:surface_raised) and semantic fills — highlight colours, avatar variants, pills — all collapsed to the same grey. Android was unaffected because it ignoresglass:and keeps solid fills, so the same app stayed legible there and not here.Fixed with
glassEffect(.clear.tint(fill), in: shape).Glass.tinttakes anOptional, so a Box with no background still gets plain clear glass. The pre-iOS-26.ultraThinMaterialpath puts the fill behind the material so it reads as frosted colour.Measured on device with pixel sampling, same rects, same theme:
:primary):surface_raised)0xFF20202E0xFF20202E— identical0xFF4000AF0xFF101023— distinct2.
fill_widthRow centred on iOS, left-aligned on AndroidThe row case applied
.frame(maxWidth: .infinity)with noalignment:, so SwiftUI defaulted to.center, while the column case a few lines above explicitly passes.topLeading. Android's Row useshorizontalArrangement = Start.Any
fill_widthrow whose children are inflexible reported the sum of its children and centred that block — so checkbox and radio rows each centred independently and read as ragged. Nowalignment: .leading.Also: per-node glass opt-out
Mob.Renderer.inject_theme_flags/3usedMap.put/3, which clobbered an explicitglass: falseon a node. NowMap.put_new/3, so a theme supplies a default and the node wins in either direction.mob_nif.malready decodes the prop, so no native surface is added.This supersedes the stale
material-liquid-glassbranch, which proposed a parallel stringly-typedmaterial:property; that branch also predatesmobBoxBackgroundand would have ignoredcorner_radius. Recommend closing it.Verification
mix compile --warnings-as-errors,mix format --check-formatted,mix credo --strict,mix test— all cleanGlass.tintsignature confirmed fromSwiftUICore.swiftinterfacerather than assumed🤖 Generated with Claude Code