Files
Socrates/apps/web/components/editor/IssuesPanel.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

74 lines
2.9 KiB
TypeScript

// Floating panel listing current validation issues — shown next to the
// status bar. Click an issue to focus its anchored element across the rail
// + diagram (via setFocusBlockId). Lets the user verify the validator is
// actually firing on the broken-fixture demos.
"use client";
import { useState } from "react";
import type { ValidationIssue } from "../../lib/sysml/validate";
interface IssuesPanelProps {
issues: ValidationIssue[];
onSelectAnchor?: (id: string) => void;
}
const SEV_GLYPH: Record<ValidationIssue["severity"], string> = {
error: "●",
warning: "▲",
soft: "·",
};
export function IssuesPanel({ issues, onSelectAnchor }: IssuesPanelProps) {
const [collapsed, setCollapsed] = useState(false);
const grouped = {
error: issues.filter(i => i.severity === "error"),
warning: issues.filter(i => i.severity === "warning"),
soft: issues.filter(i => i.severity === "soft"),
};
if (issues.length === 0) {
return (
<div className={`issues-panel issues-panel-clean ${collapsed ? "issues-panel-collapsed" : ""}`}>
<button className="issues-panel-head" onClick={() => setCollapsed(c => !c)} type="button">
<span className="issues-panel-clean-glyph"></span>
<span className="issues-panel-title">Model is clean</span>
</button>
</div>
);
}
return (
<div className={`issues-panel ${collapsed ? "issues-panel-collapsed" : ""}`}>
<button className="issues-panel-head" onClick={() => setCollapsed(c => !c)} type="button">
<span className="issues-panel-counts">
{grouped.error.length > 0 && <span className="issues-count issues-count-error"> {grouped.error.length}</span>}
{grouped.warning.length > 0 && <span className="issues-count issues-count-warning"> {grouped.warning.length}</span>}
{grouped.soft.length > 0 && <span className="issues-count issues-count-soft">· {grouped.soft.length}</span>}
</span>
<span className="issues-panel-title">{issues.length} validation issue{issues.length === 1 ? "" : "s"}</span>
<span className="issues-panel-caret">{collapsed ? "▴" : "▾"}</span>
</button>
{!collapsed && (
<ul className="issues-panel-list">
{(["error", "warning", "soft"] as const).flatMap(sev =>
grouped[sev].map((i, idx) => (
<li
key={`${sev}-${idx}`}
className={`issues-item issues-item-${i.severity}`}
onClick={() => onSelectAnchor?.(i.anchor.kind === "property" ? i.anchor.blockId : (i.anchor as { id: string }).id ?? "")}
>
<span className="issues-item-sev">{SEV_GLYPH[i.severity]}</span>
<span className="issues-item-code">{i.code}</span>
<span className="issues-item-msg">{i.message}</span>
</li>
))
)}
</ul>
)}
</div>
);
}