Add the macOS client spike and record its SwiftUI gap results

This commit is contained in:
Ian Gulliver
2026-10-16 22:23:31 -07:00
parent 7e0e6d9585
commit c2d0ff77d7
16 changed files with 1281 additions and 20 deletions
+94
View File
@@ -0,0 +1,94 @@
import Foundation
import Observation
struct ProjectRow: Decodable {
let id: String
let name: String
}
struct TaskRow: Decodable, Hashable {
let id: String
let title: String
let status: String
}
struct BlobRow: Decodable {
let id: String
}
struct SpikeError: Error, CustomStringConvertible {
let description: String
}
@MainActor
@Observable
final class Server {
var origin = UserDefaults.standard.string(forKey: "byre.server") ?? ""
var tasks: [TaskRow] = []
var taskId = ""
var status = ""
var lines: [String] = []
func log(_ line: String) {
lines.append(line)
}
private var base: URLComponents?
func url(_ path: String, _ query: [URLQueryItem] = []) throws -> URL {
guard var c = base else {
throw SpikeError(description: "not connected")
}
c.path = path
if !query.isEmpty {
c.queryItems = query
}
guard let u = c.url else {
throw SpikeError(description: "bad URL for \(path)")
}
return u
}
func data(_ request: URLRequest) async throws -> Data {
let (data, response) = try await URLSession.shared.data(for: request)
let code = (response as! HTTPURLResponse).statusCode
if !(200..<300).contains(code) {
throw SpikeError(description: "\(code) \(String(decoding: data, as: UTF8.self))")
}
return data
}
func get<T: Decodable>(_ type: T.Type, _ path: String, _ query: [URLQueryItem] = []) async throws -> T {
try JSONDecoder().decode(T.self, from: try await data(URLRequest(url: try url(path, query))))
}
func connect() async {
guard let c = URLComponents(string: origin), c.scheme == "https", let host = c.host, !host.isEmpty, c.path.isEmpty || c.path == "/", c.url != nil else {
base = nil
status = "Server URL must be an https origin with no path, such as https://host.ts.net:8270"
return
}
base = c
UserDefaults.standard.set(origin, forKey: "byre.server")
do {
var all: [TaskRow] = []
for p in try await get([ProjectRow].self, "/api/projects") {
all += try await get([TaskRow].self, "/api/projects/\(p.id)/tasks")
}
tasks = all.filter { $0.status == "active" } + all.filter { $0.status != "active" }
if !tasks.contains(where: { $0.id == taskId }) {
taskId = tasks.first?.id ?? ""
}
status = "\(tasks.count) tasks"
} catch {
status = "\(error)"
}
}
func upload(_ image: Data) async throws -> String {
var req = URLRequest(url: try url("/api/blobs"))
req.httpMethod = "POST"
req.httpBody = image
return try JSONDecoder().decode(BlobRow.self, from: try await data(req)).id
}
}