summaryrefslogtreecommitdiff
path: root/makima/frontend/src/lib/api.ts
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/lib/api.ts')
-rw-r--r--makima/frontend/src/lib/api.ts54
1 files changed, 53 insertions, 1 deletions
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 17cf1d0..f777ba0 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3597,7 +3597,7 @@ export async function pickUpOrders(directiveId: string): Promise<PickUpOrdersRes
// to plain `Contract`. URLs and the user-facing UI already say "contract".
// =============================================================================
-export type DirectiveContractStatus = "draft" | "active" | "shipped" | "archived";
+export type DirectiveContractStatus = "draft" | "queued" | "active" | "shipped" | "archived";
/** How a contract's commits land. `shared` (default): on the directive's
* branch — multiple contracts feed one PR. `own_pr`: a contract-specific
@@ -3700,6 +3700,58 @@ export async function reorderDirectiveContract(
return res.json();
}
+// ----- Lifecycle transitions -----------------------------------------------
+//
+// Lock & Start: draft → queued (if a sibling is active) or active.
+// Pause: active → queued; next queued sibling auto-promotes.
+// Complete: active → shipped, optionally records pr_url + pr_branch.
+// Unlock: queued or active → draft (reactivates editing).
+//
+// All four reach the same /contracts/{id}/<action> endpoint and surface
+// 400 with a plain message string when the transition is invalid.
+
+async function postContractAction(
+ contractId: string,
+ action: "start" | "pause" | "complete" | "unlock",
+ body?: object,
+): Promise<DirectiveContract> {
+ const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/${action}`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body ?? {}),
+ });
+ if (!res.ok) {
+ let detail = res.statusText;
+ try {
+ const err = await res.json();
+ if (err?.message) detail = err.message;
+ } catch {
+ /* fall through to statusText */
+ }
+ throw new Error(`Failed to ${action} contract: ${detail}`);
+ }
+ return res.json();
+}
+
+export async function startDirectiveContract(contractId: string): Promise<DirectiveContract> {
+ return postContractAction(contractId, "start");
+}
+
+export async function pauseDirectiveContract(contractId: string): Promise<DirectiveContract> {
+ return postContractAction(contractId, "pause");
+}
+
+export async function completeDirectiveContract(
+ contractId: string,
+ opts: { prUrl?: string; prBranch?: string } = {},
+): Promise<DirectiveContract> {
+ return postContractAction(contractId, "complete", opts);
+}
+
+export async function unlockDirectiveContract(contractId: string): Promise<DirectiveContract> {
+ return postContractAction(contractId, "unlock");
+}
+
/** Steps and tasks attached to a single contract. Drives the per-contract
* `tasks/` subfolder in the sidebar — when the contract ships, its
* tasks visually move with it. */