From d37a1df45333cd55977c902b939e286c84e7d7de Mon Sep 17 00:00:00 2001 From: Alasdair McCall Date: Fri, 13 Feb 2026 15:59:08 +0000 Subject: [PATCH 1/3] Migrate to tool-agnostic agent structure and optimize documentation - Move agents from .github/agents/ to .ai/agents/ (tool-agnostic location) - Simplify .github/copilot-instructions.md to reference AGENTS.md - Break down AGENTS.md from 966 to 130 lines (87% reduction) - Extract detailed guides to Docs/ (Architecture, GraphQL, Localization, Testing, Development, CodeStyle, QuickReference) - Fix agent file references (correct paths, remove broken anchors) - Move security content from Testing.md to QuickReference.md - Add 'How to Use' section to AGENTS.md for developers and AI agents - Update CLAUDE.md and Docs/Specs/README.md to reference new structure Result: AI agents load minimal context by default, read detailed guides on-demand. --- .../agents/feature-orchestrator.agent.md | 10 +- .../agents/graphql-specialist.agent.md | 4 +- .../agents/ios-feature-developer.agent.md | 7 +- .../agents/localization-specialist.agent.md | 4 +- .../mobile-security-specialist.agent.md | 4 +- {.github => .ai}/agents/spec-writer.agent.md | 0 .../agents/testing-specialist.agent.md | 4 +- .github/copilot-instructions.md | 925 +----------------- AGENTS.md | 131 ++- CLAUDE.md | 2 +- Docs/Architecture.md | 359 +++++++ Docs/CodeStyle.md | 95 ++ Docs/Development.md | 144 +++ Docs/GraphQL.md | 54 + Docs/Localization.md | 36 + Docs/QuickReference.md | 130 +++ Docs/Specs/README.md | 2 +- Docs/Testing.md | 54 + 18 files changed, 1028 insertions(+), 937 deletions(-) rename {.github => .ai}/agents/feature-orchestrator.agent.md (92%) rename {.github => .ai}/agents/graphql-specialist.agent.md (90%) rename {.github => .ai}/agents/ios-feature-developer.agent.md (89%) rename {.github => .ai}/agents/localization-specialist.agent.md (90%) rename {.github => .ai}/agents/mobile-security-specialist.agent.md (92%) rename {.github => .ai}/agents/spec-writer.agent.md (100%) rename {.github => .ai}/agents/testing-specialist.agent.md (93%) create mode 100644 Docs/Architecture.md create mode 100644 Docs/CodeStyle.md create mode 100644 Docs/Development.md create mode 100644 Docs/GraphQL.md create mode 100644 Docs/Localization.md create mode 100644 Docs/QuickReference.md create mode 100644 Docs/Testing.md diff --git a/.github/agents/feature-orchestrator.agent.md b/.ai/agents/feature-orchestrator.agent.md similarity index 92% rename from .github/agents/feature-orchestrator.agent.md rename to .ai/agents/feature-orchestrator.agent.md index 89f62ee8..ec3a299c 100644 --- a/.github/agents/feature-orchestrator.agent.md +++ b/.ai/agents/feature-orchestrator.agent.md @@ -6,7 +6,9 @@ tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'] You are the Feature Orchestrator for the Alfie iOS application. You coordinate specialized agents to take a feature from idea through implementation to production-ready state. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md) for detailed patterns and checklists. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Development process: [Development Guide](../../Docs/Development.md) ## Your Role @@ -154,6 +156,6 @@ Legend: ✅ Complete | 🔄 In Progress | ⬜ Not Started ## References -- [copilot-instructions.md](../copilot-instructions.md) - Detailed patterns -- [Docs/Specs/TEMPLATE.md](../../Docs/Specs/TEMPLATE.md) - Spec template -- [AGENTS.md](../../AGENTS.md) - Project overview +- [AGENTS.md](../../AGENTS.md) - Core rules and project overview +- [Development Guide](../../Docs/Development.md) - Feature implementation checklist +- [Spec Template](../../Docs/Specs/TEMPLATE.md) - Feature specification template diff --git a/.github/agents/graphql-specialist.agent.md b/.ai/agents/graphql-specialist.agent.md similarity index 90% rename from .github/agents/graphql-specialist.agent.md rename to .ai/agents/graphql-specialist.agent.md index 6065936c..296a7dc6 100644 --- a/.github/agents/graphql-specialist.agent.md +++ b/.ai/agents/graphql-specialist.agent.md @@ -6,7 +6,9 @@ tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'] You are a GraphQL specialist for the Alfie iOS application. You handle queries, mutations, fragments, schema extensions, and BFF-to-domain model conversions. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md#graphql--bff-integration) for detailed patterns. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Detailed patterns: [GraphQL Guide](../../Docs/GraphQL.md) ## Workflow diff --git a/.github/agents/ios-feature-developer.agent.md b/.ai/agents/ios-feature-developer.agent.md similarity index 89% rename from .github/agents/ios-feature-developer.agent.md rename to .ai/agents/ios-feature-developer.agent.md index a415e44b..d82bb96b 100644 --- a/.github/agents/ios-feature-developer.agent.md +++ b/.ai/agents/ios-feature-developer.agent.md @@ -6,12 +6,15 @@ tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'] You are an iOS developer implementing features for the Alfie e-commerce app following strict MVVM architecture with flow-based navigation. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md) for detailed patterns, code examples, and the full implementation checklist. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Architecture patterns: [Architecture Guide](../../Docs/Architecture.md) +- Development process: [Development Guide](../../Docs/Development.md) ## Workflow 1. **Read spec** from `Docs/Specs/Features/.md` -2. **Implement** following the [Feature Implementation Checklist](../copilot-instructions.md#feature-implementation-checklist) +2. **Implement** following the [Feature Implementation Checklist](../../Docs/Development.md#feature-implementation-checklist) 3. **Verify**: `./Alfie/scripts/verify.sh` (build + tests) 4. **Iterate** if verification fails diff --git a/.github/agents/localization-specialist.agent.md b/.ai/agents/localization-specialist.agent.md similarity index 90% rename from .github/agents/localization-specialist.agent.md rename to .ai/agents/localization-specialist.agent.md index da800e05..7a8c91c6 100644 --- a/.github/agents/localization-specialist.agent.md +++ b/.ai/agents/localization-specialist.agent.md @@ -6,7 +6,9 @@ tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'] You are a localization specialist managing all user-facing strings in the Alfie iOS application. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md#localization) for detailed patterns. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Detailed patterns: [Localization Guide](../../Docs/Localization.md) ## Workflow diff --git a/.github/agents/mobile-security-specialist.agent.md b/.ai/agents/mobile-security-specialist.agent.md similarity index 92% rename from .github/agents/mobile-security-specialist.agent.md rename to .ai/agents/mobile-security-specialist.agent.md index f2ad7c38..0aa97fcf 100644 --- a/.github/agents/mobile-security-specialist.agent.md +++ b/.ai/agents/mobile-security-specialist.agent.md @@ -6,7 +6,9 @@ tools: ['execute', 'read', 'search', 'web', 'agent', 'todo'] You are a mobile security specialist identifying and preventing security vulnerabilities in the Alfie iOS application. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md#code-review-guidelines) for security review points. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Security review points: [Quick Reference](../../Docs/QuickReference.md#code-review-guidelines) ## Security Checklist diff --git a/.github/agents/spec-writer.agent.md b/.ai/agents/spec-writer.agent.md similarity index 100% rename from .github/agents/spec-writer.agent.md rename to .ai/agents/spec-writer.agent.md diff --git a/.github/agents/testing-specialist.agent.md b/.ai/agents/testing-specialist.agent.md similarity index 93% rename from .github/agents/testing-specialist.agent.md rename to .ai/agents/testing-specialist.agent.md index 59d9849e..e6aa3ed5 100644 --- a/.github/agents/testing-specialist.agent.md +++ b/.ai/agents/testing-specialist.agent.md @@ -6,7 +6,9 @@ tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'] You are a testing specialist ensuring comprehensive test coverage for the Alfie iOS application. -📚 **Reference**: See [copilot-instructions.md](../copilot-instructions.md#testing) for test structure and patterns. +📚 **References**: +- Core rules: [AGENTS.md](../../AGENTS.md) +- Detailed patterns: [Testing Guide](../../Docs/Testing.md) ## Responsibilities diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8fc02f66..5edb0027 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,924 +1,5 @@ -# Alfie iOS - GitHub Copilot Instructions +# Alfie iOS - Project Instructions -This document provides project-specific context and guidelines for GitHub Copilot when working with the Alfie iOS e-commerce application. +**See `AGENTS.md` in the project root for complete project documentation.** ---- - -## Project Overview - -Alfie is a native iOS e-commerce application built with SwiftUI (iOS 16+) following MVVM architecture with a modular, feature-based package structure. The app fetches data from a GraphQL BFF API and includes features like product browsing, search, wishlist, and bag functionality. - ---- - -## Architecture & Code Organization - -### MVVM Pattern with Flow-Based Navigation - -The codebase follows a strict MVVM architecture with feature modules. Each feature is a separate Swift Package module containing its own Views, ViewModels, DependencyContainers, and Navigation (Flow/Routes). - -#### ViewModel -- **Location**: `Alfie/AlfieKit/Sources//UI/ViewModel.swift` -- **Protocol**: Define a protocol in `Alfie/AlfieKit/Sources//Protocols/` for mockability -- **Properties**: Use `@Published` for observable state -- **State Management**: Use `ViewState` or `PaginatedViewState` enums -- **Dependencies**: Inject via DependencyContainer, never access ServiceProvider directly -- **Navigation**: Receive navigation closures from FlowViewModel (e.g., `navigate: (Route) -> Void`) - -**Example Pattern**: -```swift -public class FeatureViewModel: FeatureViewModelProtocol, ObservableObject { - private let dependencies: FeatureDependencyContainer - private let navigate: (FeatureRoute) -> Void - @Published private(set) var state: ViewState - - init( - dependencies: FeatureDependencyContainer, - navigate: @escaping (FeatureRoute) -> Void - ) { - self.dependencies = dependencies - self.navigate = navigate - state = .loading - } - - func didTapItem(_ item: Item) { - navigate(.details(item)) - } -} -``` - -#### DependencyContainer -- **Location**: `Alfie/AlfieKit/Sources//Models/DependencyContainer.swift` -- **Flow Container**: `Alfie/AlfieKit/Sources//Models/FlowDependencyContainer.swift` -- **Purpose**: Filter ServiceProvider dependencies so ViewModels only access what they need -- **Pattern**: Concrete class, no protocol required - -**Example Pattern**: -```swift -public final class FeatureDependencyContainer { - let someService: SomeServiceProtocol - let configurationService: ConfigurationServiceProtocol - - public init(someService: SomeServiceProtocol, configurationService: ConfigurationServiceProtocol) { - self.someService = someService - self.configurationService = configurationService - } -} - -// Flow container aggregates all sub-feature containers -public final class FeatureFlowDependencyContainer { - let featureDependencyContainer: FeatureDependencyContainer - let subFeatureDependencyContainer: SubFeatureDependencyContainer - - public init(...) { ... } -} -``` - -#### View -- **Location**: `Alfie/AlfieKit/Sources//UI/View.swift` -- **Pattern**: Use `@StateObject` for ViewModel, generic over ViewModel protocol -- **State Handling**: Switch on `viewModel.state` to render appropriate UI - -**Example Pattern**: -```swift -struct FeatureView: View { - @StateObject private var viewModel: ViewModel - - init(viewModel: ViewModel) { - _viewModel = StateObject(wrappedValue: viewModel) - } - - var body: some View { - switch viewModel.state { - case .loading: - LoaderView(circleDiameter: .defaultSmall) - case .success(let data): - ContentView(data: data) - case .error(let error): - ErrorView(error: error) - } - } -} -``` - -### State Management - -**ViewState Enum** (for simple loading/success/error flows): -```swift -public enum ViewState { - case loading - case success(Value) - case error(StateError) -} -``` - -**PaginatedViewState Enum** (for paginated lists): -```swift -public enum PaginatedViewState { - case loadingFirstPage(Value) - case loadingNextPage(Value) - case success(Value) - case error(StateError) -} -``` - -### Navigation (Flow-Based Architecture) - -The app uses a flow-based navigation architecture where each feature manages its own navigation stack. - -#### FlowViewModel -- **Location**: `Alfie/AlfieKit/Sources//Navigation/FlowViewModel.swift` -- **Protocol**: Conforms to `FlowViewModelProtocol` from `Model` module -- **Purpose**: Manages NavigationPath, creates ViewModels, handles navigation actions -- **Pattern**: Uses `@Published var path = NavigationPath()` for SwiftUI navigation - -**FlowViewModelProtocol**: -```swift -public protocol FlowViewModelProtocol: ObservableObject { - associatedtype Route: Hashable - - var path: NavigationPath { get set } - var overlayViewPublisher: AnyPublisher { get } - - func navigate(_ route: Route) - func popToRoot() - func pop() -} -``` - -**Example FlowViewModel**: -```swift -public final class FeatureFlowViewModel: FeatureFlowViewModelProtocol { - public typealias Route = FeatureRoute - @Published public var path = NavigationPath() - private let dependencies: FeatureFlowDependencyContainer - - public init(dependencies: FeatureFlowDependencyContainer) { - self.dependencies = dependencies - } - - public func makeFeatureViewModel() -> FeatureViewModel { - FeatureViewModel( - dependencies: dependencies.featureDependencyContainer, - navigate: { [weak self] route in self?.navigate(route) } - ) - } - - public func navigate(_ route: FeatureRoute) { - path.append(route) - } -} -``` - -#### FlowView -- **Location**: `Alfie/AlfieKit/Sources//Navigation/FlowView.swift` -- **Purpose**: Wraps NavigationStack and provides .navigationDestination routing - -**Example FlowView**: -```swift -public struct FeatureFlowView: View { - @StateObject private var viewModel: ViewModel - - public init(viewModel: ViewModel) { - _viewModel = StateObject(wrappedValue: viewModel) - } - - public var body: some View { - NavigationStack(path: $viewModel.path) { - FeatureView(viewModel: viewModel.makeFeatureViewModel()) - .navigationDestination(for: FeatureRoute.self) { route in - route.destination(...) - } - } - } -} -``` - -#### Route Enum -- **Location**: `Alfie/AlfieKit/Sources//Navigation/Route.swift` -- **Purpose**: Define all navigation destinations within a feature -- **Destination Extension**: `Route+Destination.swift` maps routes to views - -**Example Route**: -```swift -public enum FeatureRoute: Hashable { - case details(DetailsConfiguration) - case subFeature(SubFeatureRoute) -} -``` - -#### Tab-Based Navigation -- **AppRoute**: Top-level routing (tabs) -- **TabRoute**: Routes to each tab's flow (`home`, `bag`, `shop`, `wishlist`) -- **Feature Flows**: Each tab has its own FlowView/FlowViewModel - -**Navigation Hierarchy**: -``` -AppFeatureView -├── RootTabView (tab bar) -│ ├── HomeFlowView (home tab) -│ │ ├── HomeView -│ │ ├── ProductListingView -│ │ ├── ProductDetailsView -│ │ └── ... -│ ├── CategorySelectorFlowView (shop tab) -│ ├── WishlistFlowView (wishlist tab) -│ └── BagFlowView (bag tab) -``` - ---- - -## Module Structure (AlfieKit Package) - -The project uses Swift Package Manager with a modular, feature-based architecture in `Alfie/AlfieKit/`: - -### Infrastructure Modules - -- **BFFGraph**: Apollo GraphQL types, queries, schema, and generated mocks (auto-generated API layer) -- **Core**: Core services layer (BFF client, authentication, analytics, persistence, etc.) -- **Model**: Domain models, service protocols, navigation protocols, analytics events -- **Mocks**: Mock implementations for testing (services, features) -- **SharedUI**: Localization (L10n.xcstrings), theme, reusable UI components -- **Utils**: Shared utilities and extensions -- **TestUtils**: Testing utilities (snapshot testing helpers, test schedulers) -- **DeepLink**: Deep linking handling - -### Feature Modules - -Each feature is a self-contained module with its own navigation, views, and view models: - -- **AppFeature**: App shell, tab bar, root navigation (RootTabView, AppFeatureView) -- **Home**: Home tab feature -- **ProductListing**: Product listing/search results -- **ProductDetails**: Product detail pages -- **Search**: Search functionality -- **CategorySelector**: Shop tab with category navigation -- **Wishlist**: Wishlist feature -- **Bag**: Shopping bag feature -- **MyAccount**: User account screens -- **Web**: WebView wrapper for web-based features -- **DebugMenu**: Debug/developer menu (DEBUG builds only) - -### Feature Module Structure - -Each feature module follows this structure: -``` -/ -├── Models/ -│ ├── DependencyContainer.swift -│ └── FlowDependencyContainer.swift -├── Navigation/ -│ ├── FlowView.swift -│ ├── FlowViewModel.swift -│ ├── Route.swift -│ └── Route+Destination.swift -├── Protocols/ -│ ├── ViewModelProtocol.swift -│ └── FlowViewModelProtocol.swift -├── UI/ -│ ├── View.swift -│ └── ViewModel.swift -└── Toolbar/ (optional) - └── +Toolbar.swift -``` - -### Module Dependencies - -- **App target** depends on AppFeature and Core modules -- **Model** is the most foundational (depends only on Utils) -- **Core** depends on Model, Utils, BFFGraph -- **SharedUI** depends on Core, Model, Mocks -- **Feature modules** depend on Model, SharedUI, Core, and other feature modules as needed - ---- - -## GraphQL & BFF Integration - -### Adding a New Query - -1. **Create query file**: `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Queries//Queries.graphql` -2. **Define fragments**: `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Queries//Fragments/Fragment.graphql` -3. **Extend schema**: Add `schema-.graphqls` in `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Schema/` -4. **Generate code**: Run `cd Alfie/scripts && ./run-apollo-codegen.sh` -5. **Create local models**: Add domain models in `Alfie/AlfieKit/Sources/Model/Models/` -6. **Add converters**: Create `+Converter.swift` in `Alfie/AlfieKit/Sources/Core/Services/BFFService/Converters/` -7. **Update BFFClientService**: Add fetch method in `Alfie/AlfieKit/Sources/Core/Services/BFFService/BFFClientService.swift` - -**Query Pattern**: -```graphql -query GetProduct($productId: ID!) { - product(id: $productId) { - ...ProductFragment - } -} -``` - -**Fragment Pattern**: -```graphql -fragment ProductFragment on Product { - id - name - brand { - ...BrandFragment - } - priceRange { - ...PriceRangeFragment - } -} -``` - -**Converter Pattern**: -```swift -extension BFFGraphAPI.ProductFragment { - func convertToProduct() -> Product { - Product( - id: id, - name: name, - brand: brand.fragments.brandFragment.convertToBrand(), - priceRange: priceRange?.fragments.priceRangeFragment.convertToPriceRange() - ) - } -} -``` - -### Updating Existing Queries - -1. Update the `.graphql` file -2. Run `cd Alfie/scripts && ./run-apollo-codegen.sh` -3. Update converters and local models as needed - ---- - -## Localization - -### String Catalog (L10n) - -- **Location**: `Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings` -- **Generated code**: `Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift` (auto-generated by SwiftGen) -- **Naming convention**: ReverseDomain + SnakeCase (e.g., `plp.error_view.title`, `home.search_bar.placeholder`) -- **Generation**: Run build or `swift package --allow-writing-to-package-directory generate-code-for-resources` - -### Adding New Strings - -1. **Open** `L10n.xcstrings` in Xcode -2. **Add entry** manually in base language and all supported languages -3. **Mark for Review** if not officially approved -4. **Build project** to auto-generate `L10n+Generated.swift` -5. **Use in code**: `Text(L10n.Feature.title)` or `Text(L10n.Feature.Subtitle.message("argument"))` - -**Usage Pattern**: -```swift -// Simple string -Text(L10n.Home.title) - -// String with arguments -Text(L10n.Home.LoggedIn.title(username)) - -// Pluralization (defined in String Catalog) -Text(L10n.Plp.NumberOfResults.message(count)) -``` - -### Testing Localization - -- Add test in `Alfie/AlfieKit/Tests/SharedUITests/LocalizationTests.swift` -- Test all pluralization variations (one, other, etc.) -- Validate all supported languages have translations - -**Important**: Never hardcode user-facing strings. Always use `L10n` generated enums. - ---- - -## Style Guide & UI Components - -### Theme System - -- **Colors**: Use themed colors from SharedUI (e.g., `ThemedColor.primary`) -- **Typography**: Use `theme.font` with predefined styles (e.g., `theme.font.header.h3()`) -- **Spacing**: Use `Spacing` enum values (e.g., `Spacing.space200`) -- **Icons**: Use `Icon` enum (e.g., `Icon.home.image`) - -**Example**: -```swift -Text.build(theme.font.header.h3("Title")) -Icon.home.image - .resizable() - .frame(width: 75) -ThemedButton(text: "Action") { - viewModel.didTapButton() -} -.padding(.horizontal, Spacing.space200) -``` - -### Reusable Components - -Located in `Alfie/AlfieKit/Sources/SharedUI/Components/`: - -- **Buttons**: `ThemedButton`, various button styles -- **Search**: `ThemedSearchBarView` with themes (`.soft`, etc.) -- **Product Cards**: Product display components -- **Toolbars**: `toolbarView` modifier for consistent navigation bars -- **Loaders**: `LoaderView` with configurable sizes - -**Always use existing SharedUI components** instead of creating custom UI from scratch. - ---- - -## Services & Dependency Injection - -### ServiceProvider - -- **Location**: `Alfie/Alfie/Service/ServiceProvider.swift` -- **Purpose**: Central registry of all services -- **Access**: Only ViewFactory and top-level app code should access directly -- **Pattern**: Protocol-based services for testability - -**Key Services** (protocols defined in `Model/Services/`): -- `ProductServiceProtocol`: Product data fetching -- `BrandsServiceProtocol`: Brands data -- `AuthenticationServiceProtocol`: User authentication -- `WishlistServiceProtocol`: Wishlist management -- `BagServiceProtocol`: Shopping bag -- `SearchServiceProtocol`: Search functionality -- `RecentsServiceProtocol`: Recent searches -- `DeepLinkServiceProtocol`: Deep linking -- `ConfigurationServiceProtocol`: Feature flags and remote config -- `AlfieAnalyticsTracker`: Analytics events -- `NavigationServiceProtocol`: Navigation state management -- `SessionServiceProtocol`: User session management -- `ReachabilityServiceProtocol`: Network connectivity -- `WebURLProviderProtocol`: Web URL generation -- `WebViewConfigurationServiceProtocol`: WebView configuration - -### Service Implementation Pattern - -Service protocols are defined in `Alfie/AlfieKit/Sources/Model/Services/`: -Service implementations are in `Alfie/AlfieKit/Sources/Core/Services/`: - -```swift -public protocol FeatureServiceProtocol { - func fetchData() async throws -> FeatureData -} - -public final class FeatureService: FeatureServiceProtocol { - private let bffClient: BFFClientServiceProtocol - - public init(bffClient: BFFClientServiceProtocol) { - self.bffClient = bffClient - } - - public func fetchData() async throws -> FeatureData { - let result = try await bffClient.fetchFeature() - return result.convertToFeatureData() - } -} -``` - ---- - -## Code Style & Best Practices - -### SwiftLint Rules - -- **Configuration**: `Alfie/.swiftlint.yml` -- **Trailing commas**: Mandatory -- **Function body length**: Max 200 lines -- **Type body length**: Max 400 lines -- **Identifier names**: Min 2 characters, start with lowercase -- **Fatal errors**: Use `queuedFatalError` instead of `fatalError` (custom rule) - -### Naming Conventions - -- **ViewModels**: `ViewModel.swift` -- **FlowViewModels**: `FlowViewModel.swift` -- **Views**: `View.swift` -- **FlowViews**: `FlowView.swift` -- **DependencyContainers**: `DependencyContainer.swift` -- **FlowDependencyContainers**: `FlowDependencyContainer.swift` -- **Routes**: `Route.swift` -- **Services**: `Service.swift` with `ServiceProtocol` -- **Models**: Descriptive names in `Model` module -- **Localization keys**: ReverseDomain + SnakeCase (e.g., `feature.section.item`) - -### Code Organization (Feature Module) - -``` -/ -├── Models/ -│ ├── DependencyContainer.swift -│ └── FlowDependencyContainer.swift -├── Navigation/ -│ ├── FlowView.swift -│ ├── FlowViewModel.swift -│ ├── Route.swift -│ └── Route+Destination.swift -├── Protocols/ -│ └── ViewModelProtocol.swift -└── UI/ - ├── View.swift - └── ViewModel.swift -``` - -### Preview Pattern - -```swift -#if DEBUG -#Preview("Success") { - FeatureView(viewModel: MockFeatureViewModel(state: .success(mockData))) -} - -#Preview("Loading") { - FeatureView(viewModel: MockFeatureViewModel(state: .loading)) -} -#endif -``` - -**Important**: Previews should be wrapped with `#if DEBUG` (SwiftLint custom rule enforces this). - -### Async/Await Pattern - -- Use `async/await` for asynchronous operations -- Wrap in `Task` when calling from synchronous context -- Services return `async throws` for error handling - -```swift -func viewDidAppear() { - Task { - await loadData() - } -} - -private func loadData() async { - state = .loading - do { - let data = try await dependencies.service.fetchData() - state = .success(data) - } catch { - state = .error(.generic) - } -} -``` - ---- - -## Testing - -### Test Structure - -- **Location**: `Alfie/AlfieKit/Tests/` -- Test directories mirror feature modules: - - **AppFeatureTests**: App shell tests - - **CoreTests**: Core services tests - - **HomeTests**: Home feature tests - - **ProductListingTests**: Product listing tests - - **ProductDetailsTests**: Product details tests - - **SearchTests**: Search tests - - **CategorySelectorTests**: Category selector tests - - **WishlistTests**: Wishlist tests - - **BagTests**: Bag tests - - **SharedUITests**: Localization and UI tests - - **DeepLinkTests**: Deep link tests - - **DebugMenuTests**: Debug menu tests - - **WebTests**: Web view tests - - **MyAccountTests**: Account tests - - **BFFGraphTests**: GraphQL tests - - **UtilsTests**: Utility tests - -### Testing Pattern - -```swift -final class FeatureServiceTests: XCTestCase { - func testFetchDataSuccess() async throws { - // Given - let mockBFFClient = MockBFFClientService() - let service = FeatureService(bffClient: mockBFFClient) - - // When - let result = try await service.fetchData() - - // Then - XCTAssertEqual(result.id, "expected-id") - } -} -``` - -### Mocking - -- **Mock ViewModels**: Located in `Alfie/AlfieKit/Sources/Mocks/Core/Features/` -- **Mock Services**: Located in `Alfie/AlfieKit/Sources/Mocks/Core/Services/` -- **BFF Mocks**: Located in `Alfie/AlfieKit/Sources/BFFGraph/Mocks/` (Apollo-generated) -- **Fixtures**: Located in `Alfie/AlfieKit/Sources/Mocks/Fixtures/` -- **Pattern**: Conform to same protocol as real implementation - -### Snapshot Testing - -- Uses `swift-snapshot-testing` library -- Record mode: Set `record = true` temporarily -- Verify mode: Default behavior - ---- - -## Security & Sensitive Files - -### git-secret - -- **Encrypted files**: Listed in `.gitsecret/paths/mapping.cfg` -- **Decryption**: `git secret reveal` (requires GPG keys) -- **Ignored**: Decrypted files are in `.gitignore` - -**Sensitive file locations**: -- `Alfie/Alfie/Configuration/Debug/GoogleService-Info.plist` -- `Alfie/Alfie/Configuration/Release/GoogleService-Info.plist` - -### Adding Sensitive Files - -```bash -git rm --cached path-to-sensitive-file -git secret add path-to-sensitive-file -git secret hide -``` - -**Never commit unencrypted sensitive files.** - ---- - -## Feature Development Process - -### Spec-Driven Approach ⭐ - -**ALWAYS follow a spec-driven development approach for new features:** - -#### Phase 1: Write the Spec First - -Create a comprehensive spec document in `Docs/Specs/Features/.md`. - -**Spec Location**: `Docs/Specs/Features/` - This directory is automatically indexed by all AI tools (GitHub Copilot, Cursor, Cline) and accessible to developers. - -**Required Sections in Every Spec:** -- **Feature Overview** - High-level description and business goals -- **User Stories** - Who needs this and why -- **Acceptance Criteria** - Clear definition of "done" -- **Data Models** - Structures and relationships (Swift code blocks) -- **API Contracts** - GraphQL queries/mutations with expected response shapes -- **UI/UX Flows** - Screen transitions and user interactions -- **Navigation** - Entry points, exit points, Routes and FlowViewModel methods -- **Localization** - All user-facing strings with their keys -- **Analytics** - Events to track with parameters -- **Edge Cases** - Error scenarios, empty states, loading states -- **Dependencies** - Required services, APIs, other features -- **Testing Strategy** - What tests are needed and where - -**See `Docs/Specs/TEMPLATE.md` for full example structure.** - -#### Phase 2: Break Down Into Tasks - -After the spec is complete: - -1. **Extract Small Tasks** - Break the spec into the smallest possible, independent tasks -2. **Create Task List** - Each task should be completable in a short session - - Example: "Add ProductListingQuery GraphQL query" - - Example: "Implement ProductFragment converter" - - Example: "Create ProductListingViewModel" - -#### Phase 3: Implement Feature (One Task at a Time) - -Tackle tasks **one by one**, following the implementation checklist below. - -**Always refer back to the spec** for requirements. If requirements change during implementation, **update the spec first**, then update code. - -**After implementation, EXECUTE the build command to verify - this is MANDATORY.** - -### Feature Implementation Checklist - -Use this checklist for systematic feature implementation: - -1. ✅ **Create Spec Document** in `Docs/Specs/Features/.md` -2. ✅ **Define Domain Models** in `Alfie/AlfieKit/Sources/Model/Models//` -3. ✅ **Create Service Protocol** in `Alfie/AlfieKit/Sources/Model/Services//` -4. ✅ **Add GraphQL Query** (if API needed): - - Create `Queries.graphql` in `AlfieKit/Sources/BFFGraph/CodeGen/Queries//` - - Create fragments in `Fragments/` subdirectory - - Extend schema in `CodeGen/Schema/schema-.graphqls` -5. ✅ **Run Apollo Codegen**: `cd Alfie/scripts && ./run-apollo-codegen.sh` -6. ✅ **Create Converters** in `Core/Services/BFFService/Converters/+Converter.swift` -7. ✅ **Implement Service** in `Core/Services//` -8. ✅ **Register Service** in `Alfie/Alfie/Service/ServiceProvider.swift` -9. ✅ **Create Feature Module** in `AlfieKit/Sources//`: - - Create `DependencyContainer.swift` in `Models/` - - Create `FlowDependencyContainer.swift` in `Models/` - - Create `ViewModelProtocol.swift` in `Protocols/` - - Create `FlowViewModelProtocol.swift` in `Protocols/` - - Create `Route.swift` in `Navigation/` - - Create `Route+Destination.swift` in `Navigation/` - - Create `FlowView.swift` in `Navigation/` - - Create `FlowViewModel.swift` in `Navigation/` - - Create `View.swift` in `UI/` - - Create `ViewModel.swift` in `UI/` -10. ✅ **Create Mock ViewModel** in `Mocks/Core/Features/MockViewModel.swift` -11. ✅ **Add to Package.swift**: Add new target and product in `AlfieKit/Package.swift` -12. ✅ **Integrate with Navigation**: Add route to parent feature's Route enum -13. ✅ **Add Localization Strings** in `L10n.xcstrings` (all keys from spec) -14. ✅ **Verify** - Execute `./Alfie/scripts/verify.sh` (runs build + tests) -15. ✅ **Verify Against Spec** - Check all acceptance criteria met -16. ✅ **Update Spec Status** - Mark as "Implemented" with PR link and date - ---- - -## 🏗️ Verification - -**Every code change MUST be verified with build + tests.** - -### Verify Command - -```bash -# Recommended: Run full verification (build + tests) -./Alfie/scripts/verify.sh - -# Build only (if you need to iterate on compilation) -./Alfie/scripts/build-for-verification.sh - -# Tests only (after successful build) -./Alfie/scripts/test-for-verification.sh --skip-build -``` - -### Process - -1. Execute `./Alfie/scripts/verify.sh` after completing implementation -2. Wait for "✅ FULL VERIFICATION PASSED" message -3. If build fails: fix errors, re-run -4. If tests fail: fix logic, re-run -5. Only mark task complete after full verification passes - -**Why use the script?** -- Works on all developer machines (no hardcoded simulator IDs) -- Automatically finds available simulator -- Provides clear success/failure messages -- Saves build log for debugging - -### Common Build Errors - -| Error | Fix | -|-------|-----| -| Missing imports | Add `import Model`, `import SharedUI`, `import Core`, etc. | -| Unresolved symbols | Check L10n key typos, missing enum cases | -| Type mismatches | Verify protocol conformance | -| Missing files | Notify user to add files to Xcode project | - ---- - -## 🚫 Xcode Project File Management - -**CRITICAL**: Never edit `Alfie.xcodeproj/project.pbxproj` directly. - -### Files Requiring Xcode Integration - -When creating new `.swift` files in `Alfie/Alfie/` (app target), notify the user: - -``` -⚠️ ACTION REQUIRED: Please add this file to the Xcode project: -1. Open Alfie.xcodeproj in Xcode -2. Right-click the appropriate folder -3. Select "Add Files to Alfie..." -4. Select the file and ensure "Alfie" target is checked -5. Run: ./Alfie/scripts/verify.sh -``` - -### Files Auto-Discovered (No Action Needed) - -- Files in `AlfieKit/Sources/` and `AlfieKit/Tests/` (Swift Package - auto-discovered) -- GraphQL `.graphql` files -- Documentation and scripts - -**Note**: Most new feature code goes in AlfieKit modules and is auto-discovered. - ---- - -## Things to AVOID - -❌ Access `ServiceProvider` from ViewModels (use DependencyContainer) -❌ Hardcode user-facing strings (use `L10n`) -❌ Bypass FlowViewModel for navigation (pass navigation closures from FlowViewModel) -❌ Edit auto-generated files (`L10n+Generated.swift`, `BFFGraph/API/`, `BFFGraph/Mocks/`) -❌ Use `fatalError` (use `queuedFatalError`) -❌ Commit sensitive files unencrypted -❌ Create custom UI without checking SharedUI first -❌ Create ViewModels without protocols -❌ Edit `project.pbxproj` directly -❌ Skip build verification - ---- - -## Quick Reference - -### Key Directories - -``` -Alfie/ -├── Alfie/ # Main app target (minimal code) -│ ├── Views/ # App-specific views (Info only) -│ ├── Service/ # ServiceProvider -│ ├── Delegate/ # AppDelegate -│ └── Configuration/ # App config, URLs, sensitive files -├── AlfieKit/ # Swift Package (feature modules) -│ ├── Sources/ -│ │ ├── AppFeature/ # App shell, tab bar, root navigation -│ │ ├── BFFGraph/ # GraphQL (queries, schema, codegen) -│ │ ├── Bag/ # Bag feature module -│ │ ├── CategorySelector/ # Shop tab feature module -│ │ ├── Core/ # Core services layer -│ │ ├── DebugMenu/ # Debug menu (DEBUG only) -│ │ ├── DeepLink/ # Deep linking -│ │ ├── Home/ # Home feature module -│ │ ├── Mocks/ # Test mocks -│ │ ├── Model/ # Domain models, protocols -│ │ ├── MyAccount/ # Account feature module -│ │ ├── ProductDetails/ # Product details feature module -│ │ ├── ProductListing/ # Product listing feature module -│ │ ├── Search/ # Search feature module -│ │ ├── SharedUI/ # Localization, theme, components -│ │ ├── TestUtils/ # Test helpers -│ │ ├── Utils/ # Utilities -│ │ ├── Web/ # WebView feature module -│ │ └── Wishlist/ # Wishlist feature module -│ └── Tests/ # Unit tests (per module) -└── scripts/ # Build scripts (Apollo codegen) -``` - -### Common Commands - -```bash -# Full verification (build + tests) - ALWAYS RUN AFTER CODE CHANGES -./Alfie/scripts/verify.sh - -# Decrypt sensitive files (requires GPG keys) -git secret reveal - -# Install dependencies -brew bundle install - -# Generate GraphQL code -cd Alfie/scripts && ./run-apollo-codegen.sh - -# Generate localization code (automatic on build, or manually) -swift package --allow-writing-to-package-directory generate-code-for-resources -``` - -### Key Dependencies - -- **Apollo iOS**: GraphQL client (v1.19.0) -- **Firebase**: Analytics, Crashlytics, Remote Config (v11.11.0) -- **Braze**: Marketing automation (v11.9.0) -- **Nuke**: Image loading/caching (v12.8.0) -- **Alicerce**: Utilities, logging (v0.18.0) -- **SwiftGen**: Code generation for resources (v6.6.4-mindera fork) -- **Snapshot Testing**: UI testing (v1.18.3) - ---- - -## Code Review Guidelines - -### PR Review Checklist - -- [ ] **Architecture**: MVVM pattern, DependencyContainer usage, FlowViewModel navigation -- [ ] **Localization**: All strings use L10n -- [ ] **State**: ViewState/PaginatedViewState used correctly -- [ ] **Tests**: ViewModels have unit tests, protocols exist for mocking -- [ ] **Security**: No credentials, Keychain for sensitive data, HTTPS only -- [ ] **GraphQL**: Fragments used, codegen run, no edits to generated files -- [ ] **SwiftLint**: No violations - -### 🔴 Critical (Block Merge) - -- ViewModels accessing `ServiceProvider` directly -- Hardcoded user-facing strings -- Navigation bypassing FlowViewModel -- Missing ViewModel protocols -- Credentials/secrets in code -- State not using `ViewState` enums - -### 🟠 High Priority - -- Missing tests for ViewModels -- GraphQL queries without fragments -- Missing localization translations -- Dependencies not via DependencyContainer - -### Security Review Points - -- No API keys, tokens, passwords in code -- Sensitive data uses Keychain, not UserDefaults -- No PII in logs -- git-secret for sensitive files -- Input validation on deep links - ---- - -## Additional Context - -- **Minimum iOS**: 16.0 -- **Swift Version**: 5.9+ -- **Mock Server**: Separate Alfie-Mocks repo runs locally on localhost:4000 -- **CI/CD**: Work in progress -- **Release Process**: Work in progress - ---- - -This document should be updated when major architectural decisions change or new patterns are introduced. +This file exists for compatibility with GitHub Copilot's `.github/copilot-instructions.md` convention. diff --git a/AGENTS.md b/AGENTS.md index 1003eaaf..7f2b7565 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,130 @@ -# Alfie iOS - AI Agent Instructions +# Alfie iOS - AI Agent System -**Consult @.github/copilot-instructions.md for complete project guidelines.** +This file follows the [AGENTS.md standard](https://agents.md/) and contains essential project context for AI coding assistants. -This file follows the [AGENTS.md standard](https://agents.md/). +--- + +## AI Agents + +Alfie uses specialized AI agents for different development tasks. All agents are defined in `.ai/agents/` using standard markdown with YAML frontmatter, making them compatible with any AI coding assistant. + +### Available Agents + +| Agent | Purpose | File | +|-------|---------|------| +| `feature-orchestrator` | Coordinate full feature development lifecycle | `.ai/agents/feature-orchestrator.agent.md` | +| `spec-writer` | Create comprehensive feature specifications | `.ai/agents/spec-writer.agent.md` | +| `graphql-specialist` | GraphQL queries, mutations, and Apollo codegen | `.ai/agents/graphql-specialist.agent.md` | +| `ios-feature-developer` | MVVM iOS feature implementation | `.ai/agents/ios-feature-developer.agent.md` | +| `localization-specialist` | L10n string catalog management | `.ai/agents/localization-specialist.agent.md` | +| `testing-specialist` | Unit tests, snapshot tests, and test coverage | `.ai/agents/testing-specialist.agent.md` | +| `mobile-security-specialist` | Security audits and vulnerability identification | `.ai/agents/mobile-security-specialist.agent.md` | + +### Usage + +AI tools should read the agent definition from `.ai/agents/.agent.md` and follow the instructions within. + +**Example:** +``` +Acting as the ios-feature-developer agent (see .ai/agents/ios-feature-developer.agent.md), +implement the Product Details feature following the spec in Docs/Specs/Features/ProductDetails.md +``` + +--- + +## Project Essentials + +**Alfie** is a native iOS e-commerce application built with: +- **SwiftUI** (iOS 16+) +- **MVVM Architecture** with Flow-based navigation +- **Swift Package Manager** modular structure (`AlfieKit/`) +- **GraphQL BFF API** (Apollo iOS client) + +### Core Technologies +- Swift 5.9+ +- SwiftUI with `@StateObject` and `@Published` +- Combine for reactive programming +- Apollo iOS for GraphQL +- Firebase (Analytics, Crashlytics, Remote Config) + +--- + +## Critical Rules + +### ✅ ALWAYS + +- Use `ViewState` or `PaginatedViewState` enums for state +- Inject dependencies via `DependencyContainer` (never access `ServiceProvider` from ViewModels) +- Use `L10n` for all user-facing strings (from `L10n.xcstrings`) +- Define protocols for all ViewModels (for mockability) +- Pass navigation closures from `FlowViewModel` to `ViewModel` +- Run `./Alfie/scripts/verify.sh` after every code change + +### ❌ NEVER + +- Access `ServiceProvider` directly from ViewModels +- Hardcode user-facing strings +- Bypass `FlowViewModel` for navigation +- Edit auto-generated files (`L10n+Generated.swift`, `BFFGraph/API/`, `BFFGraph/Mocks/`) +- Use `fatalError` (use `queuedFatalError` instead) +- Edit `Alfie.xcodeproj/project.pbxproj` directly +- Skip build verification +- Commit sensitive files unencrypted + +--- + +## Verification (MANDATORY) + +**Every code change MUST be verified:** + +```bash +./Alfie/scripts/verify.sh +``` + +This runs build + tests. Only mark work complete after **"✅ FULL VERIFICATION PASSED"**. + +--- + +## Detailed Documentation + +When you need specific guidance, read the appropriate guide: + +- **Architecture & MVVM** → `Docs/Architecture.md` +- **GraphQL Integration** → `Docs/GraphQL.md` +- **Localization (L10n)** → `Docs/Localization.md` +- **Testing & Mocking** → `Docs/Testing.md` +- **Feature Development Process** → `Docs/Development.md` +- **Code Style & Conventions** → `Docs/CodeStyle.md` +- **Quick Reference (dirs, commands, deps)** → `Docs/QuickReference.md` +- **Feature Spec Template** → `Docs/Specs/TEMPLATE.md` + +--- + +## How to Use This Documentation + +### For Developers + +**Quick start:** +1. Read this file (AGENTS.md) for core rules +2. Consult specific guides in `Docs/` as needed +3. Use `Docs/QuickReference.md` for commands and directory structure + +**Common scenarios:** +- **New to project?** Start with `Docs/Architecture.md` +- **Implementing feature?** Follow `Docs/Development.md` +- **Adding GraphQL?** See `Docs/GraphQL.md` +- **Need quick lookup?** Use `Docs/QuickReference.md` + +### For AI Agents + +AI agents automatically read AGENTS.md and load detailed guides on-demand based on task context. + +**Agent workflow:** +1. Load AGENTS.md (core rules) +2. Identify task type +3. Load relevant guide (e.g., GraphQL.md for API work) +4. Execute task following guidelines + +--- + +**This minimal document provides core context. Read detailed guides only when needed for specific tasks.** diff --git a/CLAUDE.md b/CLAUDE.md index 748988e6..36b350c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,5 @@ # Alfie iOS - Claude Code Instructions -**Consult @.github/copilot-instructions.md for complete project guidelines.** +**Consult @AGENTS.md for complete project guidelines.** This file provides guidance to Claude Code (claude.ai/code) when working with this repository. diff --git a/Docs/Architecture.md b/Docs/Architecture.md new file mode 100644 index 00000000..3a5a1b13 --- /dev/null +++ b/Docs/Architecture.md @@ -0,0 +1,359 @@ +# Architecture & Code Organization + +## MVVM Pattern with Flow-Based Navigation + +The codebase follows a strict MVVM architecture with feature modules. Each feature is a separate Swift Package module containing its own Views, ViewModels, DependencyContainers, and Navigation (Flow/Routes). + +### ViewModel +- **Location**: `Alfie/AlfieKit/Sources//UI/ViewModel.swift` +- **Protocol**: Define a protocol in `Alfie/AlfieKit/Sources//Protocols/` for mockability +- **Properties**: Use `@Published` for observable state +- **State Management**: Use `ViewState` or `PaginatedViewState` enums +- **Dependencies**: Inject via DependencyContainer, never access ServiceProvider directly +- **Navigation**: Receive navigation closures from FlowViewModel (e.g., `navigate: (Route) -> Void`) + +**Example Pattern**: +```swift +public class FeatureViewModel: FeatureViewModelProtocol, ObservableObject { + private let dependencies: FeatureDependencyContainer + private let navigate: (FeatureRoute) -> Void + @Published private(set) var state: ViewState + + init( + dependencies: FeatureDependencyContainer, + navigate: @escaping (FeatureRoute) -> Void + ) { + self.dependencies = dependencies + self.navigate = navigate + state = .loading + } + + func didTapItem(_ item: Item) { + navigate(.details(item)) + } +} +``` + +### DependencyContainer +- **Location**: `Alfie/AlfieKit/Sources//Models/DependencyContainer.swift` +- **Flow Container**: `Alfie/AlfieKit/Sources//Models/FlowDependencyContainer.swift` +- **Purpose**: Filter ServiceProvider dependencies so ViewModels only access what they need +- **Pattern**: Concrete class, no protocol required + +**Example Pattern**: +```swift +public final class FeatureDependencyContainer { + let someService: SomeServiceProtocol + let configurationService: ConfigurationServiceProtocol + + public init(someService: SomeServiceProtocol, configurationService: ConfigurationServiceProtocol) { + self.someService = someService + self.configurationService = configurationService + } +} + +// Flow container aggregates all sub-feature containers +public final class FeatureFlowDependencyContainer { + let featureDependencyContainer: FeatureDependencyContainer + let subFeatureDependencyContainer: SubFeatureDependencyContainer + + public init(...) { ... } +} +``` + +### View +- **Location**: `Alfie/AlfieKit/Sources//UI/View.swift` +- **Pattern**: Use `@StateObject` for ViewModel, generic over ViewModel protocol +- **State Handling**: Switch on `viewModel.state` to render appropriate UI + +**Example Pattern**: +```swift +struct FeatureView: View { + @StateObject private var viewModel: ViewModel + + init(viewModel: ViewModel) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + var body: some View { + switch viewModel.state { + case .loading: + LoaderView(circleDiameter: .defaultSmall) + case .success(let data): + ContentView(data: data) + case .error(let error): + ErrorView(error: error) + } + } +} +``` + +## State Management + +**ViewState Enum** (for simple loading/success/error flows): +```swift +public enum ViewState { + case loading + case success(Value) + case error(StateError) +} +``` + +**PaginatedViewState Enum** (for paginated lists): +```swift +public enum PaginatedViewState { + case loadingFirstPage(Value) + case loadingNextPage(Value) + case success(Value) + case error(StateError) +} +``` + +## Navigation (Flow-Based Architecture) + +The app uses a flow-based navigation architecture where each feature manages its own navigation stack. + +### FlowViewModel +- **Location**: `Alfie/AlfieKit/Sources//Navigation/FlowViewModel.swift` +- **Protocol**: Conforms to `FlowViewModelProtocol` from `Model` module +- **Purpose**: Manages NavigationPath, creates ViewModels, handles navigation actions +- **Pattern**: Uses `@Published var path = NavigationPath()` for SwiftUI navigation + +**FlowViewModelProtocol**: +```swift +public protocol FlowViewModelProtocol: ObservableObject { + associatedtype Route: Hashable + + var path: NavigationPath { get set } + var overlayViewPublisher: AnyPublisher { get } + + func navigate(_ route: Route) + func popToRoot() + func pop() +} +``` + +**Example FlowViewModel**: +```swift +public final class FeatureFlowViewModel: FeatureFlowViewModelProtocol { + public typealias Route = FeatureRoute + @Published public var path = NavigationPath() + private let dependencies: FeatureFlowDependencyContainer + + public init(dependencies: FeatureFlowDependencyContainer) { + self.dependencies = dependencies + } + + public func makeFeatureViewModel() -> FeatureViewModel { + FeatureViewModel( + dependencies: dependencies.featureDependencyContainer, + navigate: { [weak self] route in self?.navigate(route) } + ) + } + + public func navigate(_ route: FeatureRoute) { + path.append(route) + } +} +``` + +### FlowView +- **Location**: `Alfie/AlfieKit/Sources//Navigation/FlowView.swift` +- **Purpose**: Wraps NavigationStack and provides .navigationDestination routing + +**Example FlowView**: +```swift +public struct FeatureFlowView: View { + @StateObject private var viewModel: ViewModel + + public init(viewModel: ViewModel) { + _viewModel = StateObject(wrappedValue: viewModel) + } + + public var body: some View { + NavigationStack(path: $viewModel.path) { + FeatureView(viewModel: viewModel.makeFeatureViewModel()) + .navigationDestination(for: FeatureRoute.self) { route in + route.destination(...) + } + } + } +} +``` + +### Route Enum +- **Location**: `Alfie/AlfieKit/Sources//Navigation/Route.swift` +- **Purpose**: Define all navigation destinations within a feature +- **Destination Extension**: `Route+Destination.swift` maps routes to views + +**Example Route**: +```swift +public enum FeatureRoute: Hashable { + case details(DetailsConfiguration) + case subFeature(SubFeatureRoute) +} +``` + +### Tab-Based Navigation +- **AppRoute**: Top-level routing (tabs) +- **TabRoute**: Routes to each tab's flow (`home`, `bag`, `shop`, `wishlist`) +- **Feature Flows**: Each tab has its own FlowView/FlowViewModel + +**Navigation Hierarchy**: +``` +AppFeatureView +├── RootTabView (tab bar) +│ ├── HomeFlowView (home tab) +│ │ ├── HomeView +│ │ ├── ProductListingView +│ │ ├── ProductDetailsView +│ │ └── ... +│ ├── CategorySelectorFlowView (shop tab) +│ ├── WishlistFlowView (wishlist tab) +│ └── BagFlowView (bag tab) +``` + +## Module Structure (AlfieKit Package) + +The project uses Swift Package Manager with a modular, feature-based architecture in `Alfie/AlfieKit/`: + +### Infrastructure Modules + +- **BFFGraph**: Apollo GraphQL types, queries, schema, and generated mocks (auto-generated API layer) +- **Core**: Core services layer (BFF client, authentication, analytics, persistence, etc.) +- **Model**: Domain models, service protocols, navigation protocols, analytics events +- **Mocks**: Mock implementations for testing (services, features) +- **SharedUI**: Localization (L10n.xcstrings), theme, reusable UI components +- **Utils**: Shared utilities and extensions +- **TestUtils**: Testing utilities (snapshot testing helpers, test schedulers) +- **DeepLink**: Deep linking handling + +### Feature Modules + +Each feature is a self-contained module with its own navigation, views, and view models: + +- **AppFeature**: App shell, tab bar, root navigation (RootTabView, AppFeatureView) +- **Home**: Home tab feature +- **ProductListing**: Product listing/search results +- **ProductDetails**: Product detail pages +- **Search**: Search functionality +- **CategorySelector**: Shop tab with category navigation +- **Wishlist**: Wishlist feature +- **Bag**: Shopping bag feature +- **MyAccount**: User account screens +- **Web**: WebView wrapper for web-based features +- **DebugMenu**: Debug/developer menu (DEBUG builds only) + +### Feature Module Structure + +Each feature module follows this structure: +``` +/ +├── Models/ +│ ├── DependencyContainer.swift +│ └── FlowDependencyContainer.swift +├── Navigation/ +│ ├── FlowView.swift +│ ├── FlowViewModel.swift +│ ├── Route.swift +│ └── Route+Destination.swift +├── Protocols/ +│ ├── ViewModelProtocol.swift +│ └── FlowViewModelProtocol.swift +├── UI/ +│ ├── View.swift +│ └── ViewModel.swift +└── Toolbar/ (optional) + └── +Toolbar.swift +``` + +### Module Dependencies + +- **App target** depends on AppFeature and Core modules +- **Model** is the most foundational (depends only on Utils) +- **Core** depends on Model, Utils, BFFGraph +- **SharedUI** depends on Core, Model, Mocks +- **Feature modules** depend on Model, SharedUI, Core, and other feature modules as needed + +## Services & Dependency Injection + +### ServiceProvider + +- **Location**: `Alfie/Alfie/Service/ServiceProvider.swift` +- **Purpose**: Central registry of all services +- **Access**: Only ViewFactory and top-level app code should access directly +- **Pattern**: Protocol-based services for testability + +**Key Services** (protocols defined in `Model/Services/`): +- `ProductServiceProtocol`: Product data fetching +- `BrandsServiceProtocol`: Brands data +- `AuthenticationServiceProtocol`: User authentication +- `WishlistServiceProtocol`: Wishlist management +- `BagServiceProtocol`: Shopping bag +- `SearchServiceProtocol`: Search functionality +- `RecentsServiceProtocol`: Recent searches +- `DeepLinkServiceProtocol`: Deep linking +- `ConfigurationServiceProtocol`: Feature flags and remote config +- `AlfieAnalyticsTracker`: Analytics events +- `NavigationServiceProtocol`: Navigation state management +- `SessionServiceProtocol`: User session management +- `ReachabilityServiceProtocol`: Network connectivity +- `WebURLProviderProtocol`: Web URL generation +- `WebViewConfigurationServiceProtocol`: WebView configuration + +### Service Implementation Pattern + +Service protocols are defined in `Alfie/AlfieKit/Sources/Model/Services/`: +Service implementations are in `Alfie/AlfieKit/Sources/Core/Services/`: + +```swift +public protocol FeatureServiceProtocol { + func fetchData() async throws -> FeatureData +} + +public final class FeatureService: FeatureServiceProtocol { + private let bffClient: BFFClientServiceProtocol + + public init(bffClient: BFFClientServiceProtocol) { + self.bffClient = bffClient + } + + public func fetchData() async throws -> FeatureData { + let result = try await bffClient.fetchFeature() + return result.convertToFeatureData() + } +} +``` + +## Style Guide & UI Components + +### Theme System + +- **Colors**: Use themed colors from SharedUI (e.g., `ThemedColor.primary`) +- **Typography**: Use `theme.font` with predefined styles (e.g., `theme.font.header.h3()`) +- **Spacing**: Use `Spacing` enum values (e.g., `Spacing.space200`) +- **Icons**: Use `Icon` enum (e.g., `Icon.home.image`) + +**Example**: +```swift +Text.build(theme.font.header.h3("Title")) +Icon.home.image + .resizable() + .frame(width: 75) +ThemedButton(text: "Action") { + viewModel.didTapButton() +} +.padding(.horizontal, Spacing.space200) +``` + +### Reusable Components + +Located in `Alfie/AlfieKit/Sources/SharedUI/Components/`: + +- **Buttons**: `ThemedButton`, various button styles +- **Search**: `ThemedSearchBarView` with themes (`.soft`, etc.) +- **Product Cards**: Product display components +- **Toolbars**: `toolbarView` modifier for consistent navigation bars +- **Loaders**: `LoaderView` with configurable sizes + +**Always use existing SharedUI components** instead of creating custom UI from scratch. diff --git a/Docs/CodeStyle.md b/Docs/CodeStyle.md new file mode 100644 index 00000000..a4250b5a --- /dev/null +++ b/Docs/CodeStyle.md @@ -0,0 +1,95 @@ +# Code Style & Best Practices + +## SwiftLint Rules + +- **Configuration**: `Alfie/.swiftlint.yml` +- **Trailing commas**: Mandatory +- **Function body length**: Max 200 lines +- **Type body length**: Max 400 lines +- **Identifier names**: Min 2 characters, start with lowercase +- **Fatal errors**: Use `queuedFatalError` instead of `fatalError` (custom rule) + +## Naming Conventions + +- **ViewModels**: `ViewModel.swift` +- **FlowViewModels**: `FlowViewModel.swift` +- **Views**: `View.swift` +- **FlowViews**: `FlowView.swift` +- **DependencyContainers**: `DependencyContainer.swift` +- **FlowDependencyContainers**: `FlowDependencyContainer.swift` +- **Routes**: `Route.swift` +- **Services**: `Service.swift` with `ServiceProtocol` +- **Models**: Descriptive names in `Model` module +- **Localization keys**: ReverseDomain + SnakeCase (e.g., `feature.section.item`) + +## Code Organization (Feature Module) + +``` +/ +├── Models/ +│ ├── DependencyContainer.swift +│ └── FlowDependencyContainer.swift +├── Navigation/ +│ ├── FlowView.swift +│ ├── FlowViewModel.swift +│ ├── Route.swift +│ └── Route+Destination.swift +├── Protocols/ +│ └── ViewModelProtocol.swift +└── UI/ + ├── View.swift + └── ViewModel.swift +``` + +## Preview Pattern + +```swift +#if DEBUG +#Preview("Success") { + FeatureView(viewModel: MockFeatureViewModel(state: .success(mockData))) +} + +#Preview("Loading") { + FeatureView(viewModel: MockFeatureViewModel(state: .loading)) +} +#endif +``` + +**Important**: Previews should be wrapped with `#if DEBUG` (SwiftLint custom rule enforces this). + +## Async/Await Pattern + +- Use `async/await` for asynchronous operations +- Wrap in `Task` when calling from synchronous context +- Services return `async throws` for error handling + +```swift +func viewDidAppear() { + Task { + await loadData() + } +} + +private func loadData() async { + state = .loading + do { + let data = try await dependencies.service.fetchData() + state = .success(data) + } catch { + state = .error(.generic) + } +} +``` + +## Things to AVOID + +❌ Access `ServiceProvider` from ViewModels (use DependencyContainer) +❌ Hardcode user-facing strings (use `L10n`) +❌ Bypass FlowViewModel for navigation (pass navigation closures from FlowViewModel) +❌ Edit auto-generated files (`L10n+Generated.swift`, `BFFGraph/API/`, `BFFGraph/Mocks/`) +❌ Use `fatalError` (use `queuedFatalError`) +❌ Commit sensitive files unencrypted +❌ Create custom UI without checking SharedUI first +❌ Create ViewModels without protocols +❌ Edit `project.pbxproj` directly +❌ Skip build verification diff --git a/Docs/Development.md b/Docs/Development.md new file mode 100644 index 00000000..93681e66 --- /dev/null +++ b/Docs/Development.md @@ -0,0 +1,144 @@ +# Feature Development Process + +## Spec-Driven Approach ⭐ + +**ALWAYS follow a spec-driven development approach for new features:** + +### Phase 1: Write the Spec First + +Create a comprehensive spec document in `Docs/Specs/Features/.md`. + +**Spec Location**: `Docs/Specs/Features/` - This directory is automatically indexed by all AI tools and accessible to developers. + +**Required Sections in Every Spec:** +- **Feature Overview** - High-level description and business goals +- **User Stories** - Who needs this and why +- **Acceptance Criteria** - Clear definition of "done" +- **Data Models** - Structures and relationships (Swift code blocks) +- **API Contracts** - GraphQL queries/mutations with expected response shapes +- **UI/UX Flows** - Screen transitions and user interactions +- **Navigation** - Entry points, exit points, Routes and FlowViewModel methods +- **Localization** - All user-facing strings with their keys +- **Analytics** - Events to track with parameters +- **Edge Cases** - Error scenarios, empty states, loading states +- **Dependencies** - Required services, APIs, other features +- **Testing Strategy** - What tests are needed and where + +**See `Docs/Specs/TEMPLATE.md` for full example structure.** + +### Phase 2: Break Down Into Tasks + +After the spec is complete: + +1. **Extract Small Tasks** - Break the spec into the smallest possible, independent tasks +2. **Create Task List** - Each task should be completable in a short session + - Example: "Add ProductListingQuery GraphQL query" + - Example: "Implement ProductFragment converter" + - Example: "Create ProductListingViewModel" + +### Phase 3: Implement Feature (One Task at a Time) + +Tackle tasks **one by one**, following the implementation checklist below. + +**Always refer back to the spec** for requirements. If requirements change during implementation, **update the spec first**, then update code. + +**After implementation, EXECUTE the build command to verify - this is MANDATORY.** + +## Feature Implementation Checklist + +Use this checklist for systematic feature implementation: + +1. ✅ **Create Spec Document** in `Docs/Specs/Features/.md` +2. ✅ **Define Domain Models** in `Alfie/AlfieKit/Sources/Model/Models//` +3. ✅ **Create Service Protocol** in `Alfie/AlfieKit/Sources/Model/Services//` +4. ✅ **Add GraphQL Query** (if API needed): + - Create `Queries.graphql` in `AlfieKit/Sources/BFFGraph/CodeGen/Queries//` + - Create fragments in `Fragments/` subdirectory + - Extend schema in `CodeGen/Schema/schema-.graphqls` +5. ✅ **Run Apollo Codegen**: `cd Alfie/scripts && ./run-apollo-codegen.sh` +6. ✅ **Create Converters** in `Core/Services/BFFService/Converters/+Converter.swift` +7. ✅ **Implement Service** in `Core/Services//` +8. ✅ **Register Service** in `Alfie/Alfie/Service/ServiceProvider.swift` +9. ✅ **Create Feature Module** in `AlfieKit/Sources//`: + - Create `DependencyContainer.swift` in `Models/` + - Create `FlowDependencyContainer.swift` in `Models/` + - Create `ViewModelProtocol.swift` in `Protocols/` + - Create `FlowViewModelProtocol.swift` in `Protocols/` + - Create `Route.swift` in `Navigation/` + - Create `Route+Destination.swift` in `Navigation/` + - Create `FlowView.swift` in `Navigation/` + - Create `FlowViewModel.swift` in `Navigation/` + - Create `View.swift` in `UI/` + - Create `ViewModel.swift` in `UI/` +10. ✅ **Create Mock ViewModel** in `Mocks/Core/Features/MockViewModel.swift` +11. ✅ **Add to Package.swift**: Add new target and product in `AlfieKit/Package.swift` +12. ✅ **Integrate with Navigation**: Add route to parent feature's Route enum +13. ✅ **Add Localization Strings** in `L10n.xcstrings` (all keys from spec) +14. ✅ **Verify** - Execute `./Alfie/scripts/verify.sh` (runs build + tests) +15. ✅ **Verify Against Spec** - Check all acceptance criteria met +16. ✅ **Update Spec Status** - Mark as "Implemented" with PR link and date + +## 🏗️ Verification + +**Every code change MUST be verified with build + tests.** + +### Verify Command + +```bash +# Recommended: Run full verification (build + tests) +./Alfie/scripts/verify.sh + +# Build only (if you need to iterate on compilation) +./Alfie/scripts/build-for-verification.sh + +# Tests only (after successful build) +./Alfie/scripts/test-for-verification.sh --skip-build +``` + +### Process + +1. Execute `./Alfie/scripts/verify.sh` after completing implementation +2. Wait for "✅ FULL VERIFICATION PASSED" message +3. If build fails: fix errors, re-run +4. If tests fail: fix logic, re-run +5. Only mark task complete after full verification passes + +**Why use the script?** +- Works on all developer machines (no hardcoded simulator IDs) +- Automatically finds available simulator +- Provides clear success/failure messages +- Saves build log for debugging + +### Common Build Errors + +| Error | Fix | +|-------|-----| +| Missing imports | Add `import Model`, `import SharedUI`, `import Core`, etc. | +| Unresolved symbols | Check L10n key typos, missing enum cases | +| Type mismatches | Verify protocol conformance | +| Missing files | Notify user to add files to Xcode project | + +## 🚫 Xcode Project File Management + +**CRITICAL**: Never edit `Alfie.xcodeproj/project.pbxproj` directly. + +### Files Requiring Xcode Integration + +When creating new `.swift` files in `Alfie/Alfie/` (app target), notify the user: + +``` +⚠️ ACTION REQUIRED: Please add this file to the Xcode project: +1. Open Alfie.xcodeproj in Xcode +2. Right-click the appropriate folder +3. Select "Add Files to Alfie..." +4. Select the file and ensure "Alfie" target is checked +5. Run: ./Alfie/scripts/verify.sh +``` + +### Files Auto-Discovered (No Action Needed) + +- Files in `AlfieKit/Sources/` and `AlfieKit/Tests/` (Swift Package - auto-discovered) +- GraphQL `.graphql` files +- Documentation and scripts + +**Note**: Most new feature code goes in AlfieKit modules and is auto-discovered. diff --git a/Docs/GraphQL.md b/Docs/GraphQL.md new file mode 100644 index 00000000..293cf214 --- /dev/null +++ b/Docs/GraphQL.md @@ -0,0 +1,54 @@ +# GraphQL & BFF Integration + +## Adding a New Query + +1. **Create query file**: `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Queries//Queries.graphql` +2. **Define fragments**: `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Queries//Fragments/Fragment.graphql` +3. **Extend schema**: Add `schema-.graphqls` in `Alfie/AlfieKit/Sources/BFFGraph/CodeGen/Schema/` +4. **Generate code**: Run `cd Alfie/scripts && ./run-apollo-codegen.sh` +5. **Create local models**: Add domain models in `Alfie/AlfieKit/Sources/Model/Models/` +6. **Add converters**: Create `+Converter.swift` in `Alfie/AlfieKit/Sources/Core/Services/BFFService/Converters/` +7. **Update BFFClientService**: Add fetch method in `Alfie/AlfieKit/Sources/Core/Services/BFFService/BFFClientService.swift` + +**Query Pattern**: +```graphql +query GetProduct($productId: ID!) { + product(id: $productId) { + ...ProductFragment + } +} +``` + +**Fragment Pattern**: +```graphql +fragment ProductFragment on Product { + id + name + brand { + ...BrandFragment + } + priceRange { + ...PriceRangeFragment + } +} +``` + +**Converter Pattern**: +```swift +extension BFFGraphAPI.ProductFragment { + func convertToProduct() -> Product { + Product( + id: id, + name: name, + brand: brand.fragments.brandFragment.convertToBrand(), + priceRange: priceRange?.fragments.priceRangeFragment.convertToPriceRange() + ) + } +} +``` + +## Updating Existing Queries + +1. Update the `.graphql` file +2. Run `cd Alfie/scripts && ./run-apollo-codegen.sh` +3. Update converters and local models as needed diff --git a/Docs/Localization.md b/Docs/Localization.md new file mode 100644 index 00000000..71946a0a --- /dev/null +++ b/Docs/Localization.md @@ -0,0 +1,36 @@ +# Localization + +## String Catalog (L10n) + +- **Location**: `Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings` +- **Generated code**: `Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift` (auto-generated by SwiftGen) +- **Naming convention**: ReverseDomain + SnakeCase (e.g., `plp.error_view.title`, `home.search_bar.placeholder`) +- **Generation**: Run build or `swift package --allow-writing-to-package-directory generate-code-for-resources` + +## Adding New Strings + +1. **Open** `L10n.xcstrings` in Xcode +2. **Add entry** manually in base language and all supported languages +3. **Mark for Review** if not officially approved +4. **Build project** to auto-generate `L10n+Generated.swift` +5. **Use in code**: `Text(L10n.Feature.title)` or `Text(L10n.Feature.Subtitle.message("argument"))` + +**Usage Pattern**: +```swift +// Simple string +Text(L10n.Home.title) + +// String with arguments +Text(L10n.Home.LoggedIn.title(username)) + +// Pluralization (defined in String Catalog) +Text(L10n.Plp.NumberOfResults.message(count)) +``` + +## Testing Localization + +- Add test in `Alfie/AlfieKit/Tests/SharedUITests/LocalizationTests.swift` +- Test all pluralization variations (one, other, etc.) +- Validate all supported languages have translations + +**Important**: Never hardcode user-facing strings. Always use `L10n` generated enums. diff --git a/Docs/QuickReference.md b/Docs/QuickReference.md new file mode 100644 index 00000000..116bd43f --- /dev/null +++ b/Docs/QuickReference.md @@ -0,0 +1,130 @@ +# Quick Reference + +## Key Directories + +``` +Alfie/ +├── Alfie/ # Main app target (minimal code) +│ ├── Views/ # App-specific views (Info only) +│ ├── Service/ # ServiceProvider +│ ├── Delegate/ # AppDelegate +│ └── Configuration/ # App config, URLs, sensitive files +├── AlfieKit/ # Swift Package (feature modules) +│ ├── Sources/ +│ │ ├── AppFeature/ # App shell, tab bar, root navigation +│ │ ├── BFFGraph/ # GraphQL (queries, schema, codegen) +│ │ ├── Bag/ # Bag feature module +│ │ ├── CategorySelector/ # Shop tab feature module +│ │ ├── Core/ # Core services layer +│ │ ├── DebugMenu/ # Debug menu (DEBUG only) +│ │ ├── DeepLink/ # Deep linking +│ │ ├── Home/ # Home feature module +│ │ ├── Mocks/ # Test mocks +│ │ ├── Model/ # Domain models, protocols +│ │ ├── MyAccount/ # Account feature module +│ │ ├── ProductDetails/ # Product details feature module +│ │ ├── ProductListing/ # Product listing feature module +│ │ ├── Search/ # Search feature module +│ │ ├── SharedUI/ # Localization, theme, components +│ │ ├── TestUtils/ # Test helpers +│ │ ├── Utils/ # Utilities +│ │ ├── Web/ # WebView feature module +│ │ └── Wishlist/ # Wishlist feature module +│ └── Tests/ # Unit tests (per module) +└── scripts/ # Build scripts (Apollo codegen) +``` + +## Common Commands + +```bash +# Full verification (build + tests) - ALWAYS RUN AFTER CODE CHANGES +./Alfie/scripts/verify.sh + +# Decrypt sensitive files (requires GPG keys) +git secret reveal + +# Install dependencies +brew bundle install + +# Generate GraphQL code +cd Alfie/scripts && ./run-apollo-codegen.sh + +# Generate localization code (automatic on build, or manually) +swift package --allow-writing-to-package-directory generate-code-for-resources +``` + +## Key Dependencies + +- **Apollo iOS**: GraphQL client (v1.19.0) +- **Firebase**: Analytics, Crashlytics, Remote Config (v11.11.0) +- **Braze**: Marketing automation (v11.9.0) +- **Nuke**: Image loading/caching (v12.8.0) +- **Alicerce**: Utilities, logging (v0.18.0) +- **SwiftGen**: Code generation for resources (v6.6.4-mindera fork) +- **Snapshot Testing**: UI testing (v1.18.3) + +## Code Review Guidelines + +### PR Review Checklist + +- [ ] **Architecture**: MVVM pattern, DependencyContainer usage, FlowViewModel navigation +- [ ] **Localization**: All strings use L10n +- [ ] **State**: ViewState/PaginatedViewState used correctly +- [ ] **Tests**: ViewModels have unit tests, protocols exist for mocking +- [ ] **Security**: No credentials, Keychain for sensitive data, HTTPS only +- [ ] **GraphQL**: Fragments used, codegen run, no edits to generated files +- [ ] **SwiftLint**: No violations + +### 🔴 Critical (Block Merge) + +- ViewModels accessing `ServiceProvider` directly +- Hardcoded user-facing strings +- Navigation bypassing FlowViewModel +- Missing ViewModel protocols +- Credentials/secrets in code +- State not using `ViewState` enums + +### 🟠 High Priority + +- Missing tests for ViewModels +- GraphQL queries without fragments +- Missing localization translations +- Dependencies not via DependencyContainer + +### Security Review Points + +- No API keys, tokens, passwords in code +- Sensitive data uses Keychain, not UserDefaults +- No PII in logs +- git-secret for sensitive files +- Input validation on deep links + +## Security & Sensitive Files + +### git-secret + +- **Encrypted files**: Listed in `.gitsecret/paths/mapping.cfg` +- **Decryption**: `git secret reveal` (requires GPG keys) +- **Ignored**: Decrypted files are in `.gitignore` + +**Sensitive file locations**: +- `Alfie/Alfie/Configuration/Debug/GoogleService-Info.plist` +- `Alfie/Alfie/Configuration/Release/GoogleService-Info.plist` + +### Adding Sensitive Files + +```bash +git rm --cached path-to-sensitive-file +git secret add path-to-sensitive-file +git secret hide +``` + +**Never commit unencrypted sensitive files.** + +## Additional Context + +- **Minimum iOS**: 16.0 +- **Swift Version**: 5.9+ +- **Mock Server**: Separate Alfie-Mocks repo runs locally on localhost:4000 +- **CI/CD**: Work in progress +- **Release Process**: Work in progress diff --git a/Docs/Specs/README.md b/Docs/Specs/README.md index 7c1580f1..0b57df0c 100644 --- a/Docs/Specs/README.md +++ b/Docs/Specs/README.md @@ -99,4 +99,4 @@ When you ask an AI assistant to "implement the Product Listing feature", it will ## Questions? -See `.github/copilot-instructions.md` for the full development workflow and architecture patterns. +See `AGENTS.md` for the full development workflow and architecture patterns. diff --git a/Docs/Testing.md b/Docs/Testing.md new file mode 100644 index 00000000..1270e184 --- /dev/null +++ b/Docs/Testing.md @@ -0,0 +1,54 @@ +# Testing + +## Test Structure + +- **Location**: `Alfie/AlfieKit/Tests/` +- Test directories mirror feature modules: + - **AppFeatureTests**: App shell tests + - **CoreTests**: Core services tests + - **HomeTests**: Home feature tests + - **ProductListingTests**: Product listing tests + - **ProductDetailsTests**: Product details tests + - **SearchTests**: Search tests + - **CategorySelectorTests**: Category selector tests + - **WishlistTests**: Wishlist tests + - **BagTests**: Bag tests + - **SharedUITests**: Localization and UI tests + - **DeepLinkTests**: Deep link tests + - **DebugMenuTests**: Debug menu tests + - **WebTests**: Web view tests + - **MyAccountTests**: Account tests + - **BFFGraphTests**: GraphQL tests + - **UtilsTests**: Utility tests + +## Testing Pattern + +```swift +final class FeatureServiceTests: XCTestCase { + func testFetchDataSuccess() async throws { + // Given + let mockBFFClient = MockBFFClientService() + let service = FeatureService(bffClient: mockBFFClient) + + // When + let result = try await service.fetchData() + + // Then + XCTAssertEqual(result.id, "expected-id") + } +} +``` + +## Mocking + +- **Mock ViewModels**: Located in `Alfie/AlfieKit/Sources/Mocks/Core/Features/` +- **Mock Services**: Located in `Alfie/AlfieKit/Sources/Mocks/Core/Services/` +- **BFF Mocks**: Located in `Alfie/AlfieKit/Sources/BFFGraph/Mocks/` (Apollo-generated) +- **Fixtures**: Located in `Alfie/AlfieKit/Sources/Mocks/Fixtures/` +- **Pattern**: Conform to same protocol as real implementation + +## Snapshot Testing + +- Uses `swift-snapshot-testing` library +- Record mode: Set `record = true` temporarily +- Verify mode: Default behavior From 682a2aed95c2b222a52a278cad8031fcdd398cd2 Mon Sep 17 00:00:00 2001 From: Alasdair McCall Date: Mon, 16 Feb 2026 18:01:06 +0000 Subject: [PATCH 2/3] manually runnable workflow bug: debug github action --- .github/workflows/alfie.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/alfie.yml b/.github/workflows/alfie.yml index 888da327..4f168ce6 100644 --- a/.github/workflows/alfie.yml +++ b/.github/workflows/alfie.yml @@ -7,7 +7,9 @@ on: pull_request: branches: - - 'main' + - "main" + + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -97,7 +99,7 @@ jobs: SECRETS_GPG_PASSPHRASE: ${{ secrets.SECRETS_GPG_PASSPHRASE }} run: | echo "$SECRETS_GPG_PRIVATE_KEY" | gpg --batch --yes --import - git secret reveal -p "$SECRETS_GPG_PASSPHRASE" + git secret reveal -p "$SECRETS_GPG_PASSPHRASE" -v - name: Run Tests run: bundle exec fastlane ios test --env default From 0c096c31f0cd173e251ba0acc3c2cc50941c4a85 Mon Sep 17 00:00:00 2001 From: Alasdair McCall Date: Mon, 16 Feb 2026 21:55:45 +0000 Subject: [PATCH 3/3] fix: Configure GPG agent for loopback pinentry in CI Enable loopback pinentry mode for GPG agent to allow non-interactive passphrase input in CI environment. Newer GPG versions require this configuration to accept passphrases via stdin instead of attempting interactive prompts that fail in GitHub Actions runners. Also add workflow_dispatch trigger to allow manual workflow runs... --- .github/workflows/alfie.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/alfie.yml b/.github/workflows/alfie.yml index 4f168ce6..a4edaa6e 100644 --- a/.github/workflows/alfie.yml +++ b/.github/workflows/alfie.yml @@ -98,8 +98,17 @@ jobs: SECRETS_GPG_PRIVATE_KEY: ${{ secrets.SECRETS_GPG_PRIVATE_KEY }} SECRETS_GPG_PASSPHRASE: ${{ secrets.SECRETS_GPG_PASSPHRASE }} run: | + git config --global user.email "${{ secrets.GIT_EMAIL }}" + git config --global user.name "${{ secrets.GIT_USER_NAME }}" + mkdir -p ~/.gnupg + chmod 700 ~/.gnupg + echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf + echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf + chmod 600 ~/.gnupg/gpg-agent.conf + chmod 600 ~/.gnupg/gpg.conf echo "$SECRETS_GPG_PRIVATE_KEY" | gpg --batch --yes --import - git secret reveal -p "$SECRETS_GPG_PASSPHRASE" -v + gpg-connect-agent reloadagent /bye || true + git secret reveal -p "$SECRETS_GPG_PASSPHRASE" - name: Run Tests run: bundle exec fastlane ios test --env default @@ -161,7 +170,16 @@ jobs: SECRETS_GPG_PRIVATE_KEY: ${{ secrets.SECRETS_GPG_PRIVATE_KEY }} SECRETS_GPG_PASSPHRASE: ${{ secrets.SECRETS_GPG_PASSPHRASE }} run: | + git config --global user.email "${{ secrets.GIT_EMAIL }}" + git config --global user.name "${{ secrets.GIT_USER_NAME }}" + mkdir -p ~/.gnupg + chmod 700 ~/.gnupg + echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf + echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf + chmod 600 ~/.gnupg/gpg-agent.conf + chmod 600 ~/.gnupg/gpg.conf echo "$SECRETS_GPG_PRIVATE_KEY" | gpg --batch --yes --import + gpg-connect-agent reloadagent /bye || true git secret reveal -p "$SECRETS_GPG_PASSPHRASE" - name: Build and deploy release to TestFlight