Files
Socrates/docs/design-source/socrata/project/diagram.jsx
dtoro f1c4566576 Initial commit — design docs + Phase 0 validation harness
Sets up the Socrata project repo with:

docs/ — strategy and design documents
  - idea.md: full product vision
  - implementation-plan.md: Phase 0 + Phase 1 MVP plan
  - phase-0-validation.md: 2-week validation experiment strategy
  - phase-0-plan.md: concrete Phase 0 build plan
  - phase-0-results.md: Phase 0 gate outcome — GO for MVP
  - sysml-modeling.md: metamodel + SE discipline + validation rules
  - socrates.md: agent character, surfaces, modes, prompts, lifecycle
  - sync.md: bidirectional text↔diagram sync engineering
  - design-source/: HTML/CSS/JS handoff bundle from Claude Design

phase-0/ — validated harness (CLI, no UI, no DB)
  - LM Studio (local OpenAI-compatible) generation + detection + judge
  - PlantUML rendering for SysML model visualization
  - 10-seed corpus (8 working + 2 holdouts)
  - 5 corpus runs with iteration history in reports/
  - Final gate: 10/10 pass, mean 4.32/5, holdouts validated

Phase 1 MVP scope and milestones documented in implementation-plan.md.
2026-04-28 22:07:38 +02:00

143 lines
6.2 KiB
JavaScript

