Guidance for AI coding agents (Copilot, Cursor, Aider, Claude, etc.) working in this repository. Human readers are welcome, but this file is written for tools.
This repo hosts Stream's SwiftUI Chat SDK for iOS. It builds on the core client and provides SwiftUI-first chat components (views, view models, modifiers) for messaging apps.
Agents should optimize for clean code, follow Apple's SwiftUI guidelines and Swift best practices, accessibility, and high test coverage when changing code. Avoid doing any source-breaking changes without adding deprecations.
- Language: Swift 6.0 (strict concurrency enabled β
swift-tools-version:6.0) - Primary distribution: Swift Package Manager (SPM)
- Project file:
StreamChatSwiftUI.xcodeproj(used for builds and tests; SPM manifest does not declare test targets) - Xcode: 16.x or newer (Apple Silicon supported)
- Platforms / deployment targets: iOS 14+, macOS 11+ (see
Package.swift; do not lower targets without approval) - CI: GitHub Actions + Fastlane (see
.github/workflows/smoke-checks.yml) - Linting: SwiftLint (v0.59.1) β config in
.swiftlint.yml - Formatting: SwiftFormat (v0.58.2) β config in
.swiftformat - Code generation: SwiftGen (v6.5.1) β generates
L10n.swiftfor localization strings - Git hooks: lefthook (
lefthook.yml) β runs SwiftLint fix + SwiftFormat on pre-commit, SwiftLint strict on pre-push - Tool versions are pinned in
Githubfile
- StreamChat and StreamChatCommonUI from
stream-chat-swift(β₯ 5.0.0-beta) - Vendored libraries (do not edit directly):
Sources/StreamChatSwiftUI/StreamNuke/β vendored Nuke image loadingSources/StreamChatSwiftUI/StreamSwiftyGif/β vendored SwiftyGif- Update these via
make update_nuke version=X.Y.Z/make update_swiftygif version=X.Y.Z
Sources/
StreamChatSwiftUI/ # Main SDK: views, view models, theming, utils
ChatChannel/ # Channel view & sub-components
ChatChannelList/ # Channel list view & view model
ChatComposer/ # Message composer
ChatMessageList/ # Message list rendering
ChatThreadList/ # Thread list
CommonViews/ # Shared/reusable SwiftUI views
Generated/ # Auto-generated (L10n.swift, version) β do not edit manually
Resources/ # Localization files (en.lproj, etc.)
StreamNuke/ # Vendored β do not edit
StreamSwiftyGif/ # Vendored β do not edit
Utils/ # Utilities, common helpers
ViewFactory/ # ViewFactory protocol & default implementation
DemoAppSwiftUI/ # Demo/sample app (use to validate UI changes)
StreamChatSwiftUITests/ # Unit & snapshot tests for the SDK
StreamChatSwiftUITestsApp/ # Test harness app for E2E tests
StreamChatSwiftUITestsAppTests/ # E2E / UI automation tests
Scripts/ # Helper scripts (bootstrap, dependency updates, docs)
fastlane/ # Fastlane lanes for CI (build, test, release)
When editing near other packages (e.g., StreamChat or StreamChatUI), prefer extending the SwiftUI layer rather than duplicating logic from dependencies.
When creating new source or resource files, add them to the correct Xcode target(s). Update the project (e.g. project.pbxproj) so each new file is included in the appropriate target's "Compile Sources" (or "Copy Bundle Resources" for assets). Match the target(s) used by sibling files in the same directory (e.g. Sources/StreamChatSwiftUI/ β StreamChatSwiftUI target; StreamChatSwiftUITests/ β StreamChatSwiftUITests target). Omitting target membership will cause build failures or unused files.
- Open the repository in Xcode (root contains
Package.swiftandStreamChatSwiftUI.xcodeproj). - Resolve packages.
- Choose an iOS Simulator (e.g., iPhone 17 Pro) and Build.
Optional: run Scripts/bootstrap.sh to install pinned versions of SwiftLint, SwiftFormat, and SwiftGen, and to set up lefthook git hooks.
The DemoAppSwiftUI target is a fully functional sample app. Prefer running it to validate UI changes. Keep demo configs free of credentials and use placeholders like YOUR_STREAM_KEY.
Available shared schemes (under StreamChatSwiftUI.xcodeproj/xcshareddata/xcschemes/):
StreamChatSwiftUIβ builds the SDK frameworkDemoAppSwiftUIβ builds and runs the demo appStreamChatSwiftUITestsAppβ builds and runs the E2E test harness
Agents must query existing schemes before invoking xcodebuild.
Prefer Xcode for day-to-day work; use CLI for CI parity & automation.
Build (Debug):
xcodebuild \
-scheme StreamChatSwiftUI \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
-configuration Debug build
Run tests:
xcodebuild \
-scheme StreamChatSwiftUI \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
-configuration Debug test
If the device is not available, use one of the active booted devices, like so:
xcodebuild \
-scheme StreamChatSwiftUI \
-destination "platform=iOS Simulator,OS=any,name=$(xcrun simctl list devices booted | grep '(Booted)' | head -1 | sed 's/ (.*)//')" \
-configuration Debug test
SwiftLint (strict mode):
swiftlint lint --config .swiftlint.yml --strict
SwiftFormat (check only β no edits):
swiftformat --config .swiftformat --lint .
SwiftFormat (auto-fix):
swiftformat --config .swiftformat .
Respect .swiftlint.yml and .swiftformat rules. Do not broadly disable rules; scope exceptions and justify in PRs.
CI is driven by Fastlane (see fastlane/Fastfile). Key lanes:
test_uiβ runs unit/snapshot teststest_e2e_mockβ runs E2E tests against a mock serverbuild_demoβ builds the demo appbuild_test_app_and_frameworksβ builds test app and SDK frameworks
The smoke-checks.yml workflow is the primary PR gate. It runs linting, formatting validation, unit tests, E2E tests, and demo app builds.
Do not manually edit files in Sources/StreamChatSwiftUI/Generated/:
L10n.swiftβ generated by SwiftGen from localization.stringsfiles. Edit the.stringssource files instead.SystemEnvironment+Version.swiftβ updated automatically during releases.L10n_template.stencilβ the SwiftGen template for localization generation.
The SDK uses defaultLocalization: "en". String resources live in Sources/StreamChatSwiftUI/Resources/en.lproj/. After modifying .strings files, regenerate L10n.swift by running SwiftGen (or let CI handle it). Always use L10n accessors for user-facing strings rather than raw string literals.
The project uses Swift 6.0 strict concurrency. Many public types and view models are annotated with @MainActor. When adding new code:
- Mark SwiftUI view models and UI-bound types as
@MainActor - Use
Sendableconformances where needed for cross-isolation transfers - Avoid introducing data races; the compiler will enforce actor isolation
Accessibility & UI quality
- Ensure components have accessibility labels, traits, and dynamic type support.
- Support both light/dark mode.
- Use the tokens, colors, fonts, utils etc all from
InjectedValuesExtensions.swift. - When using Figma MCP, all the tokens, colors and fonts are available in the
InjectedValuesExtensions.swiftfile with the same names.
Testing policy
- Add/extend tests in
StreamChatSwiftUITests/Tests/(mirrors the source directory structure) - Test infrastructure (mocks, shared helpers) lives in
StreamChatSwiftUITests/Infrastructure/ - Prefer using
AssertSnapshotfrom StreamChatTestHelpers instead of using the SnapshotTesting framework directly. - Avoid using
AssertAsyncfrom StreamChatTestHelpers, instead useXCTestExpectationdirectly whenever possible.
- The default integration branch is
develop. Feature branches are merged intodevelop. - Update
CHANGELOG.mdunder the# Upcomingsection when making client-facing changes (follow the Keep a Changelog format with### Added,### Fixed,### Changedsubsections).
- Use the Github CLI to create a PR and use the Linear MCP to link the relevant issue assigned to me.
- When creating a PR, the base branch should be the
developbranch. - Make sure that the PR respects the PR template in
.github/PULL_REQUEST_TEMPLATE.md. - Make sure to fill the template with atomic information, do not mention things that were done and then reverted in this same PR.
- Do not write "Made with Cursor" in the PR description.