spb/forge-studio Public
The Instruments of LLM training — a native macOS cockpit for Forge. Train language models from scratch on Apple Silicon without a terminal.
Swift 95.7%
Shell 4.3%
1// Author: Simon-Pierre Boucher — contact@spboucher.ai2//3// Run registry with atomic persistence: every mutation writes4// runs.json.tmp then renames — the app survives kill -9 at any moment with5// either the old or the new registry, never a torn one.6import Foundation78@Observable9final class RunStore {10 private(set) var runs: [Run] = []11 let workspaceURL: URL12 private var registryURL: URL { workspaceURL.appendingPathComponent("runs.json") }1314 init(workspace: URL) {15 workspaceURL = workspace16 load()17 }1819 func load() {20 guard let data = try? Data(contentsOf: registryURL),21 let decoded = try? JSONDecoder().decode([Run].self, from: data) else {22 return23 }24 runs = decoded25 }2627 func upsert(_ run: Run) {28 if let i = runs.firstIndex(where: { $0.id == run.id }) {29 runs[i] = run30 } else {31 runs.append(run)32 }33 persist()34 }3536 func remove(_ run: Run) {37 runs.removeAll { $0.id == run.id }38 persist()39 }4041 private func persist() {42 do {43 let enc = JSONEncoder()44 enc.outputFormatting = [.prettyPrinted, .sortedKeys]45 enc.dateEncodingStrategy = .iso860146 let data = try enc.encode(runs)47 let tmp = registryURL.appendingPathExtension("tmp")48 try FileManager.default49 .createDirectory(at: workspaceURL, withIntermediateDirectories: true)50 try data.write(to: tmp, options: .atomic)51 _ = try FileManager.default.replaceItemAt(registryURL, withItemAt: tmp)52 } catch {53 // Persistence failure must be visible, never silent: kept on the54 // store for the UI to surface.55 lastPersistError = error.localizedDescription56 }57 }5859 private(set) var lastPersistError: String?60}61