Appendix R: OPerationsHOS (Shop Manual)

Status: In development. GPL v3. Public repo at github.com/fluhartyml/OPerationsHOS. Distribution-ready binary for iPhone, iPad, and native macOS as of 2026-05-10. Not yet on App Store. OPerationsHOS is an AI proof-of-concept: architecture by ChatGPT (OpenAI), engineered by Claude (Anthropic). Two AI systems, distinct roles. Michael Fluharty is the human operator, decision-maker, and license holder. The split is intentional and load-bearing — no in-place substitutions, no role overlap.

What it is

OPerationsHOS is a personal operations console: a place to record every appliance warranty, home system, project, contact, document, timer, and reminder that a household generates over time. Its central design rule, lifted directly from the 2026 MAY 04 roadmap: one universal record model first, modules later as filtered lenses. Everything in the app is an OperatorItem. There are no per-type detail views, no parallel WarrantyView / ApplianceView / ProjectView code paths — one detail renderer, many record types.

Architecture overview

The project is a SwiftUI app with SwiftData persistence, an App Group widget extension, and platform-tuned shells for iPhone, iPad, and native macOS.

File roles, by category:

The widget extension lives in OPerationsHOSWidgets/ as a sibling target, embedded into the host app's bundle on iOS only (the embed reference carries platformFilter = ios in the .pbxproj so native macOS builds skip the iOS-shaped widget).

The universal record model

Every record is a single OperatorItem:

@Model
final class OperatorItem {
    var id: UUID
    var title: String
    var subtitle: String
    var body: String
    var type: ItemType
    var status: ItemStatus
    var priority: ItemPriority
    var createdDate: Date
    var updatedDate: Date
    var dueDate: Date?
    var pinned: Bool
    var archived: Bool
    var tags: [String]
    var relatedSystem: String?
    var source: String?

    // Relationships
    @Relationship(deleteRule: .cascade) var activity: [ActivityEvent]
    @Relationship(deleteRule: .cascade) var attachments: [Attachment]
}

The three nested enums — ItemType, ItemStatus, ItemPriority — live in the same file:

All three are String-backed, Codable, and CaseIterable so they can drive both SwiftData persistence and SwiftUI Picker menus.

Activity events

ActivityEvent is the per-item timeline. Every record gets a list of events (noteAdded, statusChanged, attachmentAdded, etc.). The relationship from OperatorItem deletes events along with the parent record. The ActivityKind enum lists the event categories; the text property stores the human-readable summary.

Attachments

Attachment is a file reference, not the file itself. The AttachmentStorage enum distinguishes .inSandbox (URL inside the app's sandbox) from .iCloudDocument (URL inside the user's iCloud Drive). The AttachmentKind enum tags content type (pdf, image, photo, etc.) for filtering and icon selection. Large files never end up inside the SwiftData store — per the roadmap rule, attachments store file references only.

The store

OperatorStore is the @Observable facade between SwiftUI views and SwiftData. Views never touch ModelContext directly; they go through the store. Reasons:

The store is created once at the app entry in AppShellInner.init from the environment's ModelContext, then passed down to every view that needs it.

Platform shell routing

AppShellView picks the shell based on the runtime platform:

var body: some View {
#if os(macOS)
    MacShellView(store: store)
#else
    if horizontalSizeClass == .regular {
        IPadShellView(store: store)
    } else {
        RootTabView(store: store)
    }
#endif
}

Three shells, three patterns:

Tab structure (locked)

The 11 tabs the user sees, in sidebar order:

#TabRole
1DashboardToday / Pinned / Home Systems / Upcoming / Recently Updated / Projects sections
2InboxUnfiled / orphan records
3ScheduleCalendar-native agenda, EventKit-backed
4RemindersReminders.app-native, EventKit two-way sync
5SystemsFiltered: .homeSystem + .appliance
6MaintenanceFiltered: .maintenance
7ProjectsFiltered: .project
8PeopleFiltered: .person
9TimersFiltered: .timer
10PropertyFiltered: .property
11VaultBiometrically gated — Media + Secure Notes

Search and Settings are sibling sidebar entries on iPad / Mac; on iPhone they sit at the end of the TabView.

Service layer

AIService

Anthropic Messages API client. Three actions: summarize(_:), extractDates(from:), suggestCategory(for:). Each builds a system prompt + user prompt, posts to https://api.anthropic.com/v1/messages with the user's API key from the Keychain (KeychainKey.anthropicAPIKey), and returns the parsed text on success or nil with lastError set on failure. Model: claude-haiku-4-5-20251001. maxTokens: 1024. Wired into RecordDetailView at lines 24, 504, 506, 508.

AlarmKitManager

Wrapper around AlarmManager.shared for timer-record integration. iOS / iPadOS / Mac Catalyst only. Guarded with #if canImport(AlarmKit) at the import and at the extension AlarmButton declaration. On native macOS the manager is a stub that returns "AlarmKit is not available on this platform" errors. See Appendix C's "Platform availability and the Mac build" section for the full pattern.

EventKitStore

Calendar and Reminders integration. Bridges OPerationsHOS scheduled records to the user's system calendar (Schedule tab) and task records to Reminders.app (Reminders tab). Two-way sync: changes in either direction reflect in both. Asks for permission via EKEventStore.requestFullAccessToEvents() and the corresponding Reminders permission at first use.

KeychainStorage

An enum KeychainStorage with static read / write / delete methods keyed by KeychainKey (an enum of typed key names). The only key currently in use is .anthropicAPIKey. Generic enough to add more secrets later without expanding the storage surface.

Widget bridge

The widget extension can't share SwiftData with the host app directly. The bridge: WidgetSnapshotPublisher writes a JSON snapshot of the dashboard-relevant records to the App Group container (group.com.ChatGPT.OPerationsHOS) on every store refresh, then calls WidgetCenter.shared.reloadAllTimelines(). The widget reads the JSON, not the SwiftData store. One-way: the widget never writes back. Sandboxing remains intact — the host app holds the SwiftData store; the widget holds a read-only mirror.

Export pipeline

ExportSheet presents a format picker; ExportFormat enumerates the supported outputs (.markdown, .pdf, .csv, .json, .zipWithAttachments); ExportFormatter does the work. Default export scope is "all records" but the formatter accepts a filter predicate for "Home Sale Prep Report," "HVAC History," "Appliance Warranty Packet," and similar packaged reports the user-facing menu lists.

Build + deployment

Known gotchas

Roadmap status (2026 MAY 04 roadmap)

PhaseTopicStatus
0Project setupComplete
1Skeleton appComplete
2Universal record modelComplete
3DashboardComplete
4Record detail viewComplete
5EditingComplete
6SwiftData persistenceComplete
7ModulesComplete
8SearchComplete
9AttachmentsComplete
10Activity logComplete
11Dates and remindersComplete
12Timers / workflowComplete
13AI layerComplete (AIService wired into RecordDetailView)
14iCloud syncComplete (CloudKit automatic on ModelContainer)
15iPad layoutComplete 2026-05-10 (IPadShellView)
16Mac layoutComplete 2026-05-10 (MacShellView, native macOS build)
17ExportComplete (ExportSheet + ExportFormatter)

Phases 18–24 (widget code, App Group entitlement, WidgetSnapshotPublisher writing JSON, WidgetCenter reload) all shipped before the roadmap-complete date. The roadmap is the source of truth; DeveloperNotes.swift in the project repo carries the full text as a comment block.

Sources