swift-code-reviewer-skill 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: swift-code-reviewer
3
- description: Perform thorough code reviews for Swift/SwiftUI code, analyzing code quality, architecture, performance, security, and adherence to Swift 6+ best practices, SwiftUI patterns, iOS/macOS platform guidelines, and project-specific coding standards from .claude/CLAUDE.md. Use when reviewing code changes, performing quality audits, or providing structured feedback on Swift codebases with all severity levels and positive feedback.
3
+ description: Perform thorough code reviews for Swift/SwiftUI code, analyzing code quality, architecture, performance, security, and adherence to Swift 6+ best practices, SwiftUI patterns, navigation architecture, sheet routing, theming, async state, iOS/macOS platform guidelines, and project-specific coding standards from .claude/CLAUDE.md. Use when reviewing code changes, performing quality audits, or providing structured feedback on Swift codebases with all severity levels and positive feedback.
4
4
  ---
5
5
 
6
6
  # Swift/SwiftUI Code Review Skill
@@ -30,6 +30,7 @@ Use this skill when you need to:
30
30
  - Provide structured feedback to team members
31
31
 
32
32
  **Trigger patterns:**
33
+
33
34
  - "Review this PR"
34
35
  - "Review [filename].swift"
35
36
  - "Review my changes"
@@ -69,6 +70,7 @@ The skill follows a **four-phase workflow** to ensure comprehensive and actionab
69
70
  Execute checks across **six core categories**:
70
71
 
71
72
  #### 1. Swift Best Practices
73
+
72
74
  Reference: `swift-best-practices` skill knowledge base
73
75
 
74
76
  - **Concurrency Safety**
@@ -91,6 +93,7 @@ Reference: `swift-best-practices` skill knowledge base
91
93
  - Proper use of optionals
92
94
 
93
95
  #### 2. SwiftUI Quality
96
+
94
97
  Reference: `swiftui-expert-skill` knowledge base
95
98
 
96
99
  - **State Management**
@@ -118,6 +121,7 @@ Reference: `swiftui-expert-skill` knowledge base
118
121
  - Keyboard navigation
119
122
 
120
123
  #### 3. Performance
124
+
121
125
  Reference: `swiftui-performance-audit` knowledge base
122
126
 
123
127
  - **View Optimization**
@@ -256,7 +260,7 @@ Reference: `swiftui-performance-audit` knowledge base
256
260
  - Comparison with project guidelines
257
261
 
258
262
  3. **Provide Context**
259
- - Explain *why* something is an issue
263
+ - Explain _why_ something is an issue
260
264
  - Reference best practices or standards
261
265
  - Suggest specific fixes with examples
262
266
  - Link to learning resources
@@ -266,6 +270,7 @@ Reference: `swiftui-performance-audit` knowledge base
266
270
  ### 1. Swift Language Quality
267
271
 
268
272
  **What to Check:**
273
+
269
274
  - Concurrency patterns (actors, async/await, Sendable)
270
275
  - Error handling (typed throws, Result type)
271
276
  - Optionals handling (avoid force unwrapping)
@@ -279,6 +284,7 @@ Reference: `swiftui-performance-audit` knowledge base
279
284
  ### 2. SwiftUI Patterns
280
285
 
281
286
  **What to Check:**
287
+
282
288
  - Property wrapper selection and usage
283
289
  - State management patterns
284
290
  - View lifecycle understanding
@@ -292,6 +298,7 @@ Reference: `swiftui-performance-audit` knowledge base
292
298
  ### 3. Performance Optimization
293
299
 
294
300
  **What to Check:**
301
+
295
302
  - View update optimization
296
303
  - ForEach identity and performance
297
304
  - Heavy work in view body
@@ -305,6 +312,7 @@ Reference: `swiftui-performance-audit` knowledge base
305
312
  ### 4. Security & Safety
306
313
 
307
314
  **What to Check:**
315
+
308
316
  - Force unwrap detection (`!`, `as!`, `try!`)
309
317
  - Input validation and sanitization
310
318
  - Sensitive data handling (passwords, tokens)
@@ -318,6 +326,7 @@ Reference: `swiftui-performance-audit` knowledge base
318
326
  ### 5. Architecture & Design
