-
-
Notifications
You must be signed in to change notification settings - Fork 39
Adding a Feature Module
The MVVM-F recipe. Adding a feature touches 4 existing files and creates 2 new ones.
scarf/scarf/scarf/Features/MyFeature/
Views/
MyFeatureView.swift
ViewModels/
MyFeatureViewModel.swift
Both subdirectories are conventional — even features that only have one view of each follow this shape so file discovery is consistent.
import Observation
@Observable
final class MyFeatureViewModel {
let context: ServerContext
private let fileService: HermesFileService
init(context: ServerContext) {
self.context = context
self.fileService = HermesFileService(context: context)
}
var items: [MyModel] = []
var loadError: String?
func load() async {
do {
items = try await fetchItems()
loadError = nil
} catch {
loadError = error.localizedDescription
}
}
}Conventions:
- Always take
ServerContextininitso the feature works against any window's bound server. - Construct any services inside
init; don't hand them in. - Use
@Observable(Swift macro), notObservableObject. - Public state is
varproperties; mutate them onMainActor. Async work runsnonisolatedand assigns the final value back on the main actor.
struct MyFeatureView: View {
@State private var viewModel: MyFeatureViewModel
@Environment(AppCoordinator.self) private var coordinator
@Environment(HermesFileWatcher.self) private var fileWatcher
init(context: ServerContext) {
_viewModel = State(initialValue: MyFeatureViewModel(context: context))
}
var body: some View {
List(viewModel.items) { item in /* … */ }
.navigationTitle("My Feature")
.task { await viewModel.load() }
.task(id: fileWatcher.lastChangeDate) {
// Re-load when ~/.hermes/ changes
await viewModel.load()
}
}
}Conventions:
- View takes
ServerContextin itsinit; it's the only initializer parameter. -
@State private var viewModel: MyFeatureViewModel—@Stateis the right wrapper for@Observableclasses inside views. - Read coordinator and watcher from
@Environment. - Use
.task(id:)for reactive reloads — make sure you include every dependency in the id, or changes to a missing one won't trigger reload.
In Navigation/AppCoordinator.swift:
enum SidebarSection: String, CaseIterable, Identifiable {
// … existing cases …
case myFeature = "My Feature"
var icon: String {
switch self {
// … existing icons …
case .myFeature: return "star.fill" // pick an SF Symbol
}
}
}In Navigation/SidebarView.swift, add the case to the right Section:
Section("Interact") {
ForEach([SidebarSection.chat, .memory, .skills, .myFeature]) { section in
Label(section.rawValue, systemImage: section.icon).tag(section)
}
}Pick the section thematically — Monitor for views, Interact for talking-to-Hermes, Configure for setup, Manage for operational.
In ContentView.swift's detailView switch:
switch coordinator.selectedSection {
// … existing cases …
case .myFeature: MyFeatureView(context: serverContext)
}If you needed a new service to back this feature, add it under Core/Services/ and inject any shared instance in ContextBoundRoot via .environment(...). See Adding a Service.
The hard rules (CLAUDE.md):
-
Features never import sibling features. If
MyFeatureneeds data another feature also uses, the data lives in a service, not in that other feature. -
Cross-feature navigation goes through
AppCoordinator. Setcoordinator.selectedSection = .otherFeatureand (if needed)coordinator.selectedSessionId = ....
- ✏️
Navigation/AppCoordinator.swift— 1 enum case, 1 icon line. - ✏️
Navigation/SidebarView.swift— add to the rightSection. - ✏️
ContentView.swift— 1 switch case. - ✏️
scarfApp.swift— only if you needed to inject a new shared service. - ✨
Features/MyFeature/Views/MyFeatureView.swift— new. - ✨
Features/MyFeature/ViewModels/MyFeatureViewModel.swift— new.
Total: ~5-10 lines across 4 existing files, plus 2 new files.
Last updated: 2026-04-20 — Scarf v2.0.1
Wiki edited via the local .wiki-worktree/ clone. See Wiki Maintenance for the workflow. Last sync: 2026-04-20.
Getting Started
ScarfGo (iOS)
User Guide
- Dashboard
- Insights & Activity
- Chat
- Slash Commands
- Memory & Skills
- Projects & Profiles
- Project Templates
- Template Catalog
- Template Ideas
- Platforms / Personalities / Quick Commands
- Servers & Remote
- MCP, Plugins, Webhooks, Tools
- Gateway / Cron / Health / Logs
Architecture
- Overview
- Core Services
- Design System
- Data Model
- Transport Layer
- ScarfCore Package
- Sidebar & Navigation
- ACP Subprocess
Developer Guide
Reference
Troubleshooting
Contributing
Release History
Legal & Support
Unsorted