---
alwaysApply: false
---

# Tuist 集成与模块化拆分（Vibeviewer）

本规则记录项目接入 Tuist、按 Feature 拆分为独立 SPM 包、UI 层依赖注入，以及常见问题排查与修复。

## 标准方案（Single Source of Truth）
- 仅在 `Project.swift` 的 `packages` 节点声明本地包，保持“单一来源”。
- 不使用 `Tuist/Dependencies.swift` 声明本地包，避免与 `Project.swift` 重复导致解析冲突。
- App 目标依赖统一使用 `.package(product:)`。
- 生成工程：`make generate`；清理：`make clear`（仅清当前项目 DerivedData 与项目级 Tuist 缓存）。

示例（节选，自 `Project.swift`）
```swift
packages: [
    .local(path: "Packages/VibeviewerCore"),
    .local(path: "Packages/VibeviewerModel"),
    .local(path: "Packages/VibeviewerAPI"),
    .local(path: "Packages/VibeviewerLoginUI"),
    .local(path: "Packages/VibeviewerMenuUI"),
    .local(path: "Packages/VibeviewerSettingsUI")
],
```

## UI 层依赖注入（遵循 project.mdc）
- 不使用 MVVM；视图内部用 `@State` 管理轻量状态。
- 使用 Environment 注入跨模块依赖：
  - 在 `VibeviewerModel` 暴露 `EnvironmentValues.cursorStorage`。
  - 在 `VibeviewerMenuUI` 暴露 `EnvironmentValues.cursorService`、`loginWindowManager`、`settingsWindowManager`。
- App 注入：
```swift
MenuPopoverView()
    .environment(\.cursorService, DefaultCursorService())
    .environment(\.cursorStorage, CursorStorage.shared)
    .environment(\.loginWindowManager, LoginWindowManager.shared)
    .environment(\.settingsWindowManager, SettingsWindowManager.shared)
```
- 视图使用：
```swift
@Environment(\.cursorService) private var service
@Environment(\.cursorStorage) private var storage
@Environment(\.loginWindowManager) private var loginWindow
@Environment(\.settingsWindowManager) private var settingsWindow
```

## Feature 拆包规范
- 单一职责：
  - `VibeviewerLoginUI`：登录视图与窗口
  - `VibeviewerMenuUI`：菜单视图与业务触发
  - `VibeviewerSettingsUI`：设置视图与窗口
- 每个包必须包含测试目录 `Tests/<TargetName>Tests/`（即便是占位），否则会出现测试路径报错。

## 常见问题与排查
- 包在 Xcode 里显示为“文件夹 + ?”，不是 SPM 包：
  - 原因：`Project.swift` 与 `Tuist/Dependencies.swift` 同时声明了本地包（重复来源），或 SwiftPM/Xcode 缓存脏。
  - 处理：删除 `Tuist/Dependencies.swift` 的本地包声明（本项目直接删除该文件）；删除各包 `.swiftpm/`；`make clear` 后再 `make generate`。

### 修复步骤（示例：VibeviewerAppEnvironment 未作为包加载/显示为文件夹）
1. 确认 Single Source of Truth：仅在 `Project.swift` 的 `packages` 节点保留本地包声明。
   - 保持如下形式（节选）：
   ```swift
   packages: [
       .local(path: "Packages/VibeviewerCore"),
       .local(path: "Packages/VibeviewerModel"),
       .local(path: "Packages/VibeviewerAPI"),
       .local(path: "Packages/VibeviewerLoginUI"),
       .local(path: "Packages/VibeviewerMenuUI"),
       .local(path: "Packages/VibeviewerSettingsUI"),
       .local(path: "Packages/VibeviewerAppEnvironment"),
   ]
   ```
2. 清空 `Tuist/Dependencies.swift` 的本地包声明，避免与 `Project.swift` 重复：
   ```swift
   let dependencies = Dependencies(
     swiftPackageManager: .init(
       packages: [ /* 留空，统一由 Project.swift 管理 */ ],
       baseSettings: .settings(base: [:], configurations: [/* 省略 */])
     ),
     platforms: [.macOS]
   )
   ```
   - 注：也可直接删除该文件；两者目标一致——移除重复来源。
3. 可选清理缓存（若仍显示为文件夹或解析异常）：
   - 删除各包下残留的 `.swiftpm/` 目录（若存在）。
4. 重新生成工程：
   ```bash
   make clear && make generate
   ```
5. 验证：
   - Xcode 的 Project Navigator 中，`VibeviewerAppEnvironment` 以 Swift Package 方式展示（非普通文件夹）。
   - App 目标依赖通过 `.package(product: "VibeviewerAppEnvironment")` 引入。
- “Couldn't load project at …/.swiftpm/xcode”：
  - 原因：加载了过期的 `.swiftpm/xcode` 子工程缓存。
  - 处理：删除对应包 `.swiftpm/` 后重新生成。
- `no such module 'X'`：
  - 原因：缺少包/目标依赖或未在 `packages` 声明路径。
  - 处理：在包的 `Package.swift` 增加依赖；在 `Project.swift` 的 `packages` 增加 `.local(path:)`；再生成。
