--- alwaysApply: true title: Vibeviewer Architecture Guidelines --- ## Background - The project uses a layered, modular Swift Package architecture with goals: minimal public surface, one-way dependencies, single responsibility, testability, and replaceability. - Layers and dependency direction (top-down only): - Core/Shared → common utilities and extensions (no business-layer dependencies) - Model → pure data/DTO/domain entities (may depend on Core) - API/Service → networking/IO/3rd-party orchestration and DTO→domain mapping (depends on Model + 3rd-party) - Feature/UI → SwiftUI views and interactions (depends on API-exposed service protocols and domain models; must not depend on networking libraries) - Architectural style: Native SwiftUI MV (not MVVM). State via @State/@Observable; dependency injection via @Environment; concurrency with async/await and @MainActor. ## Do (Recommended) - Module placement & responsibilities - Before adding code, decide whether it belongs to UI/Service/Model/Core and place it in the corresponding package/directory; one type/responsibility per file. - The API layer exposes only “service protocol + default implementation”; networking library/targets/plugins are encapsulated internally. - Service functions return domain models (Model-layer entities) or clear error types; avoid leaking DTOs to the UI. - Domain models & mapping - Abstract API response DTOs into domain entities (e.g., UserProfile / UsageOverview / TeamSpendOverview / UsageEvent / FilteredUsageHistory). - Perform DTO→domain mapping in the API layer; UI consumes domain-only. - Dependencies & visibility - One-way: Core ← Model ← API ← Feature. - Default to internal; use public only for cross-package use; prefer protocols over concrete types. - SwiftUI & concurrency - Inject services via @Environment; place side effects in .task / .onChange so they automatically cancel with the view lifecycle. - UI updates occur on @MainActor; networking/IO on background using async/await; cross-isolation types must be Sendable. - Testing & replaceability - Provide an injectable network client interface for services; separate default implementation from testable construction paths. - Put utilities/algorithms into Core; prefer pure functions for unit testing and reuse. - Troble Shooting - if you facing an lint error by "can't not found xxx in scope" when you edit/new/delete some interface on Package, that means you need to call XCodeBuildMCP to rebuild that package, so that other package can update the codebase to fix that error ## Don't (Avoid) - UI directly depending on networking libraries, triggering requests, or being exposed to backend error details. - Feature depending on API internals (e.g., Targets/Plugins/concrete networking implementations). - Exposing API DTOs directly to the UI (causes global coupling and fragility). - Reverse dependencies (e.g., Model depends on Feature; API depends on UI). - Introducing MVVM/ViewModel as the default; or using Task { } in onAppear (use .task instead). - Overusing public types/initializers; placing multiple unrelated types in one file. ## Review checklist 1) Quadrant self-check (placement) - UI/interaction/rendering → Feature/UI - Networking/disk/auth/3rd-party → API/Service - Pure data/DTO/state aggregation → Model - Utilities/extensions/algorithms → Core 2) Surface area & replaceability - Can it be exposed via protocol to hide details? Is internal sufficient by default? - Do services return only domain models/error enums? Is it easy to replace/mock? 3) Dependency direction & coupling - Any violation of Core ← Model ← API ← Feature one-way dependency? - Does the UI still reference DTOs or networking implementations? If yes, move mapping/abstraction to the API layer. 4) Concurrency & thread safety - Are UI updates on @MainActor? Are cross-isolation types Sendable? Are we using async/await? - Should serialization-required persistence/cache be placed within an Actor boundary? 5) File organization & naming - Clear directories (Feature/Views, API/Service, API/Targets, API/Plugins, Model/Entities, Core/Extensions). - One type per file; names reflect layer and responsibility (e.g., FeatureXView, FeatureXService, GetYAPI, ZResponse). - Package directory structure: Sources// organized by feature subfolders; avoid dumping all source at one level. - Suggested subfolders: - API: Service / Targets / Plugins / Mapping (DTO→Domain mapping) - Feature: Views / Components / Scenes / Modifiers - Model: Entities - Core: Extensions / Utils - Consistent naming: use a shared prefix/suffix for similar features for discoverability. - Suffix examples: …Service, …API, …Response, …Request, …View, …Section, …Window, …Plugin, …Mapper. - Use a consistent domain/vendor prefix where needed (e.g., Cursor…). - File name equals type name: each file contains only one primary type; exact case-sensitive match. - Protocol/implementation convention: protocol uses FooService; default implementation uses DefaultFooService (or LiveFooService). Expose only protocols and inject implementations. - Model-layer naming (Entities vs DTOs): - Entities (exposed to business/UI): - Use domain-oriented neutral nouns; avoid vendor prefixes by default (e.g., UserProfile, UsageOverview, TeamSpendOverview, UsageEvent, FilteredUsageHistory, AppSettings, Credentials, DashboardSnapshot). - If source domain must be shown (e.g., “Cursor”), use a consistent prefix within that domain (e.g., CursorCredentials, CursorDashboardSnapshot) for consistency and discoverability. - Suggested suffixes: …Overview, …Snapshot, …History, …Event, …Member, …RoleCount. - Prefer struct, value semantics, and Sendable; expose public types/members only when needed cross-package. - File name equals type name; single-type files. - DTOs (API layer only, under API/Mapping/DTOs): - Use vendor/source prefix + semantic suffix: e.g., Cursor…Request, Cursor…Response, Cursor…Event. - Default visibility is internal; do not expose to Feature/UI; map to domain in the API layer only. - File name equals type name; single-type files; field names mirror backend responses (literal), adapted to domain naming via mapping. - Mapping lives in the API layer (Service/Mapping); UI/Feature must never depend on DTOs. ## Pre-PR checks - Remove unnecessary public modifiers; check for reverse dependencies across layers. - Ensure UI injects services via @Environment and contains no networking details. - Ensure DTO→domain mapping is complete, robust, and testable. Note: When using iOS 26 features, follow availability checks and progressive enhancement; ensure reasonable fallbacks for older OS versions. ## FAQ - After adding/removing module code, if lint reports a missing class but you are sure it exists, rebuild the package with XcodeBuild MCP and try again.