一键导入
add-screen
Add a screen to an existing feature
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add a screen to an existing feature
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Build the project for testing
Build the project
Take the current plan all the way to a ready-to-merge pull request — review the plan (scaled to risk), implement it test-first, code-review and fix, security-review, verify the acceptance criteria, open the PR, and watch it green. Use after you have an approved plan (e.g. from plan mode) and want the rest of the feature pipeline run end-to-end. Invoking it is itself plan approval — it then runs autonomously to a single hard stop: ready-to-merge.
Run a specific test target or test class
Run all unit tests
Run snapshot tests
| name | add-screen |
| description | Add a screen to an existing feature |
Guide for adding a new screen/view to an existing tab with MVVM navigation.
In MVVM, navigation is owned by the App layer: each tab has a Router
(@Observable @MainActor) holding a typed Route enum stack bound to a
NavigationStack(path:), plus a RouterNavigator value type that implements every
leaf feature's Navigating protocol by mutating the router. A view model never
pushes screens itself — it calls a method on its injected navigator.
Use App/Features/ExploreRoot/{ExploreRouter.swift, Views/ExploreRootView.swift}
and App/Composition/ViewModelFactory.swift as the canonical reference.
Ask the user for:
If the screen needs its own feature module, build it following the add-feature
workflow: a {ScreenName}ViewModel (@Observable @MainActor exposing
ViewState<ViewSnapshot>), a {ScreenName}Dependencies struct with
live(services:), a {ScreenName}Navigating protocol, and a {ScreenName}View
owning the view model via @State.
In App/Composition/ViewModelFactory.swift, add a make{ScreenName} method that
wires the feature's Dependencies.live(services:) to a navigator:
func make{ScreenName}(
id: Int,
navigator: some {ScreenName}Navigating
) -> {ScreenName}ViewModel {
{ScreenName}ViewModel(
id: id,
dependencies: .live(services: services),
navigator: navigator
)
}
In the tab's router file, add a case to the Route enum. The enum is Hashable
and carries the values needed to build the destination view model:
enum {Tab}Route: Hashable {
case existingScreen(id: Int)
case {screenName}(id: Int) // Add this
}
The source feature's view model calls a method on its Navigating protocol. Add
that requirement to the protocol if it doesn't exist:
@MainActor
public protocol {SourceFeature}Navigating {
func open{ScreenName}(id: Int) // Add this
}
Then implement it in the tab's RouterNavigator by appending the new route:
@MainActor
struct {Tab}RouterNavigator: {SourceFeature}Navigating /* , ... */ {
let router: {Tab}Router
func open{ScreenName}(id: Int) {
router.path.append(.{screenName}(id: id))
}
}
And call it from the source view model:
public func selectSomeItem(id: Int) {
navigator.open{ScreenName}(id: id)
}
In the tab's root view, the NavigationStack(path:) is bound to $router.path.
Add the new case to the navigationDestination switch, building the view model
through the factory and a navigator bound to this router:
NavigationStack(path: $router.path) {
{Tab}View(viewModel: {tab}ViewModel)
.navigationDestination(for: {Tab}Route.self) { route in
destination(route)
}
}
@ViewBuilder
private func destination(_ route: {Tab}Route) -> some View {
switch route {
case .existingScreen(let id):
ExistingView(viewModel: factory.makeExisting(id: id, navigator: navigator))
case .{screenName}(let id):
{ScreenName}View(viewModel: factory.make{ScreenName}(id: id, navigator: navigator)) // Add this
}
}
private var navigator: {Tab}RouterNavigator {
{Tab}RouterNavigator(router: router)
}
For a modal instead of a push, add an @Observable presentation item property to
the router (e.g. presented{ScreenName}: Presented{ScreenName}?), have the
navigator method set it, and present it with .sheet(item:) /
.fullScreenCover(item:) in the root view.
Use SCREAMING_SNAKE_CASE keys for all user-facing strings. Build first, then add
English values in Localizable.xcstrings — see SWIFTUI.md § Localization.
Navigating protocol and assert the
new method fires when the triggering view-model action runs.add-feature.$ARGUMENTS