Skip to content

fix(mobile): replace Callstack glass with Expo glass - #224

Merged
rynfar merged 2 commits into
pylonfrom
upstream/2026-08-31-expo-glass
Sep 1, 2026
Merged

fix(mobile): replace Callstack glass with Expo glass#224
rynfar merged 2 commits into
pylonfrom
upstream/2026-08-31-expo-glass

Conversation

@rynfar

@rynfar rynfar commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Second of the mobile batch. Adopted from upstream pingdotgg/t3code#8862 (9b2d04317). Depends on #223, which created floating-working-control.tsx.

What this does

Consolidates onto the glass library the app was already standardised on.
expo-glass-effect was the primary — both GlassSurface and native-glass are
built on it — while @callstack/liquid-glass survived in only two files. This
removes the second library and its native pod.

Upstream's stated reason for the switch: Expo reapplies glass after native layout
and window reattachment, where UIKit can otherwise leave the label visible but
lose the material behind it.

The rest is API renames — effectglassEffectStyle, interactive
isInteractive, and isLiquidGlassSupported → Pylon's existing
NATIVE_LIQUID_GLASS_SUPPORTED.

Port fidelity

The cherry-pick applied with no conflicts, so I checked for silent drops rather
than trusting that. For both changed components, diff(upstreamParent, upstream)
and diff(pylon, HEAD) are line-for-line identical — no Pylon divergence was
present in these files and none was lost.

Two things verified rather than assumed:

  • The support predicate is close but NOT identical, correcting an earlier
    version of this description. @callstack checks @available(iOS 26) AND the
    absence of UIDesignRequiresCompatibility; Expo's isGlassEffectAPIAvailable
    checks @available(iOS 26) AND a runtime UIGlassEffect probe. Neither
    implies the other, and Expo's other export isLiquidGlassAvailable is the
    closer analogue. This is latent rather than active: app.config.ts does not
    set UIDesignRequiresCompatibility, so today the two agree. If it were ever
    set as an iOS 26 escape hatch, headers and tab bars would go legacy while
    these two components kept rendering glass. Timing is genuinely equivalent —
    both are import-time module constants over OS/build-fixed values.
  • The removal is complete. No @callstack/liquid-glass consumers remain
    anywhere in apps/ or packages/.

The docs edit stays Pylon-voiced and only widens the sentence to cover the
working timer and scroll-to-end button.

Verification

@t3tools/mobile typecheck clean, lint clean, 194 mobile thread tests passing.

Because this drops a native dependency and CI does not build iOS on this PR,
I regenerated the native project rather than relying on the JS bundle: expo prebuild --platform ios plus CocoaPods. Confirmed by inspecting the output, not
the exit code — PylonDev.xcworkspace exists, Podfile.lock has zero
liquid-glass references and still carries expo-glass-effect. A full simulator
build against the regenerated project follows.

Worth noting the regenerated workspace is PylonDev.xcworkspace; the checkout
had a stale T3CodeDev.xcworkspace left from before the Pylon rename.

Native build: ** BUILD SUCCEEDED **, zero errors and zero liquid-glass
references anywhere in the build log. Read from the log, not the exit code.

Simulator pass (iOS 26.3)

Adversarial review raised a blocking concern worth recording, because it was
specific and testable: the library being removed ships an explicit UIKit
workaround (contentView.isUserInteractionEnabled = true) for a bug where
UIGlassEffect can leave a glass view's content non-interactive when children
mount after setupView(). Expo's GlassView.swift has no such line, and
isInteractive — which this PR introduces to Pylon for the first time — wraps
the only tappable control inside a glass view. Predicted symptom: a dead
scroll-to-end button.

It does not reproduce. Tested on a fresh native build against real seeded
threads, in both code paths:

  • standalone UniwindGlassView with isInteractive (no work running) — tap
    scrolled the feed to the end and the button correctly disappeared;
  • merged GlassContainer with the working timer — same result.

The likely reason: Expo's GlassView.swift carries 7 layoutSubviews /
didMoveToWindow hooks that re-run setup, whereas GlassContainer.swift has
none. Also verified visually: both pills render with correct rounded-full
geometry, so the unclamped-borderRadius concern does not manifest either.

The working-timer branch needs a live turn, so it was exercised by temporarily
forcing showWorkingControl on and then reverting — no such change is in the
diff, and all six gating conditions are intact on the branch.

Reviewed and integrated with Claude Opus 5 in Claude Code.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Adopted from upstream `pingdotgg/t3code#8862` (`9b2d04317`).

Consolidates onto the glass library the app already uses. `expo-glass-effect`
was the primary — `GlassSurface` and `native-glass` were built on it — while
`@callstack/liquid-glass` survived in only two files. Expo reapplies glass after
native layout and window reattachment, where UIKit can otherwise leave the label
visible but lose the material behind it.

The support predicate swaps to Pylon's existing `NATIVE_LIQUID_GLASS_SUPPORTED`,
which is semantically identical: both resolve to "iOS and the native glass API
is available". Remaining changes are API renames (`effect` to `glassEffectStyle`,
`interactive` to `isInteractive`).

Drops the `@callstack/liquid-glass` dependency; no consumers remain.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.7 KiB +176 B (+1.3%) 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 6.9 KiB +7 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.6 KiB 6.8 KiB +169 B (+2.5%) 7.8 KiB
Codex Live turn WebSocket decoded 57.2 KiB 58.7 KiB +1.5 KiB (+2.6%) 66.4 KiB
Codex Live turn messages 10 11 +1 (+10.0%) 21
Claude Total thread wire 13.5 KiB 13.5 KiB +2 B (+0.0%) 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 6.9 KiB +3 B (+0.0%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.6 KiB −1 B (−0.0%) 7.8 KiB
Claude Live turn WebSocket decoded 58.0 KiB 58.0 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 9 9 0 (0.0%) 21

Baseline: 2d168f2 · PR result: 6950c6c · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.5 KiB
  • Claude decoded thread snapshot: 110.2 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

The comment carried over from upstream described GlassView's window
handling while sitting above the withUniwind call, where it reads as if it
justifies the options object. It does not, and the options object is
load-bearing: uniwind treats any prop ending in "Style" as a style prop, so
auto mode would pass glassEffectStyle={[undefined, "regular"]} into a native
enum and lose the effect. The old `effect` prop did not end in "Style",
which is why auto mode was safe before the rename.

Uses the wording Pylon already records at GlassSurface.tsx, so a future
simplification does not drop the options object and silently lose the glass
style.
@rynfar
rynfar merged commit 9d82e68 into pylon Sep 1, 2026
19 checks passed
@rynfar
rynfar deleted the upstream/2026-08-31-expo-glass branch September 1, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant