// In-dock card showing a Socrates-proposed change. // // Displays: reasoning, op summary, impact summary (added/removed/changed + // validation diff), and Accept / Reject controls. Accept calls into the // ModelStore which routes through the same applyOps + persistence pipeline // as user-originated edits. "use client"; import { useState } from "react"; import type { ImpactSummary } from "../../lib/sysml/impact"; import type { ModelOp } from "../../lib/sync/ops"; export interface ProposalPayload { reasoning: string; ops: ModelOp[]; impactSummary: ImpactSummary; meta?: { provider?: string; model?: string }; } interface ProposalCardProps { proposal: ProposalPayload; onAccept: () => Promise | void; onReject: () => void; pending?: boolean; } export function ProposalCard({ proposal, onAccept, onReject, pending }: ProposalCardProps) { const [busy, setBusy] = useState(false); const { reasoning, ops, impactSummary } = proposal; async function accept() { if (busy) return; setBusy(true); try { await onAccept(); } finally { setBusy(false); } } return (
PROPOSAL {summarizeStats(impactSummary)}
{reasoning}
{ops.length > 0 && (
Ops · {ops.length}
    {ops.map((op, i) => (
  • {summarizeOp(op)}
  • ))}
)} {(impactSummary.added.length > 0 || impactSummary.removed.length > 0 || impactSummary.changed.length > 0) && (
Impact
{impactSummary.added.length > 0 && (
+ added {impactSummary.added.map(e => `${e.label} (${e.kind})`).join(", ")}
)} {impactSummary.removed.length > 0 && (
− removed {impactSummary.removed.map(e => `${e.label} (${e.kind})`).join(", ")}
)} {impactSummary.changed.length > 0 && (
~ changed {impactSummary.changed.map(e => `${e.label} (${e.kind})`).join(", ")}
)}
)} {(impactSummary.issuesCreated.length > 0 || impactSummary.issuesResolved.length > 0) && (
Validation
{impactSummary.issuesResolved.length > 0 && (
    {impactSummary.issuesResolved.slice(0, 4).map((i, idx) => (
  • ✓ resolves {i.code}: {i.message}
  • ))}
)} {impactSummary.issuesCreated.length > 0 && (
    {impactSummary.issuesCreated.slice(0, 4).map((i, idx) => (
  • ⚠ creates {i.code}: {i.message}
  • ))}
)}
)} {!impactSummary.ok && (
Cannot apply
    {impactSummary.errors.map((e, i) => (
  • {e.code}: {e.message}
  • ))}
)}
{proposal.meta?.model && (
via {proposal.meta.provider} · {proposal.meta.model}
)}
); } function summarizeStats(impact: ImpactSummary): string { if (!impact.ok) return "would not apply"; const parts: string[] = []; const { stats } = impact; if (stats.blocksDelta) parts.push(`${signed(stats.blocksDelta)} block${Math.abs(stats.blocksDelta) === 1 ? "" : "s"}`); if (stats.associationsDelta) parts.push(`${signed(stats.associationsDelta)} assoc`); if (stats.constraintsDelta) parts.push(`${signed(stats.constraintsDelta)} constraint${Math.abs(stats.constraintsDelta) === 1 ? "" : "s"}`); if (stats.requirementsDelta) parts.push(`${signed(stats.requirementsDelta)} req${Math.abs(stats.requirementsDelta) === 1 ? "" : "s"}`); if (stats.issuesDelta) parts.push(`${signed(stats.issuesDelta)} issue${Math.abs(stats.issuesDelta) === 1 ? "" : "s"}`); if (parts.length === 0) parts.push("structural rearrangement"); return parts.join(" · "); } function signed(n: number): string { return n > 0 ? `+${n}` : `${n}`; } function summarizeOp(op: ModelOp): string { switch (op.kind) { case "add-block": return `+ block "${op.block.label}" (${op.block.kind})`; case "remove-block": return `− block ${op.blockId}`; case "update-block": return `~ block ${op.blockId}`; case "add-association": return `+ assoc ${op.association.fromBlockId}→${op.association.toBlockId} (${op.association.kind})`; case "remove-association": return `− assoc ${op.associationId}`; case "update-association": return `~ assoc ${op.associationId}`; case "add-constraint": return `+ constraint "${op.constraint.label}"`; case "remove-constraint": return `− constraint ${op.constraintId}`; case "update-constraint": return `~ constraint ${op.constraintId}`; case "add-requirement": return `+ req ${op.requirement.tag}`; case "remove-requirement": return `− req ${op.requirementId}`; case "update-requirement": return `~ req ${op.requirementId}`; case "add-property": return `+ property ${op.blockId}.${op.property.name}`; case "update-property": return `~ property ${op.blockId}.${op.propertyId}`; case "remove-property": return `− property ${op.blockId}.${op.propertyId}`; case "add-relation": return `+ relation ${op.requirementId}.${op.relation.kind}`; case "remove-relation": return `− relation ${op.requirementId}[${op.relationIndex}]`; } }