319
327
 
320
328
  **What to Check:**
329
+
321
330
  - MVVM, MVI, TCA, or other architecture compliance
322
331
  - Dependency injection patterns
323
332
  - Separation of concerns
@@ -331,6 +340,7 @@ Reference: `swiftui-performance-audit` knowledge base
331
340
  ### 6. Project-Specific Standards
332
341
 
333
342
  **What to Check:**
343
+
334
344
  - `.claude/CLAUDE.md` compliance
335
345
  - Custom architecture patterns
336
346
  - Design system usage
@@ -345,9 +355,11 @@ Reference: `swiftui-performance-audit` knowledge base
345
355
  This skill **references** (not duplicates) three foundational skills for domain expertise:
346
356
 
347
357
  ### 1. swift-best-practices
358
+
348
359
  **When to Use:** Reviewing Swift language usage, concurrency patterns, API design, or Swift 6+ migration
349
360
 
350
361
  **What it Provides:**
362
+
351
363
  - Swift 6+ concurrency patterns (actors, async/await, Sendable)
352
364
  - API design guidelines compliance
353
365
  - Availability pattern validation
@@ -355,14 +367,17 @@ This skill **references** (not duplicates) three foundational skills for domain
355
367
  - Modern Swift feature adoption
356
368
 
357
369
  **How to Leverage:**
370
+
358
371
  - Read `~/.claude/skills/swift-best-practices/references/concurrency.md` for concurrency checks
359
372
  - Reference `swift6-features.md` for Swift 6 migration patterns
360
373
  - Use `api-design.md` for naming and parameter validation
361
374
 
362
375
  ### 2. swiftui-expert-skill
376
+
363
377
  **When to Use:** Reviewing SwiftUI views, state management, or UI code
364
378
 
365
379
  **What it Provides:**
380
+
366
381
  - State management patterns (@Observable, @State, @Binding)
367
382
  - Modern SwiftUI API guidance (iOS 17+, macOS 14+)
368
383
  - View composition best practices
@@ -370,14 +385,17 @@ This skill **references** (not duplicates) three foundational skills for domain
370
385
  - Accessibility patterns
371
386
 
372
387
  **How to Leverage:**
388
+
373
389
  - Read `~/.claude/skills/swiftui-expert-skill/references/state-management.md` for property wrapper checks
374
390
  - Reference `modern-apis.md` for deprecation detection
375
391
  - Use `view-composition.md` for component structure validation
376
392
 
377
393
  ### 3. swiftui-performance-audit
394
+
378
395
  **When to Use:** Performance concerns identified or mentioned in PR description
379
396
 
380
397
  **What it Provides:**
398
+
381
399
  - View update optimization patterns
382
400
  - ForEach performance analysis
383
401
  - Layout thrash detection
@@ -385,11 +403,33 @@ This skill **references** (not duplicates) three foundational skills for domain
385
403
  - Memory management
386
404
 
387
405
  **How to Leverage:**
406
+
388
407
  - Read `~/.claude/skills/swiftui-performance-audit/SKILL.md` for performance audit workflow
389
408
  - Reference performance-specific checks when reviewing view code
390
409
  - Apply recommendations from the skill to performance-sensitive paths
391
410
 
411
+ ### 4. swiftui-ui-patterns
412
+
413
+ **When to Use:** Reviewing navigation architecture, sheet/modal routing, TabView setup, theming, async state management, focus handling, or API client patterns
414
+
415
+ **What it Provides:**
416
+
417
+ - Navigation architecture (route enums, RouterPath, centralized navigationDestination)
418
+ - Sheet/modal routing (item-driven sheets, SheetDestination enum)
419
+ - TabView with independent per-tab navigation history
420
+ - Theming with semantic colors via `@Environment(Theme.self)`
421
+ - Async state patterns (`.task(id:)`, LoadState enum, CancellationError handling)
422
+ - Focus chaining with FocusField enum and `.onSubmit`
423
+ - Lightweight API client pattern (closure-based structs, `.live()` / `.mock()` factories)
424
+
425
+ **How to Leverage:**
426
+
427
+ - Read `~/.claude/skills/swiftui-ui-patterns/references/navigation.md` for route enum and RouterPath checks
428
+ - Reference `sheets-modals.md` for sheet routing validation
429
+ - Use `theming.md` for semantic color enforcement
430
+
392
431
  **Integration Strategy:**