- 捕获列表语法错误（如 `[weak _ = service]`）：
  - Swift 不允许匿名弱引用捕获。移除该语法，使用受控任务生命周期（持有 `Task` 并适时取消）。

## Make 命令
- 生成：
```bash
make generate
```
- 清理（当前项目）：
```bash
make clear
```

## 新增 Feature 包 Checklist
1. 在 `Packages/YourFeature/` 创建 `Package.swift`、`Sources/YourFeature/`、`Tests/YourFeatureTests/`。
2. 在 `Package.swift` 写入 `.package(path: ...)` 与 `targets.target.dependencies`。
3. 在 `Project.swift` 的 `packages` 增加 `.local(path: ...)`，并在 App 目标依赖加 `.package(product: ...)`。
4. `make generate` 重新生成。

> 经验：保持“单一来源”（只在 `Project.swift` 声明本地包）显著降低 Tuist/SwiftPM 解析歧义与缓存问题。# Tuist 集成与模块化拆分（Vibeviewer）

本规则记录项目接入 Tuist、按 Feature 拆分为独立 SPM 包、UI 层依赖注入，以及常见问题排查与修复。

## 标准方案（Single Source of Truth）
- 仅在 `Project.swift` 的 `packages` 节点声明本地包，保持“单一来源”。
- 不使用 `Tuist/Dependencies.swift` 声明本地包，避免与 `Project.swift` 重复导致解析冲突。
- App 目标依赖统一使用 `.package(product:)`。
- 生成工程：`make generate`；清理：`make clear`（仅清当前项目 DerivedData 与项目级 Tuist 缓存）。

示例（节选，自 `Project.swift`）
```swift
packages: [
    .local(path: "Packages/VibeviewerCore"),
    .local(path: "Packages/VibeviewerModel"),
    .local(path: "Packages/VibeviewerAPI"),
    .local(path: "Packages/VibeviewerLoginUI"),
    .local(path: "Packages/VibeviewerMenuUI"),
    .local(path: "Packages/VibeviewerSettingsUI")
],
```

## UI 层依赖注入（遵循 project.mdc）
- 不使用 MVVM；视图内部用 `@State` 管理轻量状态。
- 使用 Environment 注入跨模块依赖：
  - 在 `VibeviewerModel` 暴露 `EnvironmentValues.cursorStorage`。
  - 在 `VibeviewerMenuUI` 暴露 `EnvironmentValues.cursorService`、`loginWindowManager`、`settingsWindowManager`。
- App 注入：
```swift
MenuPopoverView()
    .environment(\.cursorService, DefaultCursorService())
    .environment(\.cursorStorage, CursorStorage.shared)
    .environment(\.loginWindowManager, LoginWindowManager.shared)
    .environment(\.settingsWindowManager, SettingsWindowManager.shared)
```
- 视图使用：
```swift
@Environment(\.cursorService) private var service
@Environment(\.cursorStorage) private var storage
@Environment(\.loginWindowManager) private var loginWindow
@Environment(\.settingsWindowManager) private var settingsWindow
```

## Feature 拆包规范
- 单一职责：
  - `VibeviewerLoginUI`：登录视图与窗口
  - `VibeviewerMenuUI`：菜单视图与业务触发
  - `VibeviewerSettingsUI`：设置视图与窗口
- 每个包必须包含测试目录 `Tests/<TargetName>Tests/`（即便是占位），否则会出现测试路径报错。

## 常见问题与排查
- 包在 Xcode 里显示为“文件夹 + ?”，不是 SPM 包：
  - 原因：`Project.swift` 与 `Tuist/Dependencies.swift` 同时声明了本地包（重复来源），或 SwiftPM/Xcode 缓存脏。
  - 处理：删除 `Tuist/Dependencies.swift` 的本地包声明（本项目直接删除该文件）；删除各包 `.swiftpm/`；`make clear` 后再 `make generate`。
- “Couldn't load project at …/.swiftpm/xcode”：
  - 原因：加载了过期的 `.swiftpm/xcode` 子工程缓存。
  - 处理：删除对应包 `.swiftpm/` 后重新生成。
- `no such module 'X'`：
  - 原因：缺少包/目标依赖或未在 `packages` 声明路径。
  - 处理：在包的 `Package.swift` 增加依赖；在 `Project.swift` 的 `packages` 增加 `.local(path:)`；再生成。
- 捕获列表语法错误（如 `[weak _ = service]`）：
  - Swift 不允许匿名弱引用捕获。移除该语法，使用受控任务生命周期（持有 `Task` 并适时取消）。

## Make 命令
- 生成：
```bash
make generate
```
- 清理（当前项目）：
```bash
make clear
```

## 新增 Feature 包 Checklist
1. 在 `Packages/YourFeature/` 创建 `Package.swift`、`Sources/YourFeature/`、`Tests/YourFeatureTests/`。
2. 在 `Package.swift` 写入 `.package(path: ...)` 与 `targets.target.dependencies`。
3. 在 `Project.swift` 的 `packages` 增加 `.local(path: ...)`，并在 App 目标依赖加 `.package(product: ...)`。
4. `make generate` 重新生成。

> 经验：保持“单一来源”（只在 `Project.swift` 声明本地包）显著降低 Tuist/SwiftPM 解析歧义与缓存问题。