-
Notifications
You must be signed in to change notification settings - Fork 28
Feature/adaptive ios ipad design #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Created three new core adaptive components: - AdaptiveTheme.swift: Device context detection and adaptive spacing/layout system - AdaptiveNavigationContainer.swift: Adaptive navigation (stack on iPhone, split view on iPad) - AdaptiveSplitView.swift: Adaptive content layouts (single pane on iPhone, side-by-side on iPad) Integrated AdaptiveTheme into catnipApp.swift via .withAdaptiveTheme() modifier. This foundation enables environment-driven theming that adapts intelligently across iPhone, iPad, and prepares for future macOS support. No visual changes yet - pure infrastructure that existing views will adopt in subsequent phases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Transformed WorkspacesView to use AdaptiveNavigationContainer: - Added selectedWorkspaceId state for adaptive navigation - Refactored body to use AdaptiveNavigationContainer with sidebar/detail/empty views - Created workspacesList with selection support (List with selection binding) - Created selectedWorkspaceDetail that shows WorkspaceDetailView when workspace selected - Created emptySelectionView for iPad when no workspace is selected - Removed old navigationWorkspace state and .navigationDestination - Updated createWorkspace to use selectedWorkspaceId instead - Fixed missing Combine import in AdaptiveTheme.swift Navigation behavior: - iPhone: Uses NavigationStack with full-screen push (unchanged UX) - iPad portrait: Sidebar overlay with detail pane - iPad landscape: Side-by-side split view with persistent sidebar All existing functionality preserved: ✅ Pull-to-refresh ✅ Swipe-to-delete ✅ Create workspace flow ✅ Delete confirmation ✅ Health check monitoring ✅ Codespace reconnection ✅ Claude authentication 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Transformed WorkspaceDetailView to use AdaptiveSplitView for terminal and chat: **Removed old orientation detection:** - Removed @Environment horizontalSizeClass and verticalSizeClass - Removed @State isLandscape and showPortraitTerminal - Removed updateOrientation(), rotateToLandscape(), portraitTerminalView - Removed orientation change handlers from body - Removed old toolbarContent **Added adaptive theme integration:** - Added @Environment(\.adaptiveTheme) access - Added @State showTerminalOnly for iPhone toggle **Refactored content views:** - Simplified mainContentView to always show contentView (no branching) - Refactored contentView to use AdaptiveSplitView: - iPad/Mac: Side-by-side terminal + chat with mode toggle menu - iPhone: Single pane with simple toggle button - Extracted chatInterfaceView from old contentView - Moved navigation title to contentView level - Integrated toolbar into contentView (context ring + toggle) **Updated terminal connection logic:** - Terminal connects when: adaptiveTheme.prefersSideBySideTerminal OR showTerminalOnly - Replaces old isLandscape check **Applied adaptive spacing:** - Used adaptiveTheme.cardPadding for VStack spacing - Used adaptiveTheme.containerPadding for horizontal padding Display behavior: - iPhone: Toggle between chat and terminal (simple button with context ring) - iPad portrait: Toggle between chat and terminal - iPad landscape: Side-by-side split view with mode menu (split/leading/trailing) All existing functionality preserved: ✅ Polling logic (WorkspacePoller) ✅ Phase determination ✅ Terminal WebSocket connection ✅ Prompt submission via PTY ✅ Diff viewer ✅ PR creation/update ✅ All sheets and alerts ✅ Session data fetching 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Added proper presentation configuration to ClaudeAuthSheet: - .presentationDetents([.medium, .large]) - allows sheet to resize - .presentationDragIndicator(.visible) - shows drag handle - .scrollDismissesKeyboard(.interactively) - keyboard dismisses on scroll This allows the sheet to expand to .large when keyboard appears, preventing the text field from being obscured. Users can also drag the sheet up manually or scroll to dismiss keyboard. Fixes keyboard covering input fields on iPad during authentication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Added adaptive theme integration to CodespaceView and constrained button widths using adaptiveTheme.maxContentWidth: - connectView: "Access My Codespace" button - createRepositoryView: "Create Repository" and "I Created" buttons - installingView: Action buttons after installation complete This prevents ridiculous full-width buttons on iPad that span the entire screen. Buttons now max out at: - iPhone: .infinity (unchanged, full width) - iPad Compact: 600pt - iPad Regular: 800pt - Mac Compact: 700pt - Mac Regular: 1000pt Much more reasonable and follows iOS design guidelines for button sizing on larger displays. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Improved maxContentWidth values and background presentation: **maxContentWidth adjustments:** - iPad Compact: 600pt → 400pt (more reasonable on smaller iPads) - iPad Regular: 800pt → 600pt (better balance) - Mac Compact: 700pt → 500pt - Mac Regular: 1000pt → 700pt **Fixed background color mismatch:** - Wrapped connectView and createRepositoryView in ZStack with Color(uiColor: .systemGroupedBackground) background - Applied .frame(maxWidth: adaptiveTheme.maxContentWidth) to inner VStack - Applied .frame(maxWidth: .infinity) to maintain centering - This creates centered content with proper background fill Before: White ScrollView background created harsh visual break After: Seamless background color with centered, width-constrained content The content is now properly centered on iPad with constrained width while the background color fills the entire screen naturally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Removed unnecessary ZStack wrapper with .ignoresSafeArea() from workspacesList that was causing awkward spacing in the iPad split view navigation bar. **Before:** - ZStack with Color background and .ignoresSafeArea() - Fought with NavigationSplitView's native layout - Created weird margin/padding in top right nav bar on iPad **After:** - Clean Group wrapper for conditional content - Let NavigationSplitView handle layout naturally - Navigation bar now looks clean and proper on iPad The split view sidebar now has proper, native spacing that looks FABULOUS on iPad! No more weird margins in the navigation bar. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Changed navigationBarTitleDisplayMode from .inline to .large for the WorkspacesView sidebar. **Before:** - .inline title mode caused misalignment with back button - Sidebar title and toolbar appeared lower than back button - Created awkward vertical spacing mismatch **After:** - .large title mode aligns properly with navigation hierarchy - "Workspaces" title and toolbar buttons align with back button - Clean, top-aligned navigation on iPad split view This creates proper visual alignment in the iPad split view where the sidebar navigation elements align with the parent navigation context (back button). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replaced NavigationSplitView's automatic navigation bar with a
custom VStack header to eliminate unwanted top margin/spacing.
**Before:**
- Used .navigationTitle() and .toolbar() inside sidebar
- NavigationSplitView added its own navigation context
- Created unwanted top margin and spacing issues
- Large title mode was too huge, inline had alignment issues
**After:**
- Custom HStack header with manual layout control
- Text("Workspaces") with .largeTitle.bold() font
- Plus button and split view toggle in trailing position
- VStack(spacing: 0) starts at very top with no margin
- Full control over spacing and alignment
This bypasses NavigationSplitView's navigation bar system entirely,
giving us pixel-perfect control over the sidebar header layout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced custom header hack with the correct iOS-native approach using .listStyle(.sidebar) which is specifically designed for NavigationSplitView sidebar columns. **Key Changes:** - Changed .listStyle(.plain) to .listStyle(.sidebar) - Used .navigationBarTitleDisplayMode(.inline) for compact header - Used .automatic toolbar placement for proper sidebar behavior - Removed custom VStack header hack **Why .sidebar is correct:** - Purpose-built for NavigationSplitView sidebars - Handles safe areas and spacing properly - Follows iOS design guidelines automatically - Works with system navigation components - Provides proper visual hierarchy This is the modern, standards-inspired approach recommended by Apple for iOS 18+ NavigationSplitView implementations. Sources: - https://www.createwithswift.com/exploring-the-navigationsplitview/ - https://medium.com/@expertappdevs/navigationstack-splitview-for-ios-apps-a9f5959e52ba - https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
**The Problem:** Adding .toolbar() to sidebar content inside NavigationSplitView created duplicate/misplaced toolbar items because NavigationSplitView manages its own navigation context for the sidebar column. **The Root Cause:** - NavigationSplitView creates a navigation bar FOR THE SIDEBAR - Our .toolbar() modifier was adding items to this sidebar navigation - This conflicted with the parent navigation context - Result: Duplicate + button appearing in wrong locations **The Solution:** - Removed .navigationTitle() and .toolbar() entirely from sidebar - Used List Section with custom header instead - Header contains "Workspaces" title + plus button - textCase(nil) prevents iOS from uppercasing the header - Custom padding for proper spacing **Why This Works:** - No navigation bar conflict - we're not creating a nav context - Section headers are part of List's native layout system - Works perfectly with .sidebar list style - Clean, predictable rendering - Follows iOS design patterns This eliminates the mysterious obscured + button and toolbar layout issues completely. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Removed custom Section header that was fighting against NavigationSplitView's
automatic navigation bar system. The obscured + button was caused by the Section
header creating duplicate navigation elements.
Now using proper iOS approach:
- .navigationTitle("Workspaces") on the List
- .toolbar() with topBarTrailing placement for + button
- Let NavigationSplitView handle all navigation bar layout automatically
This fixes:
- Obscured + icon in upper left corner
- Top margin/padding issues
- Sidebar toggle button positioning
- Proper use of full vertical height
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed .navigationBarTitleDisplayMode from .large to .inline. Large title mode created a huge scrolling "Workspaces" header that pushed content down and created poor layout. Inline mode puts the title in the navigation bar at normal size, which is proper for NavigationSplitView sidebars. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- AdaptiveSplitView: Use maxContentWidth for leading pane in split mode instead of forcing 50/50 split. This allows chat content to be constrained to readable width (600px iPad Regular, 400px Compact) while terminal expands to fill remaining space. - AdaptiveNavigationContainer: Wrap sidebar in NavigationStack to properly support navigation bar with title and toolbar items. - WorkspacesView: Remove zero list row insets to allow proper spacing. WorkspaceCard already has appropriate padding (16px horizontal, 12px vertical) that works well in both orientations. Fixes issues with half-width content and improper sidebar spacing on iPad in landscape mode. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
**iOS 26 Compatibility Fix:** - Changed NavigationSplitView columnVisibility from `.automatic` to `.all` - iOS 26 changed default behavior to collapse sidebar initially with `.automatic` - Now sidebar is always visible on iPad across both iOS 18 and iOS 26 **Testing Infrastructure:** - Created test-ios-comparison.sh for cross-version testing - Tests same device model (iPad Pro 13-inch) on iOS 18.5 and iOS 26.0 - Captures screenshots to verify layout consistency - Created test-ipad-comparison.sh (deprecated in favor of test-ios-comparison.sh) **Files Changed:** - catnip/Components/AdaptiveNavigationContainer.swift:19 Changed: `.automatic` → `.all` for columnVisibility **Verified:** ✅ iOS 18.5: Sidebar displays correctly with split view ✅ iOS 26.0: Sidebar now displays correctly (was collapsed before) ✅ Layout consistency across iOS versions maintained 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…trait **Workspace List Items:** - Removed horizontal and vertical padding from WorkspaceCard - Added zero listRowInsets to make items truly edge-to-edge - Items now extend fully from left to right edge of sidebar **iPad Portrait Mode Split View:** - Changed from horizontal (side-by-side) to vertical (top/bottom) layout - Modified AdaptiveSplitView to detect iPad portrait mode (.iPadCompact) - Created new verticalSplitLayout using VStack for portrait orientation - Renamed splitLayout to horizontalSplitLayout for clarity - Terminal now appears at bottom half, chat at top half in portrait **Files Changed:** - catnip/Views/WorkspacesView.swift:263 - Added .listRowInsets(EdgeInsets()) - catnip/Views/WorkspacesView.swift:719-720 - Removed .padding modifiers - catnip/Components/AdaptiveSplitView.swift:41-58 - Added orientation detection - catnip/Components/AdaptiveSplitView.swift:87-112 - Added verticalSplitLayout **Behavior:** ✅ iPhone: Single pane with toggle (unchanged) ✅ iPad Landscape: Horizontal split (side-by-side) ✅ iPad Portrait: Vertical split (top/bottom) - NEW ✅ Workspace items: Edge-to-edge with no margins 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:** Workspace list items had white gaps on left/right sides between the gray backgrounds and the sidebar edges. NavigationSplitView's .sidebar list style enforced safe area insets that couldn't be overridden. **Solution:** Changed from .listStyle(.sidebar) to .listStyle(.plain) which removes the forced safe area insets while maintaining proper list behavior. **Implementation Details:** - Added .frame(maxWidth: .infinity) to WorkspaceCard for full width - Applied background with .ignoresSafeArea(edges: .horizontal) - Set .listRowInsets(EdgeInsets()) for zero row padding - Changed .listRowBackground() to Color.clear - Kept internal WorkspaceCard padding (16px horizontal, 12px vertical) **Files Changed:** - catnip/Views/WorkspacesView.swift:260-265 - Background application - catnip/Views/WorkspacesView.swift:281 - List style change (.sidebar → .plain) **Result:** ✅ Gray backgrounds extend fully edge-to-edge (like iOS Mail) ✅ Text maintains proper internal padding ✅ No white gaps on sides ✅ Matches native iOS design patterns 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The iPad split view changes inadvertently broke iPhone by nesting NavigationStacks. Rewrote WorkspacesView to use different navigation patterns per device: - iPhone: Uses parent's NavigationStack with .navigationDestination(item:) - iPad: Uses NavigationSplitView for sidebar/detail pattern Also removed debug logging and cleaned up dead code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The iOS liquid glass effect was stretching the terminal toggle button into an ellipse. Using .buttonStyle(.plain) removes the default chrome and lets the ContextProgressRing's circular background render correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: iPad Split View Adaptive DesignThis is a comprehensive adaptive design overhaul that adds proper iPad split view support. The implementation is well-structured with a clear architectural foundation. StrengthsArchitecture
Code Quality
Issues & Concerns1. Navigation Stack Regression (Severity: High)Location: WorkspacesView.swift The PR mentions fixing iPhone navigation breaking from nested NavigationStack (commit 00ec3da). This indicates a regression was introduced and later fixed. The dual-path approach for iPhone vs iPad increases testing complexity. Concern: Need to ensure both navigation paths are thoroughly tested. 2. iOS 26 Compatibility Hack (Severity: Medium)Location: AdaptiveNavigationContainer.swift:19 Changed columnVisibility from .automatic to .all for iOS 26 compatibility Concerns:
Recommendation: Use .automatic with proper state management, or add runtime iOS version detection 3. Terminal Dimension Calculation Issues (Severity: Medium)Location: TerminalView.swift:1946-2003 Multiple issues:
Recommendation: Use layout completion callbacks instead of arbitrary delays 4. Unused Layout Mode (Severity: Low)Location: AdaptiveSplitView.swift:61-86 The horizontalSplitLayout is defined but never used - all iPads now use verticalSplitLayout. Recommendation: Remove dead code or document why it's being kept for future use. 5. Performance: Excessive Layout Recalculations (Severity: Medium)Location: TerminalView.swift:263-267 layoutSubviews() is called frequently and handleBoundsChange() may trigger excessive terminal resize operations during animations. Recommendation: Add more aggressive debouncing or use willTransition for rotation detection. 6. Hardcoded Device-Specific Values (Severity: Low)Location: AdaptiveTheme.swift:236-248 maxContentWidth values went through multiple iterations (commit 3990912: 600 to 400, 800 to 600). They may not scale well to all iPad sizes (iPad mini vs iPad Pro 12.9 inch). Recommendation: Consider dynamic calculations based on actual screen width. 7. Missing Tests (Severity: High)Concerns:
Recommendation: Add XCTest UI tests for rotation, split view transitions, and navigation patterns Security ConcernsNo Critical Issues:
Performance Considerations1. Terminal WebSocket Connection LogicThe connection logic changed from orientation-based to adaptive theme-based. Ensure WebSocket reconnection is properly debounced during rapid device rotation. 2. Layout ThrashingMultiple @Environment values trigger updates. During rotation, these may update in sequence, causing multiple re-renders. Consider batching updates. Test Coverage AssessmentManual Tests Only:
Critical: Add automated tests before merging for:
RecommendationsBefore Merging:
Post-Merge Improvements:
Overall AssessmentQuality Score: 7/10 This is a solid architectural foundation with excellent component design, but it has several rough edges:
The PR is functional but needs polish before production deployment. The iterative development (18 commits) shows good problem-solving, but some temporary fixes should be cleaned up. Verdict: Approve with required changes (automated tests, iOS version clarification, dead code removal). |
New iPAd split view adaptive design!