import Foundation import Observation /// `build list `, narrowed by `filter`, thirty to a page as the web /// pages it. Rows stay in server order: a client sort would reorder them /// across a page boundary. @Observable @MainActor final class BuildListViewModel { let list: PagedListModel /// A change reloads from the first page. The sheet edits a copy and /// assigns once, so a typed branch does not fire a request per /// keystroke. var filter = BuildFilter() { didSet { guard filter != oldValue else { return } list.argv = ["build", "list", repoPath] + filter.flags() list.emptyMessage = filter.emptyMessage reloadTask?.cancel() reloadTask = Task { await list.reload() } } } private var reloadTask: Task? /// The jobs a trigger can name. Empty when the repo has no CI /// config — the web hides its trigger form in that case. private(set) var jobs: [CIJob] = [] private(set) var actionError: String? private(set) var working = false private let client: GitbayClient let repoPath: String init(client: GitbayClient, repoPath: String) { self.client = client self.repoPath = repoPath list = PagedListModel( client: client, argv: ["build", "list", repoPath] + BuildFilter().flags(), emptyMessage: BuildFilter().emptyMessage, pageSize: 30 ) } var state: LoadState<[Build]> { list.state } func load() async { reloadTask?.cancel() await list.reload() } /// The list's `.task`: reads only while nothing is shown yet. func loadIfNeeded() async { guard list.needsLoad else { return } await load() } /// `build jobs ` — what the picker offers. A repo /// without a CI config answers "not found"; that means no jobs, not /// a failure worth showing. func loadJobs() async { jobs = (try? await client.readList(["build", "jobs", repoPath], of: CIJob.self)) ?? [] } /// `build trigger ` — queue a job now. func trigger(job: String) async { working = true actionError = nil defer { working = false } do { try await client.run(["build", "trigger", repoPath, job]) await load() } catch let error as GitbayError { actionError = error.userFacingMessage } catch { actionError = GitbayError.transport(error).userFacingMessage } } }