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).
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
// React NodeView for the chip TipTap node.
|
|
// Renders identically to the static Chip via the same CSS classes.
|
|
|
|
"use client";
|
|
|
|
import { NodeViewWrapper } from "@tiptap/react";
|
|
import type { NodeViewProps } from "@tiptap/react";
|
|
import type { ChipKind } from "../../lib/fixtures/aristotle";
|
|
import type { MarkupStyle } from "./Chip";
|
|
import { useChipFocus } from "./FocusContext";
|
|
|
|
const KIND_LABEL: Record<ChipKind, string> = {
|
|
block: "block",
|
|
property: "property",
|
|
association: "assoc",
|
|
requirement: "req",
|
|
};
|
|
|
|
function kindGlyph(kind: ChipKind): string {
|
|
switch (kind) {
|
|
case "block": return "▢";
|
|
case "property": return "·";
|
|
case "association": return "→";
|
|
case "requirement": return "§";
|
|
}
|
|
}
|
|
|
|
export function ChipView({ node, selected, editor }: NodeViewProps) {
|
|
const kind = (node.attrs.kind as ChipKind) ?? "block";
|
|
const label = (node.attrs.label as string) ?? "untitled";
|
|
const refId = (node.attrs.refId as string | null) ?? null;
|
|
// Read the current markup style from the editor's storage; defaults to "color".
|
|
const markupStyle =
|
|
((editor.storage as unknown as Record<string, unknown>).markupStyle as MarkupStyle | undefined) ?? "color";
|
|
|
|
const { focusBlockId, setFocusBlockId } = useChipFocus();
|
|
const isFocused = (refId !== null && refId === focusBlockId) || selected;
|
|
|
|
const cls = `chip chip-${kind} chip-style-${markupStyle}${isFocused ? " chip-focus" : ""}`;
|
|
const handlers = refId
|
|
? {
|
|
onMouseEnter: () => setFocusBlockId(refId),
|
|
onMouseLeave: () => setFocusBlockId(null),
|
|
onClick: () => setFocusBlockId(refId),
|
|
}
|
|
: {};
|
|
|
|
if (markupStyle === "bracket") {
|
|
return (
|
|
<NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers}>
|
|
<span className="chip-bracket">[</span>
|
|
<span className="chip-kind">{KIND_LABEL[kind]}:</span>
|
|
<span className="chip-label">{label}</span>
|
|
<span className="chip-bracket">]</span>
|
|
</NodeViewWrapper>
|
|
);
|
|
}
|
|
|
|
if (markupStyle === "underline") {
|
|
return (
|
|
<NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers}>
|
|
<span className="chip-glyph">{kindGlyph(kind)}</span>
|
|
<span className="chip-label">{label}</span>
|
|
</NodeViewWrapper>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<NodeViewWrapper as="span" className={cls} contentEditable={false} draggable={false} {...handlers}>
|
|
<span className="chip-glyph">{kindGlyph(kind)}</span>
|
|
<span className="chip-label">{label}</span>
|
|
</NodeViewWrapper>
|
|
);
|
|
}
|