Files
Socrates/apps/web/components/text-canvas/ChipView.tsx
dtoro 2052a280b1 MVP M4: SysML metamodel + validator + dependency graph + UI surfacing
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).
2026-04-28 23:22:24 +02:00

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>
);
}