432
+
393
433
  1. Load relevant reference files from these skills as needed
394
434
  2. Apply their checklist items to the review
395
435
  3. Reference their documentation in feedback
@@ -398,6 +438,7 @@ This skill **references** (not duplicates) three foundational skills for domain
398
438
  ## Platform Support
399
439
 
400
440
  ### GitHub Pull Requests
441
+
401
442
  Use `gh` CLI for fetching PR data:
402
443
 
403
444
  ```bash
@@ -415,6 +456,7 @@ gh pr view <PR-number> --json comments
415
456
  ```
416
457
 
417
458
  ### GitLab Merge Requests
459
+
418
460
  Use `glab` CLI for fetching MR data:
419
461
 
420
462
  ```bash
@@ -432,6 +474,7 @@ glab mr note list <MR-number>
432
474
  ```
433
475
 
434
476
  ### Local Git Changes
477
+
435
478
  For uncommitted changes or manual review:
436
479
 
437
480
  ```bash
@@ -452,10 +495,11 @@ git show <commit-hash>
452
495
 
453
496
  The review report follows this structure:
454
497
 
455
- ```markdown
498
+ ````markdown
456
499
  # Code Review Report
457
500
 
458
501
  ## Summary
502
+
459
503
  - **Files Reviewed**: X
460
504
  - **Total Findings**: Y
461
505
  - **Critical**: 0
@@ -466,6 +510,7 @@ The review report follows this structure:
466
510
  - **Refactoring Suggestions**: 4
467
511
 
468
512
  ## Executive Summary
513
+
469
514
  [Brief overview of the changes and overall code quality]
470
515
 
471
516
  ---
@@ -475,6 +520,7 @@ The review report follows this structure:
475
520
  ### File: Sources/Features/Login/LoginView.swift
476
521
 
477
522
  #### ✅ Positive Feedback
523
+
478
524
  1. **Excellent State Management** (line 23)
479
525
  - Proper use of @Observable for view model
480
526
  - Clean separation of concerns
@@ -484,11 +530,13 @@ The review report follows this structure:
484
530
  - Proper async/await integration
485
531
 
486
532
  #### 🔴 Critical Issues
533
+
487
534
  1. **Data Race Risk** (line 67)
488
535
  - **Severity**: Critical
489
536
  - **Category**: Concurrency
490
537
  - **Issue**: Mutable state accessed from multiple actors without synchronization
491
538
  - **Fix**:
539
+
492
540
  ```swift
493
541
  // Before
494
542
  class LoginViewModel {
@@ -501,14 +549,17 @@ The review report follows this structure:
501
549
  @Published var isLoading = false
502
550
  }
503
551
  ```
552
+
504
553
  - **Reference**: swift-best-practices/references/concurrency.md
505
554
 
506
555
  #### 🟡 High Priority
556
+
507
557
  1. **Force Unwrap Detected** (line 89)
508
558
  - **Severity**: High
509
559
  - **Category**: Safety
510
560
  - **Issue**: Force unwrapping optional can cause crash
511
561
  - **Fix**:
562
+
512
563
  ```swift
513
564
  // Before
514
565
  let user = fetchUser()!
@@ -519,9 +570,11 @@ The review report follows this structure:
519
570
  return
520
571
  }
521
572
  ```
573
+
522
574
  - **Reference**: Project coding standard (.claude/CLAUDE.md:45)
523
575
 
524
576
  #### 💡 Refactoring Suggestions
577
+
525
578
  1. **Extract Subview** (lines 120-150)
526
579
  - Consider extracting login form into separate view
527
580
  - Improves testability and reusability
@@ -531,20 +584,24 @@ The review report follows this structure:
531
584
  ## Prioritized Action Items
532
585
 
533
586
  ### Must Fix (Critical/High)
587
+
534
588
  1. [ ] Fix data race in LoginViewModel.swift:67