// DiagramCanvas — softened SysML diagram (rounded blocks w/ compartments,
// associations as gentle splines). Themed entirely via CSS variables read
// from the wrapping aesthetic. Pure visual, with hover + selection.
function DiagramCanvas({ data, density = "comfortable", variant = "softened", focusBlockId, onSelect }) {
const W = 720, H = 460;
const pad = density === "compact" ? 8 : 12;
const titleSize = density === "compact" ? 12 : 13;
const propSize = density === "compact" ? 10.5 : 11.5;
const stroke = variant === "formal" ? 1.4 : 1;
const blockMap = React.useMemo(
() => Object.fromEntries(data.blocks.map((b) => [b.id, b])),
[data]
);
function blockRect(b) {
return { x: b.x * W, y: b.y * H, w: b.w, h: b.h };
}
// Compute a clean orthogonal-ish path between blocks: exit center→nearest edge,
// then a soft S-curve.
function pathFor(a) {
const f = blockRect(blockMap[a.from]);
const t = blockRect(blockMap[a.to]);
const fc = { x: f.x + f.w / 2, y: f.y + f.h / 2 };
const tc = { x: t.x + t.w / 2, y: t.y + t.h / 2 };
// Exit/enter on horizontal axis if dx dominates, else vertical.
const dx = tc.x - fc.x, dy = tc.y - fc.y;
let p1, p2, mid;
if (Math.abs(dx) > Math.abs(dy)) {
p1 = { x: fc.x + Math.sign(dx) * f.w / 2, y: fc.y };
p2 = { x: tc.x - Math.sign(dx) * t.w / 2, y: tc.y };
const cx = (p1.x + p2.x) / 2;
mid = `C ${cx} ${p1.y}, ${cx} ${p2.y}, ${p2.x} ${p2.y}`;
} else {
p1 = { x: fc.x, y: fc.y + Math.sign(dy) * f.h / 2 };
p2 = { x: tc.x, y: tc.y - Math.sign(dy) * t.h / 2 };
const cy = (p1.y + p2.y) / 2;
mid = `C ${p1.x} ${cy}, ${p2.x} ${cy}, ${p2.x} ${p2.y}`;
}
return { d: `M ${p1.x} ${p1.y} ${mid}`, mid: { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 } };
}
const radius = variant === "formal" ? 3 : variant === "graph" ? 14 : 8;
return (
<div className="diagram-wrap" style={{ width: "100%", height: "100%", position: "relative", overflow: "hidden", background: "var(--diagram-bg)" }}>
<div className="diagram-grid" />
<svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" width="100%" height="100%" style={{ display: "block" }}>
<defs>
<marker id="arrow-soft" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 1 L 9 5 L 0 9 z" fill="var(--edge)" />
</marker>
<marker id="diamond-soft" viewBox="0 0 12 10" refX="11" refY="5" markerWidth="10" markerHeight="8" orient="auto-start-reverse">
<path d="M 0 5 L 6 1 L 12 5 L 6 9 z" fill="var(--surface)" stroke="var(--edge)" strokeWidth="1" />
</marker>
</defs>
{/* Edges */}
{data.associations.map((a) => {
const p = pathFor(a);
const isComposition = a.kind === "composition";
const isConstraint = a.kind === "constraint";
return (
<g key={a.id}>
<path
d={p.d}
fill="none"
stroke="var(--edge)"
strokeWidth={stroke}
strokeDasharray={isConstraint ? "4 4" : null}
markerEnd={isComposition ? "url(#diamond-soft)" : "url(#arrow-soft)"}
opacity={0.85}
/>
<g transform={`translate(${p.mid.x}, ${p.mid.y})`}>
<rect x={-a.label.length * 3.4 - 5} y={-8} width={a.label.length * 6.8 + 10} height={16}
rx={4} fill="var(--diagram-bg)" stroke="var(--edge-soft)" strokeWidth="0.75" />
<text x={0} y={3} textAnchor="middle" fontSize="10.5" fontFamily="var(--font-mono)" fill="var(--edge-label)">
{a.label}
</text>
</g>
</g>
);
})}
{/* Blocks */}
{data.blocks.map((b) => {
const r = blockRect(b);
const focus = focusBlockId === b.id;
const isConstraint = b.kind === "constraint";
const isActor = b.kind === "actor";
return (
<g key={b.id} style={{ cursor: "pointer" }} onClick={() => onSelect && onSelect(b.id)}>
{/* shadow */}
{variant !== "formal" && (
<rect x={r.x + 1.5} y={r.y + 3} width={r.w} height={r.h} rx={radius}
fill="var(--shadow)" opacity="0.5" />
)}
<rect
x={r.x}
y={r.y}
width={r.w}
height={r.h}
rx={radius}
fill={isConstraint ? "var(--block-constraint-bg)" : isActor ? "var(--block-actor-bg)" : "var(--block-bg)"}
stroke={focus ? "var(--accent)" : "var(--block-border)"}
strokeWidth={focus ? 1.75 : stroke}
strokeDasharray={isConstraint ? "5 3" : null}
/>
{/* Header bar */}
{!isConstraint && (
<line x1={r.x} y1={r.y + 32} x2={r.x + r.w} y2={r.y + 32} stroke="var(--block-divider)" strokeWidth="0.75" />
)}
{/* Stereotype */}
<text x={r.x + pad} y={r.y + 15} fontSize="9" fontFamily="var(--font-mono)" fill="var(--muted)" letterSpacing="0.04em">
«{isConstraint ? "constraint" : isActor ? "actor" : "block"}»
</text>
{/* Title */}
<text x={r.x + pad} y={r.y + (isConstraint ? 40 : 28)} fontSize={titleSize} fontFamily="var(--font-display)" fontWeight="600" fill="var(--fg)">
{b.label}
</text>
{/* Properties */}
{!isConstraint && b.properties.slice(0, 4).map((p, i) => (
<text key={i} x={r.x + pad} y={r.y + 50 + i * (propSize + 5)} fontSize={propSize} fontFamily="var(--font-mono)" fill="var(--muted-strong)">
· {p}
</text>
))}
{isConstraint && (
<text x={r.x + pad} y={r.y + 58} fontSize={propSize} fontFamily="var(--font-mono)" fill="var(--muted-strong)">
{"{ tenancy = institutional }"}
</text>
)}
</g>
);
})}
</svg>
</div>
);
}
window.DiagramCanvas = DiagramCanvas;