feat: rebuild Process Studio into multi-phase Process Creation Wizard\n\n- Replace toy Studio.tsx with proper Wizard.tsx canvas\n- Build 6-step flow: Intake -> Analyze -> Generate -> Validate -> Draft saved -> Publish\n- Implement incremental EA2 writes via wizardApi.ts (flow, batch apply)\n- Use localStorage for draft recovery across reloads\n- Apply industrial-blueprint UI styling matching core doctrine\n- Add Vitest tests for Wizard components and API wrappers

This commit is contained in:
2026-06-14 03:47:26 +04:00
parent b2c1e57c86
commit 4bc7ae228a
27 changed files with 2274 additions and 275 deletions
+40 -1
View File
@@ -174,6 +174,13 @@ async function authedRequest<T>(
export const api = {
config: DEFAULT_CONFIG,
/** Set the bearer token explicitly (e.g. from SSO or login form) */
setBearer(token: string) {
sessionStorage.setItem(TOKEN_KEY, token);
// Also set a non-httpOnly cookie for server-side middleware
document.cookie = `access_token=${token}; path=/; max-age=86400; SameSite=Lax`;
},
/** Subscribe to all API calls. Returns unsubscribe. */
onCall(fn: ApiCallObserver): () => void {
observers.add(fn);
@@ -184,6 +191,38 @@ export const api = {
return authedRequest<AuthMe>(this.config, "GET", "/api/v1/auth/me", undefined, signal);
},
async signIn(email: string, password?: string, signal?: AbortSignal): Promise<{ access_token: string; refresh_token?: string }> {
const payload = password ? { email, password } : { email };
const path = password ? "/api/v1/auth/login" : "/api/v1/auth/dev-login";
const init: RequestInit = {
method: "POST",
headers: { "Content-Type": "application/json", "Accept": "application/json" },
body: JSON.stringify(payload),
signal,
};
const r = await instrumentedFetch("POST", `${this.config.baseUrl}${path}`, init, payload);
if (!r.ok) {
const text = await r.text().catch(() => "");
throw new Error(`Login failed: ${r.status} ${text.slice(0, 100)}`);
}
const data = await r.json() as { access_token: string; refresh_token?: string };
this.setBearer(data.access_token);
return data;
},
async devLoginConfig(signal?: AbortSignal): Promise<{ enabled: boolean }> {
try {
const init: RequestInit = { method: "GET", headers: { "Accept": "application/json" }, signal };
const r = await instrumentedFetch("GET", `${this.config.baseUrl}/internal/dev-login-config`, init);
if (r.ok) {
return await r.json() as { enabled: boolean };
}
} catch {
// swallow
}
return { enabled: false };
},
async workItems(signal?: AbortSignal): Promise<WorkItem[]> {
const body = await authedRequest<{ items?: WorkItem[] }>(this.config, "GET", "/api/ea2/work-items?view=all", undefined, signal);
return body.items ?? [];
@@ -256,7 +295,7 @@ export const api = {
{
kind: "definition",
status: "published",
source_context: "mc-demo-studio",
source_context: "mission-control-studio",
version: 1,
...payload,
},
+80
View File
@@ -0,0 +1,80 @@
import { api, type Actor } from "./api";
export interface CreateDraftPayload {
name: string;
display_name: string;
description: string;
source_context: string;
config: {
wizard: {
marker: string;
maxDepth: number;
maxNodes: number;
chat: any[];
uploads: any[];
panelState: any;
debug: any[];
};
};
}
export interface BatchOperation {
collection: string;
op: "insert" | "update" | "delete";
_key?: string;
_from?: string;
_to?: string;
[key: string]: any;
}
export const wizardApi = {
async createDraft(payload: CreateDraftPayload, signal?: AbortSignal) {
const res = await fetch(`${api.config.baseUrl}/api/ea2/flow`, {
method: "POST",
headers: {
Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
kind: "definition",
status: "draft",
...payload,
}),
signal,
});
if (!res.ok) throw new Error(`createDraft failed: ${res.status}`);
return res.json() as Promise<{ _key: string; name: string }>;
},
async updateDraftConfig(key: string, patch: any, signal?: AbortSignal) {
const res = await fetch(`${api.config.baseUrl}/api/ea2/flow/${key}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}`,
"Content-Type": "application/json",
},
body: JSON.stringify(patch),
signal,
});
if (!res.ok) throw new Error(`updateDraftConfig failed: ${res.status}`);
return res.json();
},
async applyBatch(flow_key: string, ops: BatchOperation[], actor: Actor, signal?: AbortSignal) {
const res = await fetch(`${api.config.baseUrl}/api/ea2/apply-batch`, {
method: "POST",
headers: {
Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ flow_key, ops, actor }),
signal,
});
if (!res.ok) throw new Error(`applyBatch failed: ${res.status}`);
return res.json();
},
async startInstance(process_definition_id: string, business_subject?: string, signal?: AbortSignal) {
return api.startTransaction(process_definition_id, business_subject, signal);
}
};