// // Recipe.swift // Focale // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // // A recipe is a complete, named, one-gesture preset: manual settings, // format, lens, shutter behavior, and the capture context to apply. // Recipes are exportable files โ€” they're meant to be shared (CLAUDE.md ยง6). // import Foundation import UniformTypeIdentifiers struct Recipe: Codable, Identifiable, Sendable, Hashable { typealias ID = UUID let id: ID var name: String var settings: ManualSettings /// Project automatically applied while this recipe is active (by name, /// so an imported recipe stays meaningful on another device). var autoProjectName: String? /// Scene signal that *proposes* this recipe โ€” never a silent switch. var sceneTrigger: SceneTrigger /// Overlay a visible timestamp on captures (e.g. "Chantier" recipe). var showsTimestamp: Bool /// Prioritize OCR indexing for photos taken with this recipe. var prioritizeOCR: Bool init( id: ID = UUID(), name: String, settings: ManualSettings = .automatic, autoProjectName: String? = nil, sceneTrigger: SceneTrigger = .none, showsTimestamp: Bool = false, prioritizeOCR: Bool = false ) { self.id = id self.name = name self.settings = settings self.autoProjectName = autoProjectName self.sceneTrigger = sceneTrigger self.showsTimestamp = showsTimestamp self.prioritizeOCR = prioritizeOCR } enum SceneTrigger: String, Codable, Sendable { case none case lowLight case documentDetected } // MARK: - Import / export (shared as files โ€” a free acquisition channel) static let fileType = UTType(exportedAs: "ai.spboucher.focale.recipe") func exportData() throws -> Data { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] return try encoder.encode(self) } init(importing data: Data) throws { self = try JSONDecoder().decode(Recipe.self, from: data) } } extension Recipe { /// Built-in starting recipes. User-facing names in French. static let builtIns: [Recipe] = [ Recipe( name: "Documents", settings: { var s = ManualSettings.automatic s.format = .heic return s }(), sceneTrigger: .documentDetected, prioritizeOCR: true ), Recipe( name: "Nuit longue pose", settings: { var s = ManualSettings.automatic s.iso = 100 s.shutterSeconds = 0.5 s.format = .proRAW return s }(), sceneTrigger: .lowLight ), Recipe( name: "Chantier", settings: { var s = ManualSettings.automatic s.lens = .ultraWide return s }(), autoProjectName: "Chantier", showsTimestamp: true ), ] }