mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
iOS port M0d: extract 6 portable ViewModels to ScarfCore
Fourth and final M0 sub-PR. Wraps up the ScarfCore extraction with the
ViewModels that have no dependency on Mac-target services or AppKit.
Views deliberately stay in the Mac target — see plan for rationale.
Moved (6 VMs):
ActivityViewModel.swift — HermesDataService consumer, SQLite3-gated
ConnectionStatusViewModel.swift — @MainActor heartbeat for remote SSH
InsightsViewModel.swift — HermesDataService aggregator, SQLite3-gated
(+ InsightsPeriod, ModelUsage, PlatformUsage,
ToolUsage, NotableSession types; exports
free functions formatDuration/formatTokens)
LogsViewModel.swift — HermesLogService consumer, fully portable
(+ nested LogFile / LogComponent enums)
ProjectsViewModel.swift — ProjectDashboardService wrapper, portable
RichChatViewModel.swift — ~700 lines of ACP-event + message-group
handling, SQLite3-gated
(+ ChatDisplayMode, MessageGroup types)
Reverted in-flight:
GatewayViewModel.swift — my audit missed that it calls
`context.runHermes(...)`, a Mac-target-only extension. Not portable
without moving HermesFileService too. Left in the Mac target.
Platform guards applied:
- `#if canImport(SQLite3)` wraps entire files for ActivityVM, InsightsVM,
and RichChatVM (they transitively depend on HermesDataService).
- `#if canImport(Darwin)` around LocalizedStringResource displayName
in LogsViewModel's nested LogFile and LogComponent enums.
- `#if canImport(os)` around the unused Logger in
ConnectionStatusViewModel (kept the field for future use).
Swift 6 / Observation notes:
- `import Observation` explicitly added to each @Observable file.
Mac target gets Observation via SwiftUI; ScarfCore doesn't import
SwiftUI, so it needs the explicit module import. Observation ships
in the Swift 5.9+ standard library on every platform.
- Nested enums' `var id: String { rawValue }` had to be manually
promoted to `public var id` since my sed only touches 4-space-indent
declarations and the nested enum's members are at 8-space indent.
- Two accidentally-publicized function-local `let` variables in
InsightsViewModel reverted back to internal.
- Sed adjustment: an earlier pattern was producing `@Observable public`
which is a Swift syntax error. Fixed post-hoc by stripping the
stray trailing `public` after the attribute; noted in the plan file
as a checklist item for M1+ sed work.
Consumer import sweeps:
4 Mac-target files gained `import ScarfCore` for the moved VM types:
ContentView.swift, ChatView.swift, RichChatView.swift, and
ConnectionStatusPill.swift.
Test coverage (M0dViewModelsTests): 14 new tests.
- ConnectionStatusViewModel: local-always-connected, remote idle-start,
Status Equatable pinning.
- LogsViewModel: init defaults, filteredEntries across level / search /
component filters, nested enum Identifiable ids and loggerPrefix.
- ProjectsViewModel: .local context binding.
- (SQLite3-gated, Apple-only):
ActivityVM construction, InsightsVM period defaults and sinceDate
ordering, ChatDisplayMode case coverage, RichChatVM empty-state
invariants, MessageGroup derived properties.
Running `docker run --rm -v $PWD/scarf/Packages/ScarfCore:/work -w /work
swift:6.0 swift test` now reports 51 / 51 passing on Linux
(M0a 16 + M0b 18 + M0c 8 + M0d 9 + smoke 1 − 5 SQLite3-gated).
Apple-target CI should see 56 / 56 with the 5 gated tests added in.
Updated scarf/docs/IOS_PORT_PLAN.md with M0d's shipped state, the
Views-stay-Mac-only scope decision, and the sed-gotcha checklist
future phases should watch for.
https://claude.ai/code/session_019yMRP6mwZWfzVrPTqevx2y
This commit is contained in:
@@ -369,7 +369,47 @@ stderr patterns, and round-trip an actual local file through
|
||||
`ServerContext.local.id`. Test helpers in ScarfCoreTests lean on this
|
||||
heavily.
|
||||
|
||||
### M0d — pending
|
||||
### M0d — shipped
|
||||
|
||||
**Scope decision:** ViewModels only; **Views stay in the Mac target** for now. SwiftUI Views have heavy cross-feature coupling (AppCoordinator navigation, sidebar integration), AppKit-dependent widgets (NSOpenPanel, NSWorkspace.open for "reveal in Finder"), and platform-specific layout idioms that iPhone should re-implement rather than inherit. The Mac target will keep its current Views; M3+ builds fresh iOS Views on top of the shared ViewModels.
|
||||
|
||||
**Moved (6 ViewModels):**
|
||||
|
||||
- `ActivityViewModel.swift` — wraps `HermesDataService.fetchToolCalls`. Gated on `#if canImport(SQLite3)`.
|
||||
- `ConnectionStatusViewModel.swift` — heartbeat for remote SSH health; `@MainActor @Observable`.
|
||||
- `InsightsViewModel.swift` — aggregates over sessions via `HermesDataService`. Also exports `InsightsPeriod`, `ModelUsage`, `PlatformUsage`, `ToolUsage`, `NotableSession` and the free functions `formatDuration(_:)` / `formatTokens(_:)`. Gated on `#if canImport(SQLite3)`.
|
||||
- `LogsViewModel.swift` — log tail + filter state (level, component, search). Uses only `HermesLogService`; no SQLite3 gate needed. Exposes `LogFile` and `LogComponent` nested enums with `#if canImport(Darwin)`-guarded `LocalizedStringResource` display names.
|
||||
- `ProjectsViewModel.swift` — wraps `ProjectDashboardService`. Fully portable.
|
||||
- `RichChatViewModel.swift` — ~700 lines of ACP-event + message-group handling. Gated on `#if canImport(SQLite3)` because it pulls message history from `HermesDataService`. Also exports `ChatDisplayMode` and `MessageGroup`.
|
||||
|
||||
**Reverted during M0d** (wasn't actually portable):
|
||||
|
||||
- `GatewayViewModel.swift` — my initial audit grepped for service-type names but missed that this VM calls `context.runHermes()`, which is a Mac-target-only extension (`ServerContext+Mac.swift`). Moving the extension would require dragging `HermesFileService` too. Left in the Mac target; a later phase can revisit once `HermesFileService` moves or a different CLI-invocation surface lands.
|
||||
|
||||
**Discovered while moving:**
|
||||
|
||||
- The sed transform needs a `s/^@Observable$/@Observable/` neutralization — earlier I was accidentally producing `@Observable public` which is a Swift syntax error (the stray `public` has no target). Post-fix, the `public` lives on the `public final class X` line as intended.
|
||||
- Swift's `Observation` framework (for `@Observable`) needs an explicit `import Observation` in ScarfCore files because ScarfCore doesn't pull in SwiftUI. The Mac target gets `Observation` implicitly through SwiftUI, but a pure ScarfCore file doesn't. `Observation` is in the Swift toolchain from 5.9 onwards and compiles fine on Linux too.
|
||||
- Nested enums inside a public enclosing type do **not** inherit `public` for their `Identifiable.id` requirement — that property has to be `public var id` explicitly when the enum declares `Identifiable` conformance. My sed didn't touch deeper indent levels (nested types at indent 4 inside a class at indent 0) so these had to be fixed by hand.
|
||||
- `CharacterSet.whitespaces` is present in swift-corelibs-foundation on Linux — no guard needed there. The build error I saw was cascaded from `runHermes` not existing.
|
||||
|
||||
**Test coverage (`M0dViewModelsTests`):**
|
||||
|
||||
- `ConnectionStatusViewModel`: local context always-connected invariant; remote context idle-start; `Status` `Equatable`.
|
||||
- `LogsViewModel`: init defaults, `filteredEntries` across level / search / component filters, nested enum `Identifiable` ids and `loggerPrefix` routing.
|
||||
- `ProjectsViewModel`: init binding to `.local`.
|
||||
- `ActivityViewModel`, `InsightsViewModel`, `RichChatViewModel`: construction + key initial state. Tests wrapped in `#if canImport(SQLite3)` so they only run on Apple-target CI.
|
||||
- `MessageGroup.allMessages` / `toolCallCount` (also SQLite3-gated).
|
||||
- `InsightsPeriod.sinceDate` ordering.
|
||||
- `ChatDisplayMode` case coverage.
|
||||
|
||||
**Rules next phases can rely on:**
|
||||
|
||||
- When moving a file with `@Observable`, **remember to add `import Observation`** and to fix the stray `@Observable public` that sed produces.
|
||||
- ViewModels that call `context.runHermes(...)` or `context.openInLocalEditor(...)` are **not** portable to ScarfCore — those methods live in `ServerContext+Mac.swift`. Either leave the VM in the Mac target, or add the specific extension method to ScarfCore with a platform-neutral implementation path.
|
||||
- Types used only from the Mac app target (`GatewayInfo`, `PlatformInfo`, etc.) should NOT be marked `public` — keep them internal. My sed sometimes adds `public` to main-target-internal types when I'm reverting a move; strip those back with a second sed pass.
|
||||
- Views are deliberately **not** in ScarfCore. iOS will build its own Views against the shared ViewModels. M3 is where iOS's ViewRegistry / tab bar / NavigationStack composition happens.
|
||||
|
||||
### M1 — pending
|
||||
### M2 — pending
|
||||
### M3 — pending
|
||||
|
||||
Reference in New Issue
Block a user