535
589
  2. [ ] Remove force unwrap in LoginView.swift:89
536
590
 
537
591
  ### Should Fix (Medium)
592
+
538
593
  1. [ ] Add documentation to public APIs
539
594
  2. [ ] Improve error handling in NetworkService.swift
540
595
 
541
596
  ### Consider (Low)
597
+
542
598
  1. [ ] Refactor login form into separate view
543
599
  2. [ ] Add more unit tests for edge cases
544
600
 
545
601
  ---
546
602
 
547
603
  ## Positive Patterns Observed
604
+
548
605
  - Excellent use of @Observable for state management
549
606
  - Consistent adherence to project architecture (MVVM)
550
607
  - Comprehensive accessibility support
@@ -552,14 +609,16 @@ The review report follows this structure:
552
609
  - Good test coverage for core functionality
553
610
 
554
611
  ## References
612
+
555
613
  - [Swift Best Practices](~/.claude/skills/swift-best-practices/SKILL.md)
556
614
  - [SwiftUI Expert Guide](~/.claude/skills/swiftui-expert-skill/SKILL.md)
557
615
  - [Project Coding Standards](.claude/CLAUDE.md)
558
- ```
616
+ ````
559
617
 
560
618
  ## How to Use
561
619
 
562
620
  ### Example 1: Review Specific File
621
+
563
622
  ```
564
623
  User: "Review UserProfileView.swift"
565
624
 
@@ -571,6 +630,7 @@ Steps:
571
630
  ```
572
631
 
573
632
  ### Example 2: Review Git Changes
633
+
574
634
  ```
575
635
  User: "Review my uncommitted changes"
576
636
 
@@ -583,6 +643,7 @@ Steps:
583
643
  ```
584
644
 
585
645
  ### Example 3: Review Pull Request
646
+
586
647
  ```
587
648
  User: "Review PR #123"
588
649
 
@@ -596,6 +657,7 @@ Steps:
596
657
  ```
597
658
 
598
659
  ### Example 4: Review Against Custom Guidelines
660
+
599
661
  ```
600
662
  User: "Review LoginViewModel.swift against our coding standards"
601
663
 
@@ -608,6 +670,7 @@ Steps:
608
670
  ```
609
671
 
610
672
  ### Example 5: Review Multiple Files
673
+
611
674
  ```
612
675
  User: "Review all ViewModels in the Features folder"
613
676
 
@@ -619,21 +682,48 @@ Steps:
619
682
  5. Summarize common patterns and issues across all files
