/** * FNXC:CodeOrganization 2026-07-16-20:00: * Board workflow client types/API peeled from legacy.ts. */ import type { Task, WorkflowFieldDefinition, WorkflowFieldType, WorkflowFieldOption, WorkflowFieldRender, WorkflowSettingDefinition, WorkflowSettingType, WorkflowSettingOption, WorkflowSettingRender, WorkflowSettingRejection, } from "@fusion/core"; import { api } from "../client/client.js"; import type { FetchOptions } from "../client/client.js"; import { withProjectId } from "../client/health.js"; import { dedupe } from "../client/dedupe.js"; // Workflow field/setting declaration types re-exported from @fusion/core so // WorkflowSettingsPanel can import them from `../api` (KTD-13/14, U6/KTD-1). export type { WorkflowFieldDefinition, WorkflowFieldType, WorkflowFieldOption, WorkflowFieldRender, WorkflowSettingDefinition, WorkflowSettingType, WorkflowSettingOption, WorkflowSettingRender, WorkflowSettingRejection, }; /** Resolved trait flags for a board column (subset the client cares about). */ export interface BoardWorkflowColumnFlags { countsTowardWip?: boolean; complete?: boolean; archived?: boolean; hiddenFromBoard?: boolean; hold?: boolean; intake?: boolean; /** An intake column that does NOT auto-triage — cards wait for an operator to promote * them. Derived server-side from the `intake` trait's `autoTriage: false` config, * which is configuration rather than a flag and was therefore invisible to clients. */ manualIntake?: boolean; mergeBlocker?: boolean; /** Merge/review lane membership used by the shared live-agent predicate. */ mergeOrchestration?: boolean; humanReview?: boolean; [key: string]: boolean | undefined; } export interface BoardWorkflowColumn { id: string; name: string; /** Optional author-defined explanatory copy from the workflow IR. */ description?: string; flags: BoardWorkflowColumnFlags; /* FNXC:WorkflowResolvedColumns 2026-07-29-00:00 (U12 — R8): Columns this one may move to, from the workflow's own graph adjacency (`resolveAllowedColumns`, the same resolver `moveTaskInternal` validates against). Optional so a client that predates the field keeps working; the move menu falls back to approximating targets from neighbouring columns when it is absent. */ moveTargets?: string[]; } export interface BoardWorkflowDefinition { id: string; name: string; /** Optional compact custom workflow icon; built-ins render the Fusion mark by id. */ icon?: string; columns: BoardWorkflowColumn[]; /** Custom field definitions declared by this workflow (U13/KTD-14). Absent on * workflows with no fields, or from older servers. */ fields?: WorkflowFieldDefinition[]; } export interface BoardWorkflowsPayload { flagEnabled: boolean; defaultWorkflowId: string; workflows: BoardWorkflowDefinition[]; taskWorkflowIds: Record; } /** A typed custom-field rejection surfaced by the PATCH endpoint (KTD-13). */ export interface CustomFieldRejection { code: "no-fields-defined" | "unknown-field" | "type-mismatch" | "enum-violation"; fieldId: string; detail: string; } /** * Patch a task's custom field values (U13/KTD-14). The server validates the * patch against the task's workflow field schema and returns the updated task; * a validation failure surfaces as a 400 carrying `{ fieldId, code, detail }`. * A `null` value for a field deletes it. */ export function updateTaskCustomFields( id: string, customFields: Record, projectId?: string, ): Promise { return api(withProjectId(`/tasks/${id}/custom-fields`, projectId), { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ customFields }), }); } /** Fetch the multi-lane board metadata (U9). When the flag is OFF the server * returns `{ flagEnabled: false }` and the board renders its legacy form. */ export function fetchBoardWorkflows(projectId?: string, options?: FetchOptions): Promise { const path = withProjectId("/tasks/board-workflows", projectId); return dedupe(path, () => api(path), options); }