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).
This commit is contained in:
@@ -923,3 +923,123 @@ button { font-family: inherit; }
|
||||
.tiptap .ProseMirror-selectednode .chip {
|
||||
box-shadow: 0 0 0 1.5px var(--accent);
|
||||
}
|
||||
|
||||
/* ─── Issues panel (M4) ─── */
|
||||
.issues-panel {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
right: 16px;
|
||||
width: 380px;
|
||||
max-height: 50vh;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 24px var(--shadow-strong);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 50;
|
||||
font-family: var(--font-body);
|
||||
overflow: hidden;
|
||||
}
|
||||
.issues-panel-clean {
|
||||
width: auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
.issues-panel-clean .issues-panel-head { color: var(--ok-strong); }
|
||||
.issues-panel-clean-glyph {
|
||||
color: var(--ok);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.issues-panel-collapsed {
|
||||
max-height: none;
|
||||
}
|
||||
.issues-panel-head {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
color: var(--fg);
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.issues-panel-collapsed .issues-panel-head { border-bottom: none; }
|
||||
.issues-panel-head:hover { background: var(--surface-2); }
|
||||
.issues-panel-title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
flex: 1;
|
||||
}
|
||||
.issues-panel-counts { display: flex; gap: 8px; }
|
||||
.issues-count {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.issues-count-error { color: var(--warn-strong); }
|
||||
.issues-count-warning { color: var(--accent-strong); }
|
||||
.issues-count-soft { color: var(--muted); }
|
||||
.issues-panel-caret { font-size: 9px; color: var(--muted); }
|
||||
|
||||
.issues-panel-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.issues-item {
|
||||
display: grid;
|
||||
grid-template-columns: 16px auto 1fr;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.issues-item:hover { background: var(--surface-2); }
|
||||
.issues-item-error .issues-item-sev { color: var(--warn-strong); }
|
||||
.issues-item-warning .issues-item-sev { color: var(--accent); }
|
||||
.issues-item-soft .issues-item-sev { color: var(--muted); }
|
||||
.issues-item-sev { font-size: 11px; line-height: 1.6; text-align: center; }
|
||||
.issues-item-code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--muted-strong);
|
||||
background: var(--surface-2);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
align-self: start;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.issues-item-msg { color: var(--prose); }
|
||||
|
||||
/* Issue dots in the rail */
|
||||
.rail-issue-dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.rail-issue-dot-error { background: var(--warn-strong); box-shadow: 0 0 0 2px var(--warn-soft); }
|
||||
.rail-issue-dot-warning { background: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
|
||||
.rail-issue-dot-soft { background: var(--muted); }
|
||||
|
||||
/* Diagram node issue ring */
|
||||
.sysml-node-issue-error {
|
||||
border-color: var(--warn-strong) !important;
|
||||
box-shadow: 0 0 0 2px var(--warn-soft), 0 2px 4px var(--shadow);
|
||||
}
|
||||
.sysml-node-issue-warning {
|
||||
border-color: var(--accent) !important;
|
||||
box-shadow: 0 0 0 2px var(--accent-soft), 0 2px 4px var(--shadow);
|
||||
}
|
||||
.sysml-node-issue-soft {
|
||||
border-style: dotted !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user