620
683
  ```
621
684
 
685
+ ### Example 6: Review Navigation / Routing Code
686
+
687
+ ```
688
+ User: "Review our navigation setup and routing code"
689
+
690
+ Steps:
691
+ 1. Read .claude/CLAUDE.md for project navigation patterns
692
+ 2. Read router/coordinator files (RouterPath, AppCoordinator, TabRouter)
693
+ 3. Read root views that set up NavigationStack and TabView
694
+ 4. Run navigation architecture checks:
695
+ - Route destinations use typed Hashable enum (not String/Int)
696
+ - RouterPath @Observable owns path (not ad-hoc @State)
697
+ - Single centralized .navigationDestination per stack
698
+ - .sheet(item:) preferred over .sheet(isPresented:) when model selected
699
+ - Multiple sheets use SheetDestination enum (not multiple booleans)
700
+ - Each tab has independent RouterPath (not shared)
701
+ - .onOpenURL at app root, not scattered in feature views
702
+ 5. Run async state checks:
703
+ - .task(id:) for input-driven async work
704
+ - CancellationError silenced
705
+ - LoadState<T> enum instead of multiple booleans
706
+ 6. Generate report with navigation-specific findings
707
+ ```
708
+
622
709
  ## Resources
623
710
 
624
711
  This skill includes the following reference materials:
625
712
 
626
713
  ### Core References
714
+
627
715
  - **review-workflow.md**: Detailed step-by-step review process, git commands, and diff parsing strategies
628
716
  - **feedback-templates.md**: Templates for positive/negative comments, severity classification guidelines
629
717
 
630
718
  ### Quality Checklists
719
+
631
720
  - **swift-quality-checklist.md**: Swift 6+ concurrency, error handling, optionals, access control, naming
632
721
  - **swiftui-review-checklist.md**: Property wrappers, state management, modern APIs, view composition
633
722
  - **performance-review.md**: View updates, ForEach optimization, layout efficiency, resource management
634
723
  - **security-checklist.md**: Input validation, sensitive data, keychain, network security
635
724
 
636
725
  ### Architecture & Customization
726
+
637
727
  - **architecture-patterns.md**: MVVM, MVI, TCA patterns, dependency injection, testing strategies
638
728
  - **custom-guidelines.md**: How to read and parse .claude/CLAUDE.md and project-specific standards
639
729
 
@@ -652,7 +742,7 @@ This skill includes the following reference materials:
652
742
  3. **Be Specific and Actionable**
653
743
  - Include exact file:line references
654
744
  - Provide code examples for fixes
655
- - Explain *why* something is an issue
745
+ - Explain _why_ something is an issue
656
746
  - Link to relevant documentation
657
747
 
658
748
  4. **Prioritize by Severity**
@@ -685,6 +775,6 @@ For runtime analysis, recommend using Instruments or other profiling tools.
685
775
 
686
776
  ## Version
687
777
 
688
- **Version**: 1.0.0
689
- **Last Updated**: 2026-02-10
778
+ **Version**: 1.1.1
779
+ **Last Updated**: 2026-03-16
690
780
  **Compatible with**: Swift 6+, SwiftUI (iOS 17+, macOS 14+, watchOS 10+, tvOS 17+, visionOS 1+)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swift-code-reviewer-skill",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Claude Code skill for comprehensive Swift/SwiftUI code reviews with multi-layer analysis",
5
5
  "keywords": [
6
6
  "claude",
@@ -593,6 +593,281 @@ struct UserListView: View {
593
593
 
594
594
  ---
595
595
 
596
+ ## 5A. Lightweight Client Pattern (Closure-Based)
597
+
598
+ ### 5A.1 Overview
599
+
600
+ **Purpose**: Define API clients as value-type structs with async closure properties, enabling easy swapping between live and mock implementations — especially for SwiftUI previews.
601
+
602
+ **Benefits:**
603
+ - Preview-friendly (no network calls in Xcode Previews)
604
+ - Testable without subclassing or protocol mocking boilerplate
605
+ - Composable: clients can be scoped per-feature
606
+ - No shared mutable singleton state
607
+
608
+ ### 5A.2 Implementation Pattern
609
+
610
+ **Check for:**
611
+ - [ ] API client defined as a `struct` with `async` closure properties (not a singleton class)
612
+ - [ ] Static factory `.live(baseURL:)` for production
613
+ - [ ] Static factory `.mock(...)` for previews and tests
614
+ - [ ] Store (`@Observable`) holds the client; views never call the client directly
615
+ - [ ] Client injected via `@Environment` or constructor
616
+
617
+ **Examples:**
618
+
619
+ ❌ **Bad: Singleton class client**
620
+ ```swift
621
+ final class UserAPIClient {
622
+ static let shared = UserAPIClient() // ❌ Singleton — untestable, preview-unfriendly
623
+
624
+ func fetchUser(id: UUID) async throws -> User {
625
+ let url = URL(string: "https://api.example.com/users/\(id)")!
626
+ let (data, _) = try await URLSession.shared.data(from: url)
627
+ return try JSONDecoder().decode(User.self, from: data)
628
+ }
629
+ }
630
+
631
+ final class UserViewModel {
632
+ func loadUser(id: UUID) async {
633
+ let user = try await UserAPIClient.shared.fetchUser(id: id) // ❌ Hard dependency
634
+ }
635
+ }
636
+ ```
637
+
638
+ ✅ **Good: Struct with closure properties**
639
+ ```swift
640
+ // Client defined as a struct with closure properties
641
+ struct UserAPIClient {
642
+ var fetchUser: (UUID) async throws -> User
643
+ var updateUser: (User) async throws -> User
644
+ var deleteUser: (UUID) async throws -> Void
645
+ }
646
+
647
+ // Live implementation
648
+ extension UserAPIClient {
649
+ static func live(baseURL: URL) -> Self {
650
+ UserAPIClient(
651
+ fetchUser: { id in
652
+ let url = baseURL.appendingPathComponent("users/\(id)")
653
+ let (data, _) = try await URLSession.shared.data(from: url)
654
+ return try JSONDecoder().decode(User.self, from: data)
655
+ },
656
+ updateUser: { user in
657
+ var request = URLRequest(url: baseURL.appendingPathComponent("users/\(user.id)"))
658
+ request.httpMethod = "PUT"
659
+ request.httpBody = try JSONEncoder().encode(user)
660
+ let (data, _) = try await URLSession.shared.data(for: request)
661
+ return try JSONDecoder().decode(User.self, from: data)
662
+ },
663
+ deleteUser: { id in
664
+ var request = URLRequest(url: baseURL.appendingPathComponent("users/\(id)"))
665
+ request.httpMethod = "DELETE"
666
+ _ = try await URLSession.shared.data(for: request)
667
+ }
668
+ )
669
+ }
670
+ }
671
+
672
+ // Mock implementation for previews and tests
673
+ extension UserAPIClient {
674
+ static func mock(
675
+ fetchUser: @escaping (UUID) async throws -> User = { _ in .mock },
676
+ updateUser: @escaping (User) async throws -> User = { $0 },
677
+ deleteUser: @escaping (UUID) async throws -> Void = { _ in }
678
+ ) -> Self {
679
+ UserAPIClient(
680
+ fetchUser: fetchUser,
681
+ updateUser: updateUser,
682
+ deleteUser: deleteUser
683
+ )
684
+ }
685
+ }
686
+
687
+ // Store holds the client — views never call it directly
688
+ @MainActor
689
+ @Observable
690
+ final class UserStore {
691
+ private let client: UserAPIClient
692
+ private(set) var user: User?
693
+ private(set) var isLoading = false
694
+
695
+ init(client: UserAPIClient) {
696
+ self.client = client
697
+ }
698
+
699
+ func loadUser(id: UUID) async {
700
+ isLoading = true
701
+ defer { isLoading = false }
702
+ user = try? await client.fetchUser(id)
703
+ }
704
+ }
705
+
706
+ // View uses Store, not Client directly
707
+ struct UserProfileView: View {
708
+ let store: UserStore
709
+
710
+ var body: some View {
711
+ Group {
712
+ if let user = store.user {
713
+ Text(user.name)
714
+ } else {
715
+ ProgressView()
716
+ }
717
+ }
718
+ .task { await store.loadUser(id: userID) }
719
+ }
720
+ }
721
+
722
+ // Preview uses mock client
723
+ #Preview {
724
+ UserProfileView(store: UserStore(client: .mock(
725
+ fetchUser: { _ in User(id: UUID(), name: "Preview User") }
726
+ )))
727
+ }
728
+
729
+ // App uses live client
730
+ @main
731
+ struct MyApp: App {
732
+ let userStore = UserStore(client: .live(baseURL: URL(string: "https://api.example.com")!))
733
+
734
+ var body: some Scene {
735
+ WindowGroup {
736
+ UserProfileView(store: userStore)
737
+ }
738
+ }
739
+ }
740
+ ```
741
+
742
+ ---
743
+
744
+ ## 5B. Per-Tab Navigation Architecture
745
+
746
+ ### 5B.1 Overview
747
+
748
+ **Purpose**: Provide each tab with its own independent navigation stack and router, preserving navigation history across tab switches and supporting deep link routing to a specific tab.
749
+
750
+ **Benefits:**
751
+ - Tab navigation state preserved when switching tabs (native iOS behavior)
752
+ - Deep links can target specific tabs without resetting all stacks
753
+ - Decoupled tab-specific routing logic
754
+
755
+ ### 5B.2 Implementation Pattern
756
+
757
+ **Check for:**
758
+ - [ ] `TabRouter` (or `AppTabRouter`) owns one `RouterPath` per tab
759
+ - [ ] `Binding(for tab:)` helper creates a `Binding<[Route]>` for each tab's stack
760
+ - [ ] Deep links dispatched to the correct tab's router (not a global path)
761
+ - [ ] Tab switching does not reset other tabs' navigation stacks
762
+
763
+ **Examples:**
764
+
765
+ ❌ **Bad: Single shared path for all tabs**
766
+ ```swift
767
+ struct MainTabView: View {
768
+ @State private var path = NavigationPath() // ❌ Shared — tab switch loses history
769
+ @State private var selectedTab = 0
770
+
771
+ var body: some View {
772
+ TabView(selection: $selectedTab) {
773
+ NavigationStack(path: $path) { HomeView() }.tag(0)
774
+ NavigationStack(path: $path) { SearchView() }.tag(1) // ❌ Same path!
775
+ }
776
+ }
777
+ }
778
+ ```
779
+
780
+ ✅ **Good: Per-tab RouterPath with deep link routing**
781
+ ```swift
782
+ @Observable
783
+ final class RouterPath {
784
+ var path: [AppRoute] = []
785
+
786
+ func navigate(to route: AppRoute) { path.append(route) }
787
+ func pop() { if !path.isEmpty { path.removeLast() } }
788
+ func popToRoot() { path.removeAll() }
789
+
790
+ func handle(url: URL) -> Bool {
791
+ guard let route = AppRoute(url: url) else { return false }
792
+ navigate(to: route)
793
+ return true
794
+ }
795
+ }
796
+
797
+ enum AppTab: Int, CaseIterable, Identifiable {
798
+ case home, search, notifications, profile
799
+ var id: Int { rawValue }
800
+ }
801
+
802
+ @Observable
803
+ final class AppTabRouter {
804
+ var selectedTab: AppTab = .home
805
+
806
+ var homeRouter = RouterPath()
807
+ var searchRouter = RouterPath()
808
+ var notificationsRouter = RouterPath()
809
+ var profileRouter = RouterPath()
810
+
811
+ // Binding helper for each tab's path
812
+ func pathBinding(for tab: AppTab) -> Binding<[AppRoute]> {
813
+ Binding(
814
+ get: { self.router(for: tab).path },
815
+ set: { self.router(for: tab).path = $0 }
816
+ )
817
+ }
818
+
819
+ func router(for tab: AppTab) -> RouterPath {
820
+ switch tab {
821
+ case .home: return homeRouter
822
+ case .search: return searchRouter
823
+ case .notifications: return notificationsRouter
824
+ case .profile: return profileRouter
825
+ }
826
+ }
827
+
828
+ // Route deep link to correct tab
829
+ func handle(url: URL) {
830
+ guard let route = AppRoute(url: url) else { return }
831
+ let targetTab = route.preferredTab
832
+ selectedTab = targetTab
833
+ router(for: targetTab).navigate(to: route)
834
+ }
835
+ }
836
+
837
+ struct MainTabView: View {
838
+ @State private var tabRouter = AppTabRouter()
839
+
840
+ var body: some View {
841
+ TabView(selection: $tabRouter.selectedTab) {
842
+ Tab("Home", systemImage: "house", value: AppTab.home) {
843
+ NavigationStack(path: tabRouter.pathBinding(for: .home)) {
844
+ HomeView(router: tabRouter.homeRouter)
845
+ .navigationDestination(for: AppRoute.self) { route in
846
+ routeDestination(route, router: tabRouter.homeRouter)
847
+ }
848
+ }
849
+ }
850
+
851
+ Tab("Search", systemImage: "magnifyingglass", value: AppTab.search) {
852
+ NavigationStack(path: tabRouter.pathBinding(for: .search)) {
853
+ SearchView(router: tabRouter.searchRouter)
854
+ .navigationDestination(for: AppRoute.self) { route in
855
+ routeDestination(route, router: tabRouter.searchRouter)
856
+ }
857
+ }
858
+ }
859
+
860
+ // ... other tabs
861
+ }
862
+ .onOpenURL { url in
863
+ tabRouter.handle(url: url) // ✅ Deep link dispatched to correct tab router
864
+ }
865
+ }
866
+ }
867
+ ```
868
+
869
+ ---
870
+
596
871
  ## 6. Testing Strategies
597
872
 
598
873
  ### 6.1 Unit Testing