Repository navigation
iOS: stop open menus from flickering when their parent views re-render - #8611
Conversation
…ilds Apply the TerminalPickerMenu isolation pattern (PR 7959) to the four menus whose owning views re-render during live sync/streaming while the menu can be open: the computer title picker, the workspace-detail title menu, the new-workspace split menu, and the workspace list filter menu. Each now renders from an Equatable value snapshot with closures excluded from equality, so parent body churn no longer rebuilds an open UIMenu. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend the menu-isolation sweep to the eight medium-churn sites: group header row, notification feed row, artifact gallery items and sort menu, chat artifact viewer toolbar menu, chat prose bubble copy menu, and both task composer pickers. Actions now key on stable IDs and resolve current state at invocation, so gated snapshots cannot go stale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR refactors iOS SwiftUI menus and rows around equatable value/action models, adds path-aware artifact viewer actions, introduces dedicated gallery and workspace menus, and expands Sendable conformance for artifact state. ChangesArtifact viewer actions
Mobile shell menus
Transcript equatable rendering
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ChatArtifactViewerActionsMenu
participant ChatArtifactViewerPager
participant ChatArtifactViewerPagerModel
User->>ChatArtifactViewerActionsMenu: select viewer action
ChatArtifactViewerActionsMenu->>ChatArtifactViewerPager: invoke action callback
ChatArtifactViewerPager->>ChatArtifactViewerPagerModel: perform action for artifact path
Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 2 warnings)
✅ Passed checks (22 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
Greptile SummaryThis PR isolates frequently updated iOS menus behind immutable snapshots and equality gates. The main changes are:
Confidence Score: 4/5The artifact viewer menu can still rebuild during unrelated loader updates.
Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerActionsMenuValue.swift Important Files Changed
Reviews (1): Last reviewed commit: "test(ios): pass artifact path to path-ke..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListNewWorkspaceMenu.swift`:
- Around line 21-39: Update the workspace-group action in the menu labeled by
`MobileNewWorkspaceGroupMenuItem` to gate its callback with
`value.canCreateGroup` rather than `value.canCreate`. Change the surrounding
menu’s disabled condition so it is disabled only when both `value.canCreate` and
`value.canCreateGroup` are unavailable, while preserving the existing workspace
creation guard and action behavior.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceMacTitlePickerValue.swift`:
- Around line 3-10: The action availability and invocation paths can diverge,
allowing enabled UI actions without handlers. In
WorkspaceMacTitlePickerValue.swift (lines 3-10), replace canAddDevice with the
shared typed action contract; in WorkspaceListView+MacSelection.swift (lines
182-184), render Add Computer from that contract. In
WorkspaceGroupHeaderRowValue.swift (lines 3-15), replace parallel can* fields
with typed available actions; in WorkspaceGroupHeaderRowActions.swift (lines
3-11), provide a nonoptional dispatcher or enforce paired construction; and in
WorkspaceGroupHeaderRow.swift (lines 137-232), derive menu and confirmation
visibility and invocation from the same contract so unavailable actions cannot
be rendered or dispatched.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: ef848c3e-ae4f-4cff-8533-9b08097979ca
📒 Files selected for processing (54)
Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactFileActionPresentation.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerActionsMenu.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerActionsMenuActions.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerActionsMenuValue.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerFileActionState.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPageSnapshot.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPager.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPagerModel.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerState.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/ChatMessageRowView.swiftPackages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/Rows/ChatProseBubbleView.swiftPackages/iOS/CmuxAgentChatUI/Tests/CmuxAgentChatUITests/ChatArtifactViewerPagerModelTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentChatDemoScreen.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerContextSection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMachineMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMachineMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMachineMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGalleryItemActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGalleryItemValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGalleryItemView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGallerySortMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGallerySortMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactGallerySortMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceGroupHeaderRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceGroupHeaderRowActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceGroupHeaderRowValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListFilterControls.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListFilterMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListNewWorkspaceMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListNewWorkspaceMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListNewWorkspaceMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListTableCoordinator.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+Actions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+MacSelection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+Toolbar.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceMacTitlePickerActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceMacTitlePickerValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenuContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenuLabelToken.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/WorkspaceTitleMenuValueTests.swift
Extends the terminal-picker menu isolation from #7959 to every remaining flicker-prone menu in the iOS app.
When a SwiftUI
Menu-owning view's body re-runs while the menu is presented, iOS rebuilds the UIMenu, producing a continuous cross-fade flicker and canceling scroll gestures. Parents like the workspace list and workspace detail re-render constantly during live sync and agent streaming, so any open menu mounted on them flickered.Each affected menu is now an
Equatableview compared over an immutable value snapshot (everything the label and menu content render), with action closures excluded from equality and applied via.equatable(). Bindings are built inside the gated child from value + action so parents stop passing freshBinding(get:set:)closures. Actions key on stable IDs and resolve current state at invocation, so gated snapshots cannot act on stale rows.Gated in this PR, by churn risk:
High (toolbar menus on streaming parents): the computer title picker (
WorkspaceMacTitlePicker, both mounts), the workspace-detail title menu (WorkspaceTitleMenu, full label token incl. chat/browser/standard branches), the new-workspace split menu, and the workspace list filter menu.Medium: workspace group header context menu, notification feed row context menu, artifact gallery item context menu and sort menu, chat artifact viewer toolbar menu, chat prose bubble copy menu, and both task composer pickers (agent/template and machine).
Intentionally unchanged: settings/iroh/editor form menus (parents don't re-render while open), segmented/inline pickers, PhotosPicker/ColorPicker/document pickers, macOS-only branches, and the workspace-row UIKit
UIMenu(already immune, built per long-press).Tests:
WorkspaceTitleMenuValueTestspins that label branch changes invalidate the title-menu value. ExistingChatArtifactViewerPagerModelTestsupdated for the path-keyed toolbar actions. Note:TerminalSurfaceMountOwnershipTestsinCmuxMobileShellUIdoes not compile on currentmain(missingterminalFolderTapEnabledargument, pre-existing, untouched here).🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Stops iOS menus from flickering or canceling gestures when their parent views re-render by gating them behind Equatable, snapshot-driven subviews. Open menus now stay stable during live sync and agent streaming.
Bug Fixes
Refactors
UIMenus.Written for commit 59996df. Summary will update on new commits.
Summary by CodeRabbit
New Features
Performance
Tests