Files
Socrates/apps/web/styles/diagram.css
dtoro a0566ce64c MVP M1–M3: visual port, TipTap text editor, React Flow diagram
apps/web — Next.js 16 + TypeScript + React 19 (no Tailwind)

M1 Visual port
- Manuscript theme + base styles ported verbatim from design-source
- 100dvh layout with internal scroll regions (rail / narrative / dock thread)
- TopBar, LeftRail (collapsible sections + collapsed strip), CanvasHeader,
  StatusBar, EditorShell composing the dual-canvas
- SocratesDock (subtle/default/prominent) with bubbles + numbered options
- Sigil (Σ + laurel) used across editor and seed screens
- SeedScreen with emerging-seed rail, mini-graph, confidence bar, thread,
  typing indicator, input row
- Routes: / (homepage), /editor/[projectId], /seed
- Aristotle fixture in lib/fixtures (ported from data.js)

M2 Real text editor
- TipTap (StarterKit + custom Chip atom inline node + ReactNodeViewRenderer)
- Slash-menu insertion via @tiptap/suggestion with arrow / number-key shortcuts
- ChipFocusContext bridges narrative ↔ diagram hover/selection across the
  TipTap render boundary
- fixtureToDoc converts the fixture narrative → ProseMirror JSON

M3 Real diagram
- React Flow (@xyflow/react) custom node (block / actor / constraint / system)
  matching prototype's softened look
- Custom edge with bezier path + label renderer (association / composition /
  constraint variants)
- Drag-create from a left-side palette via HTML5 DnD + screenToFlowPosition
- NodeInspector panel: edit kind / label / properties (or expression for
  constraints); delete cascades to incident edges
- Bidirectional focus highlighting between chips (narrative) and blocks
  (diagram)
- Removed redundant legend (stereotype labels on nodes serve same purpose)

State for M3 lives in component memory; persistence + bidirectional sync land
in M4–M5.
2026-04-28 22:54:36 +02:00

337 lines
7.8 KiB
CSS

