// React provider exposing the canonical SysMLModel + applyOps. // Used by both canvases (and the rail) so they stay in sync. "use client"; import { createContext, useCallback, useContext, useMemo, useState } from "react"; import { applyOps, type ApplyResult } from "./applyOps"; import { validate, type ValidationIssue } from "../sysml/validate"; import type { SysMLModel } from "../sysml/model"; import type { ModelOp } from "./ops"; export interface ModelStoreValue { model: SysMLModel; apply: (ops: ModelOp[]) => ApplyResult; /** Validation issues, recomputed on each successful apply. */ issues: ValidationIssue[]; issuesByElement: Map; } const ModelStoreContext = createContext(null); interface ModelStoreProviderProps { initialModel: SysMLModel; children: React.ReactNode; } export function ModelStoreProvider({ initialModel, children }: ModelStoreProviderProps) { const [model, setModel] = useState(initialModel); const apply = useCallback((ops: ModelOp[]): ApplyResult => { let result: ApplyResult = { model, idMapping: {}, errors: [], applied: false }; setModel(current => { result = applyOps(current, ops); return result.applied ? result.model : current; }); return result; }, [model]); const { issues, issuesByElement } = useMemo(() => { const issues = validate(model); const issuesByElement = new Map(); for (const i of issues) { const k = anchorKey(i.anchor); if (!k) continue; const prev = issuesByElement.get(k) ?? []; prev.push(i); issuesByElement.set(k, prev); } return { issues, issuesByElement }; }, [model]); const value: ModelStoreValue = useMemo( () => ({ model, apply, issues, issuesByElement }), [model, apply, issues, issuesByElement] ); return {children}; } export function useModelStore(): ModelStoreValue { const ctx = useContext(ModelStoreContext); if (!ctx) throw new Error("useModelStore must be used inside ModelStoreProvider"); return ctx; } export function useModel(): SysMLModel { return useModelStore().model; } export function useApply(): (ops: ModelOp[]) => ApplyResult { return useModelStore().apply; } function anchorKey(anchor: ValidationIssue["anchor"]): string | null { switch (anchor.kind) { case "block": return anchor.id; case "association": return `assoc:${anchor.id}`; case "constraint": return `constraint:${anchor.id}`; case "requirement": return `req:${anchor.id}`; case "property": return anchor.blockId; case "model": return null; } }