apps/web/lib/sysml — pure engine (no UI deps) - model.ts: canonical types ported from phase-0 (Block, Property, Association, Constraint, Requirement, SysMLModel) - validate.ts: 12 rules grouped Structural (S1–S5) / Semantic (M1–M5) / Traceability (T1–T2). Pure function returning element-anchored issues. Cycle detection via DFS with canonical-key dedup. T3 deferred to Phase 1.5 (depends on narrative chip references). - depgraph.ts: directed graph over Blocks / Properties / Constraints / Requirements with `dependentsOf()` and `neighborhood()` for impact analysis (substrate for M7). - fromFixture.ts: loose FixtureData → strict SysMLModel converter (interim until M5 unifies state). - breaks.ts: 8 named model corruptions (S1/S2/S3/M2/M3/M5/T1/T2) for ?break=... demonstrations. UI integration in EditorShell + LeftRail + DiagramCanvas + IssuesPanel - Floating IssuesPanel (bottom-right) with severity-grouped counts and collapse. Click an issue to focus its anchor across the rail and diagram. - LeftRail: severity dots on each block in the Model section and on each requirement entry. Tooltips show full issue text. - DiagramCanvas: red/blue/dotted ring around offending blocks via issuesByElement → BlockNode.data.issueSeverity. - ?break=S1-dangling,M2-cycle,M5-dup-property,... query param injects corruptions for verification of the "done when" criterion. - Suspense boundary added at the editor route for useSearchParams. Two pre-existing TipTap typing fixes (editor.storage cast through unknown).
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
// SysML block / actor / constraint rendered as a React Flow custom node.
|
|
// Drives the softened look from the prototype: stereotype label, divider, property compartment.
|
|
|
|
"use client";
|
|
|
|
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
import type { BlockKind } from "../../../lib/fixtures/aristotle";
|
|
|
|
export interface BlockNodeData extends Record<string, unknown> {
|
|
label: string;
|
|
kind: BlockKind | "system";
|
|
properties: string[];
|
|
/** For constraint kinds, optional one-line expression. */
|
|
expression?: string;
|
|
/** Highest severity of any validation issue anchored to this block. */
|
|
issueSeverity?: "error" | "warning" | "soft";
|
|
}
|
|
|
|
const STEREO: Record<string, string> = {
|
|
block: "block",
|
|
actor: "actor",
|
|
constraint: "constraint",
|
|
system: "system",
|
|
};
|
|
|
|
export function BlockNode({ data, selected }: NodeProps) {
|
|
const d = data as BlockNodeData;
|
|
const kind = d.kind ?? "block";
|
|
const isConstraint = kind === "constraint";
|
|
|
|
const cls = [
|
|
"sysml-node",
|
|
`sysml-node-${kind}`,
|
|
selected ? "sysml-node-selected" : "",
|
|
d.issueSeverity ? `sysml-node-issue-${d.issueSeverity}` : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
return (
|
|
<div className={cls}>
|
|
<Handle type="target" position={Position.Left} />
|
|
<Handle type="target" position={Position.Top} />
|
|
|
|
<div className="sysml-node-stereo">«{STEREO[kind] ?? "block"}»</div>
|
|
<div className="sysml-node-title">{d.label}</div>
|
|
|
|
{!isConstraint && <div className="sysml-node-divider" />}
|
|
|
|
{!isConstraint ? (
|
|
<div className="sysml-node-props">
|
|
{d.properties.length === 0 ? (
|
|
<span className="sysml-node-prop-empty">no properties</span>
|
|
) : (
|
|
d.properties.slice(0, 6).map((p, i) => (
|
|
<span key={i} className="sysml-node-prop">· {p}</span>
|
|
))
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="sysml-node-constraint-body">
|
|
{d.expression ?? "{ }"}
|
|
</div>
|
|
)}
|
|
|
|
<Handle type="source" position={Position.Right} />
|
|
<Handle type="source" position={Position.Bottom} />
|
|
</div>
|
|
);
|
|
}
|