/* React Flow styling overrides for the Manuscript theme. */
.diagram-flow {
width: 100%;
height: 100%;
background: var(--diagram-bg);
}
/* Hide the default attribution badge for cleaner aesthetic */
.react-flow__attribution {
display: none;
}
.react-flow__pane {
cursor: default;
}
/* Background dots */
.react-flow__background {
background-color: var(--diagram-bg);
}
/* Connection line drawn while drag-creating an edge */
.react-flow__connection-path {
stroke: var(--accent);
stroke-width: 1.5;
stroke-dasharray: 4 3;
}
/* Selection box */
.react-flow__nodesselection-rect,
.react-flow__selection {
background: var(--accent-soft);
border: 1px dashed var(--accent);
}
/* Node base wrapper — we draw our own card; React Flow's outer just provides position. */
.react-flow__node {
font-family: var(--font-body);
cursor: grab;
}
.react-flow__node:active { cursor: grabbing; }
/* Custom node card (BlockNode component) */
.sysml-node {
position: relative;
background: var(--block-bg);
border: 1px solid var(--block-border);
border-radius: 8px;
box-shadow: 0 2px 4px var(--shadow);
font-family: var(--font-body);
user-select: none;
display: flex; flex-direction: column;
min-width: 168px;
}
.sysml-node-actor {
background: var(--block-actor-bg);
}
.sysml-node-constraint {
background: var(--block-constraint-bg);
border-style: dashed;
}
.sysml-node-system {
border-color: var(--accent);
border-width: 1.5px;
}
.sysml-node-selected,
.react-flow__node.selected .sysml-node {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-soft), 0 2px 4px var(--shadow);
}
.sysml-node-stereo {
font-family: var(--font-mono);
font-size: 9px;
color: var(--muted);
letter-spacing: 0.04em;
padding: 6px 12px 0 12px;
}
.sysml-node-title {
font-family: var(--font-display);
font-weight: 600;
font-size: 13px;
color: var(--fg);
padding: 1px 12px 8px 12px;
}
.sysml-node-divider {
height: 1px;
background: var(--block-divider);
}
.sysml-node-props {
padding: 6px 12px 8px 12px;
display: flex; flex-direction: column; gap: 3px;
}
.sysml-node-prop {
font-family: var(--font-mono);
font-size: 11.5px;
color: var(--muted-strong);
}
.sysml-node-prop-empty {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--muted);
font-style: italic;
}
.sysml-node-constraint-body {
padding: 4px 12px 8px 12px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--muted-strong);
}
/* Connection handles — kept subtle; visible on hover */
.react-flow__handle {
width: 8px;
height: 8px;
background: var(--surface);
border: 1.25px solid var(--accent);
opacity: 0;
transition: opacity 0.15s;
}
.react-flow__node:hover .react-flow__handle,
.react-flow__node.selected .react-flow__handle {
opacity: 1;
}
/* Edges */
.react-flow__edge-path {
stroke: var(--edge);
stroke-width: 1;
}
.react-flow__edge.selected .react-flow__edge-path {
stroke: var(--accent);
stroke-width: 1.5;
}
.react-flow__edge-text {
font-family: var(--font-mono);
font-size: 10.5px;
fill: var(--edge-label);
}
.react-flow__edge-textbg {
fill: var(--diagram-bg);
}
.sysml-edge-label {
background: var(--diagram-bg);
border: 0.75px solid var(--edge-soft);
border-radius: 4px;
padding: 1px 6px;
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--edge-label);
white-space: nowrap;
pointer-events: all;
}
/* Controls (zoom in/out/fit) — positioned via React Flow's `position` prop */
.react-flow__controls {
box-shadow: 0 2px 8px var(--shadow);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
}
.react-flow__controls-button {
background: transparent;
border: none;
border-bottom: 1px solid var(--border);
color: var(--muted-strong);
width: 26px;
height: 26px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.react-flow__controls-button:last-child { border-bottom: none; }
.react-flow__controls-button:hover {
background: var(--surface-2);
color: var(--fg);
}
.react-flow__controls-button svg {
fill: currentColor;
width: 11px;
height: 11px;
max-width: 11px;
max-height: 11px;
}
/* Palette — drag source for new blocks */
.diagram-palette {
position: absolute;
top: 12px;
left: 12px;
display: flex; flex-direction: column; gap: 6px;
z-index: 5;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px;
box-shadow: 0 2px 8px var(--shadow);
}
.diagram-palette-label {
font-family: var(--font-mono);
font-size: 9.5px;
color: var(--muted);
letter-spacing: 0.10em;
text-transform: uppercase;
padding: 2px 4px 4px 4px;
}
.diagram-palette-item {
display: flex; align-items: center; gap: 8px;
padding: 5px 8px;
border-radius: 4px;
background: var(--bg);
border: 1px solid var(--border);
cursor: grab;
font-family: var(--font-mono);
font-size: 11px;
color: var(--fg);
}
.diagram-palette-item:hover {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent-strong);
}
.diagram-palette-item:active { cursor: grabbing; }
.diagram-palette-glyph {
width: 14px; height: 14px;
display: inline-flex; align-items: center; justify-content: center;
font-size: 11px;
color: var(--muted);
}
/* Inspector panel for selected node */
.diagram-inspector {
position: absolute;
top: 12px;
right: 12px;
width: 240px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px 12px;
box-shadow: 0 2px 8px var(--shadow);
z-index: 5;
display: flex; flex-direction: column; gap: 8px;
font-family: var(--font-body);
}
.diagram-inspector-row {
display: flex; flex-direction: column; gap: 3px;
}
.diagram-inspector-label {
font-family: var(--font-mono);
font-size: 9.5px;
color: var(--muted);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.diagram-inspector-input {
font-family: inherit;
font-size: 13px;
padding: 5px 7px;
background: var(--bg);
border: 1px solid var(--border-strong);
border-radius: 4px;
color: var(--fg);
outline: none;
}
.diagram-inspector-input:focus { border-color: var(--accent); }
.diagram-inspector-select {
font-family: var(--font-mono);
font-size: 11.5px;
padding: 4px 6px;
background: var(--bg);
border: 1px solid var(--border-strong);
border-radius: 4px;
color: var(--fg);
}
.diagram-inspector-prop {
display: flex; gap: 4px; align-items: center;
}
.diagram-inspector-prop input {
flex: 1;
font-family: var(--font-mono);
font-size: 11.5px;
padding: 3px 6px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--fg);
outline: none;
}
.diagram-inspector-prop input:focus { border-color: var(--accent); }
.diagram-inspector-prop-x {
width: 18px; height: 18px;
display: flex; align-items: center; justify-content: center;
background: transparent;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 13px;
}
.diagram-inspector-prop-x:hover { color: var(--warn-strong); }
.diagram-inspector-add {
margin-top: 2px;
padding: 4px 8px;
background: transparent;
border: 1px dashed var(--border-strong);
border-radius: 4px;
color: var(--muted-strong);
font-family: var(--font-mono);
font-size: 10.5px;
cursor: pointer;
}
.diagram-inspector-add:hover {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent-strong);
}
.diagram-inspector-actions {
display: flex; gap: 6px; margin-top: 6px;
}
.diagram-inspector-btn {
flex: 1;
padding: 5px 8px;
font-family: var(--font-mono);
font-size: 11px;
background: var(--surface);
border: 1px solid var(--border-strong);
border-radius: 4px;
cursor: pointer;
color: var(--fg);
}
.diagram-inspector-btn:hover { background: var(--surface-2); }
.diagram-inspector-btn-danger { color: var(--warn-strong); }
.diagram-inspector-btn-danger:hover { background: var(--warn-soft); }