This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
IcySky is a SwiftUI-based Bluesky client for iOS and macOS built with a modular architecture using Swift Package Manager. The project now requires iOS 26 SDK and targets iOS 26+ exclusively, leveraging the latest SwiftUI APIs. It uses the AT Protocol for Bluesky integration.
# Open in Xcode
open IcySky.xcodeproj
# Build from command line
xcodebuild -project IcySky.xcodeproj -scheme IcySky build
# Build with Tuist (if using Tuist workflows)
tuist build# Run all tests
swift test --package-path Packages/Model
xcodebuild test -scheme FeaturesTests -destination 'platform=iOS Simulator,name=iPhone 16'
# Run specific test targets
swift test --package-path Packages/Features --filter DesignSystemTests
swift test --package-path Packages/Features --filter FeedUITests
swift test --package-path Packages/Model --filter AuthTests
# Run features tests via Xcode test plan
xcodebuild test -scheme FeaturesTests -destination 'platform=iOS Simulator,name=iPhone 16'The codebase is split into two main Swift packages:
Packages/Features/ - UI layer containing SwiftUI views and components:
AuthUI- Authentication screensFeedUI- Feed list and navigationPostUI- Post display and interactionProfileUI- User profile viewsSettingsUI- App settingsNotificationsUI- Notification screensComposerUI- Post compositionMediaUI- Media viewingDesignSystem- Reusable UI components and styling
Packages/Model/ - Core logic and data layer:
Network- AT Protocol client wrapper (BSkyClient)Models- Data models for posts, feeds, profilesAuth- Authentication with keychain storageUser- Current user state managementDestinations- App navigation and routing
- ATProtoKit - Bluesky/AT Protocol client
- AppRouter - Navigation and routing
- KeychainSwift - Secure credential storage
- Nuke/NukeUI - Image loading and caching
- VariableBlur - Advanced UI blur effects
- ViewInspector - SwiftUI testing utilities
- Uses
@Observableclasses with SwiftUI's Observation framework AppStateenum manages authentication and app lifecycle states- Environment-based dependency injection for shared services
- Tab-based navigation with sheet-based modal presentation
The Auth class uses AsyncStream for reactive authentication state management:
- AsyncStream Pattern:
configurationUpdatesemits configuration changes as they happen - Stateless Updates: No timestamp tracking or manual state synchronization
- Automatic Propagation: Login, logout, and session refresh all emit through the same stream
- App Integration: The root app view listens to the stream and updates UI state accordingly
Example:
// In Auth class
public let configurationUpdates: AsyncStream<ATProtocolConfiguration?>
// In IcySkyApp
.task {
for await configuration in auth.configurationUpdates {
if let configuration {
await refreshEnvWith(configuration: configuration)
} else {
appState = .unauthenticated
router.presentedSheet = .auth
}
}
}
### Testing Approach
- Swift Testing framework (modern replacement for XCTest)
- ViewInspector for SwiftUI component testing
- Test targets organized by package modules
- `@MainActor` test classes for UI testing
## Development Notes
### Package Dependencies
Features package depends on Model package. When adding new functionality:
- UI components go in Features package
- Business logic and data models go in Model package
- Cross-package dependencies are explicitly declared in Package.swift
### Design System
All UI components should use the DesignSystem module for consistency:
- Custom colors defined in `Colors.swift` and `Colors.xcassets`
- Reusable components like `Pill`, `Container`, `GlowingRoundedRectangle`
- Custom button styles like `PillButtonStyle`
### Navigation
- Uses AppRouter for declarative navigation
- `RouterDestination` enum defines available destinations
- `SheetDestination` enum defines modal presentations
- Tab structure defined in `AppTab` enum
## SwiftUI Philosophy: No ViewModels
This project follows a strict **no-ViewModel** approach, embracing SwiftUI's native design patterns:
### Core Principles
- **Views as Pure State Expressions**: SwiftUI views are structs designed to be lightweight and disposable
- **Environment-Based Dependency Injection**: Use `@Environment` for shared services instead of manual ViewModel injection
- **Local State Management**: Use `@State` and enum-based view states directly within views
- **Composition Over Abstraction**: Split complex views into smaller components rather than extracting logic to ViewModels
### Patterns to Follow
- Define view states using enums (`.loading`, `.error`, `.loaded`)
- Use `@Environment` to access shared services like `BSkyClient`, `Auth`, `CurrentUser`
- Leverage `.task(id:)` and `.onChange()` modifiers for side effects and state reactions
- Keep business logic in service classes, not in ViewModels
- Test services and models independently; use ViewInspector for view testing when needed
### Example Structure
```swift
struct ExampleView: View {
@Environment(BSkyClient.self) private var client
@State private var viewState: ViewState = .loading
enum ViewState {
case loading
case error(String)
case loaded([Item])
}
var body: some View {
// Pure state expression
}
}This approach results in cleaner, more maintainable code that works with SwiftUI's design rather than against it.
IMPORTANT: After making code changes, you MUST use the XcodeBuildMCP commands to build and verify the project compiles without errors:
-
First, discover available schemes:
mcp__XcodeBuildMCP__list_schems_proj({ projectPath: "/Users/thomas/Documents/Dev/Open Source/IcySky/IcySky.xcodeproj" }) -
Build the IcySky scheme for iOS Simulator:
mcp__XcodeBuildMCP__build_ios_sim_name_proj({ projectPath: "/Users/thomas/Documents/Dev/Open Source/IcySky/IcySky.xcodeproj", scheme: "IcySky", simulatorName: "iPhone 16" }) -
If there are build errors, fix them before considering the task complete.
This ensures code changes are syntactically correct and don't break the build.
IMPORTANT: This project now requires iOS 26 SDK and targets iOS 26+ exclusively. We fully embrace and utilize the latest SwiftUI APIs introduced in June 2025.
Feel free to use any of these new APIs throughout the codebase:
glassEffect(_:in:isEnabled:)- Apply Liquid Glass effects to viewsbuttonStyle(.glass)- Apply Liquid Glass styling to buttonsToolbarSpacer- Create visual breaks in toolbars with Liquid Glass
scrollEdgeEffectStyle(_:for:)- Configure scroll edge effectsbackgroundExtensionEffect()- Duplicate, mirror, and blur views around edges
tabBarMinimizeBehavior(_:)- Control tab bar minimization behavior- Search role for tabs with search field replacing tab bar
TabViewBottomAccessoryPlacement- Adjust accessory view content based on placement
WebViewandWebPage- Full control over browsing experience
draggable(_:_:)- Drag multiple itemsdragContainer(for:id:in:selection:_:)- Container for draggable views
@Animatablemacro - SwiftUI synthesizes custom animatable data properties
Sliderwith automatic tick marks when using step parameterwindowResizeAnchor(_:)- Set window anchor point for resizing
TextEditornow supportsAttributedStringAttributedTextSelection- Handle text selection with attributed textAttributedTextFormattingDefinition- Define text styling in specific contextsFindContext- Create find navigator in text editing views
AssistiveAccess- Support Assistive Access in iOS scenes
Color.ResolvedHDR- RGBA values with HDR headroom information
UIHostingSceneDelegate- Host and present SwiftUI scenes in UIKitNSGestureRecognizerRepresentable- Incorporate gesture recognizers from AppKit
manipulable(coordinateSpace:operations:inertia:isEnabled:onChanged:)- Hand gesture manipulationSurfaceSnappingInfo- Snap volumes and windows to surfacesRemoteImmersiveSpace- Render stereo content from Mac to Apple Vision ProSpatialContainer- 3D layout container- Depth-based modifiers:
aspectRatio3D(_:contentMode:),rotation3DLayout(_:),depthAlignment(_:)
- Leverage these new APIs to enhance the user experience
- Replace legacy implementations with iOS 26 APIs where appropriate
- Take advantage of Liquid Glass effects for modern UI aesthetics
- Use the enhanced text and drag-and-drop capabilities for better interactions
The ComposerUI module implements automatic pattern detection for @mentions and #hashtags using iOS 26's AttributedString APIs. This implementation demonstrates how to work with TextEditor's behavior when implementing real-time pattern detection.
Location: Packages/Features/Sources/ComposerUI/TextProcessing/
Key Components:
ComposerTextPattern.swift- Defines patterns (hashtag, mention, URL) and their attributesComposerTextProcessor.swift- Processes text to detect and mark patternsComposerFormattingDefinition.swift- Applies visual styling using AttributedTextValueConstraint
struct TextPatternAttribute: CodableAttributedStringKey {
typealias Value = ComposerTextPattern
static let name = "IcySky.TextPatternAttribute"
static let inheritedByAddedText: Bool = false
}Due to TextEditor creating fragmented character-by-character runs during typing, we must:
- Create a fresh AttributedString from the plain text on each update
- Apply all pattern attributes to the fresh string
- Replace the entire text with the fresh version
func processText(_ text: inout AttributedString) {
let plainString = String(text.characters)
var freshText = AttributedString(plainString)
// Find and apply patterns to fresh text
// This avoids fragmented runs from TextEditor
}The ComposerFormattingDefinition uses constraints to automatically apply visual styling:
PatternColorConstraint- Applies colors based on text patternURLUnderlineConstraint- Underlines URLs
Applied at the parent view level:
.attributedTextFormattingDefinition(ComposerFormattingDefinition())-
TextEditor Behavior: During active typing, TextEditor creates character-by-character AttributedString runs, causing fragmentation
-
Apple's Approach vs Ours:
- Apple's sample (recipe editor) uses manual attribute application by user selection
- Our approach requires automatic pattern detection during typing
- This fundamental difference necessitates rebuilding the AttributedString
-
Performance Optimization: Process immediately for small changes (typing), debounce for large changes (paste)
-
Pattern Matching: Centralize regex patterns and matching logic in the enum to avoid duplication
- The fresh AttributedString approach is necessary for automatic pattern detection
- AttributedTextFormattingDefinition constraints work elegantly for visual styling
- This pattern can be adapted for other real-time text processing needs (e.g., syntax highlighting)
- The implementation leverages iOS 26's enhanced TextEditor with AttributedString support