diff options
Diffstat (limited to 'makima/frontend/src/lib/api.ts')
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 127 |
1 files changed, 76 insertions, 51 deletions
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 80da511..17cf1d0 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3583,116 +3583,141 @@ export async function pickUpOrders(directiveId: string): Promise<PickUpOrdersRes } // ============================================================================= -// Directive Documents API +// Directive Contracts API // -// A directive_document is one of N markdown documents owned by a directive. -// Each has its own lifecycle (draft → active → shipped → archived) and may be -// attached to a PR. The frontend never calls the /ship endpoint — the backend -// invokes that itself when PRs are raised. Editing a shipped document -// auto-reactivates it on the backend (see PATCH handler). +// A "directive contract" (DB table: directive_documents) is one of N spec +// documents owned by a directive. Each has its own lifecycle +// (draft → active → shipped → archived) and may be attached to a PR. +// Contracts run sequentially in the directive's shared worktree (or, when +// mergeMode = 'own_pr', on a contract-specific branch). +// +// Naming note: types are `DirectiveContract*` (rather than `Contract*`) +// because the legacy contracts system still defines a `Contract` type at +// the top of this file. Once Phase 5 drops legacy contracts, we'll rename +// to plain `Contract`. URLs and the user-facing UI already say "contract". // ============================================================================= -export type DirectiveDocumentStatus = "draft" | "active" | "shipped" | "archived"; +export type DirectiveContractStatus = "draft" | "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 + * branch + PR carved out at activation time. */ +export type DirectiveContractMergeMode = "shared" | "own_pr"; -export interface DirectiveDocument { +export interface DirectiveContract { id: string; directiveId: string; title: string; body: string; - status: DirectiveDocumentStatus; + status: DirectiveContractStatus; prUrl: string | null; prBranch: string | null; shippedAt: string | null; archivedAt: string | null; version: number; + /** 0-indexed queue position within the parent directive. */ + position: number; + mergeMode: DirectiveContractMergeMode; createdAt: string; updatedAt: string; } -export interface CreateDirectiveDocumentRequest { +export interface CreateDirectiveContractRequest { title?: string; body?: string; } -export interface UpdateDirectiveDocumentRequest { +export interface UpdateDirectiveContractRequest { title?: string; body?: string; + mergeMode?: DirectiveContractMergeMode; } -export async function listDirectiveDocuments(directiveId: string): Promise<DirectiveDocument[]> { - const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/documents`); - if (!res.ok) throw new Error(`Failed to list directive documents: ${res.statusText}`); +export async function listDirectiveContracts( + directiveId: string, +): Promise<DirectiveContract[]> { + const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/contracts`); + if (!res.ok) throw new Error(`Failed to list contracts: ${res.statusText}`); return res.json(); } -export async function createDirectiveDocument( +export async function createDirectiveContract( directiveId: string, - req: CreateDirectiveDocumentRequest = {}, -): Promise<DirectiveDocument> { - const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/documents`, { + req: CreateDirectiveContractRequest = {}, +): Promise<DirectiveContract> { + const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/contracts`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(req), }); - if (!res.ok) throw new Error(`Failed to create directive document: ${res.statusText}`); + if (!res.ok) throw new Error(`Failed to create contract: ${res.statusText}`); return res.json(); } -export async function getDirectiveDocument(documentId: string): Promise<DirectiveDocument> { - const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}`); - if (!res.ok) throw new Error(`Failed to get directive document: ${res.statusText}`); +export async function getDirectiveContract(contractId: string): Promise<DirectiveContract> { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}`); + if (!res.ok) throw new Error(`Failed to get contract: ${res.statusText}`); return res.json(); } /** - * Update a directive document's title and/or body. The backend auto-reactivates - * a shipped document when its body changes (re-stamps status to `active`), - * which is why we don't expose a separate "reactivate" call. + * Update a contract's title, body, and/or merge mode. Backend auto- + * reactivates a shipped contract when its body changes (status flips + * back to `active`), so there's no separate reactivate call. */ -export async function updateDirectiveDocument( - documentId: string, - req: UpdateDirectiveDocumentRequest, -): Promise<DirectiveDocument> { - const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}`, { +export async function updateDirectiveContract( + contractId: string, + req: UpdateDirectiveContractRequest, +): Promise<DirectiveContract> { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(req), }); - if (!res.ok) throw new Error(`Failed to update directive document: ${res.statusText}`); + if (!res.ok) throw new Error(`Failed to update contract: ${res.statusText}`); + return res.json(); +} + +export async function archiveDirectiveContract(contractId: string): Promise<DirectiveContract> { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/archive`, { + method: "POST", + }); + if (!res.ok) throw new Error(`Failed to archive contract: ${res.statusText}`); return res.json(); } -export async function archiveDirectiveDocument(documentId: string): Promise<DirectiveDocument> { - const res = await authFetch(`${API_BASE}/api/v1/directive-documents/${documentId}/archive`, { +/** Move a contract to a new 0-indexed queue position inside its directive. */ +export async function reorderDirectiveContract( + contractId: string, + position: number, +): Promise<DirectiveContract> { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/reorder`, { method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ position }), }); - if (!res.ok) throw new Error(`Failed to archive directive document: ${res.statusText}`); + if (!res.ok) throw new Error(`Failed to reorder contract: ${res.statusText}`); return res.json(); } -/** Steps and tasks attached to a single directive document. Drives the - * per-document `tasks/` subfolder in the sidebar — when the document - * ships, its tasks visually move with it under shipped/. */ -export interface DocumentTasksResponse { +/** 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. */ +export interface DirectiveContractTasksResponse { steps: DirectiveStep[]; tasks: Task[]; } /** - * List the steps and ephemeral tasks attached to a specific directive - * document. Used by the sidebar to render a `tasks/` subfolder beside each - * document — including shipped documents, whose tasks remain attached so - * they continue to render under shipped/ alongside the document. + * List the steps and ephemeral tasks attached to a specific contract. + * Used by the sidebar to render a `tasks/` subfolder beside each + * contract — including shipped ones, whose tasks remain attached. */ -export async function listDirectiveDocumentTasks( - documentId: string, -): Promise<DocumentTasksResponse> { - const res = await authFetch( - `${API_BASE}/api/v1/directive-documents/${documentId}/tasks`, - ); - if (!res.ok) { - throw new Error(`Failed to list directive document tasks: ${res.statusText}`); - } +export async function listDirectiveContractTasks( + contractId: string, +): Promise<DirectiveContractTasksResponse> { + const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/tasks`); + if (!res.ok) throw new Error(`Failed to list contract tasks: ${res.statusText}`); return res.json(); } |
