fix(ios): grok composer keeps focus — stop shell placement flipping on focus#10
Merged
Conversation
…n focus Real root cause of "grok composer shows neither above rows nor telemetry when focused" (build-24's zIndex(0) treated the wrong layer). 3 independent Opus reviewers converged: The core VStack's `.composerShellIf((detached && !inputOwnsSurface) || tuckedTab)` and the outer `.composerShellIf(!detached && !tuckedTab)` toggle a ViewModifier on/off based on `tuckedTab`, which folds in `hasAboveContent` → `composerFocused`. `composerShellIf` is a @ViewBuilder if/else, so flipping it changes the subtree's STRUCTURAL IDENTITY → SwiftUI tears down + rebuilds the Composer → its @focusstate resets to false → onFocusChange(false) → composerFocused can never latch true → BOTH focus-gated halves (above rows + telemetry) collapse. grok is the only `tuckedAboveTab` shell, so it's the only one whose shell placement depends on focus; cursor et al. have focus-stable predicates, hence immune. Fix: key the two shell placements + the core zIndex off the STATIC `resolved.layout.tuckedAboveTab` (new `tuckedShell`) instead of the focus-derived `tuckedTab`. grok now always wears its shell on the core (focused or not) — no identity churn, focus latches, both halves render. `tuckedTab` still drives the focus-gated tab geometry/spacing (those don't change identity). Byte-identical for the other 12 shells (their tuckedAboveTab is false). Verified: swift build + 73 tests + iPhone-17 simulator build. Diagnosed via 3 adversarial Opus 4.8 reviewers (independent convergence, high confidence). Co-Authored-By: Claude Opus 4.8 (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 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.
Symptom
On the grok composer shell, focusing the composer (keyboard up) showed neither the above rows nor the bottom telemetry row — just a bare one-line input. Every other shell (e.g. cursor) shows both when focused. Build 24's
zIndex(0)did not fix it.Root cause (3 independent Opus 4.8 reviewers converged)
The core VStack's
.composerShellIf((detached && !inputOwnsSurface) || tuckedTab)and the outer.composerShellIf(!detached && !tuckedTab)toggle aViewModifieron/off based ontuckedTab, which folds inhasAboveContent → composerFocused.composerShellIfis a@ViewBuilderif/else, so flipping it changes the subtree's structural identity → SwiftUI tears down + rebuilds theComposer→ its@FocusStateresets tofalse→onFocusChange(false)→composerFocusedcan never latchtrue→ both focus-gated halves (above rows and telemetry) collapse.grok is the only
tuckedAboveTabshell, so it's the only one whose shell placement depends on focus; cursor & co. have focus-stable predicates (hence immune). This also explains why the symptom is "both halves" (a sharedcomposerFocusedgate that can't stabilise) and whyzIndexcouldn't help (paint order ≠ identity).Fix
Key the two shell placements + the core
zIndexoff the staticresolved.layout.tuckedAboveTab(newtuckedShell) instead of the focus-derivedtuckedTab. grok now always wears its shell on the core (focused or not) — no identity churn, focus latches, both halves render.tuckedTabstill drives the focus-gated tab geometry/spacing (those don't change identity).Byte-identical for the other 12 shells — their
tuckedAboveTabisfalse, sotuckedShell == tuckedTab == falseand every changed expression evaluates the same as before.Verification
swift build✓ ·swift test73/73 ✓ · iPhone-17 simulator BUILD SUCCEEDED ✓. Diagnosed by 3 adversarial Opus 4.8 reviewers (independent convergence, high confidence). On-device confirm to follow in build 25.🤖 Generated with Claude Code