Commit Graph

3 Commits

Author SHA1 Message Date
4b8e3f04ee MVP M7: Socrates can propose model changes — review impact + Accept/Reject
The dock now has a "propose" button that asks Socrates to suggest one
high-value structural change. He returns a structured payload (reasoning
+ ops + impactSummary) which renders as a ProposalCard inline in the
thread. Accept routes the ops through the same useApply() pipeline as
user-originated edits (optimistic local + persisted POST + server
reconciliation).

apps/web/lib/llm/prompts/socrates/propose.md
- Promoted verbatim from phase-0/src/prompts/.

apps/web/lib/llm/proposeChange.ts
- Server-side proposeChange() — calls the LLM with character + propose
  prompts + trimmed model + active issues. JSON Schema uses oneOf per
  op kind (Phase 0 lesson: small models need this to produce real op
  shapes instead of cramming everything into the kind name).
- normalizeOps() converts the LLM-emitted op shapes into canonical
  ModelOp[] (fills in property ids/multiplicity defaults, expands
  satisfiedBy[] into RequirementRelation[], splits PropertyType union
  per kind).

apps/web/lib/sysml/impact.ts
- Pure pre-apply impact analysis. Runs the ops through the same
  applyOps() reducer locally, then diffs:
    - structural: added / removed / changed elements (per kind)
    - validation: issues created vs resolved (by canonical issue key)
    - dep-graph blast radius (closure of touched element ids on the
      post-apply graph)
  Headline stats summarized as deltas (+1 block, −1 assoc, etc.) for
  the proposal card.

apps/web/app/api/projects/[projectId]/socrates/propose
- POST: returns { reasoning, ops, impactSummary, meta }. Pure read of
  the model — does not apply anything; client must POST /apply with the
  same ops to commit.

apps/web/components/socrates/ProposalCard.tsx
- In-dock card: PROPOSAL tag + delta stats / reasoning / collapsible
  ops list / Impact section (added/removed/changed) / Validation diff
  (resolves ✓ / creates ⚠) / Accept + Reject. Disabled when impact
  analysis flagged the apply as illegal.

apps/web/components/socrates/SocratesDock.tsx
- New "propose" button between the input field and send. Renders
  ProposalCard for assistant turns of role "proposal". Accept calls
  useApply(); on success the card collapses to a "✓ Applied" system
  bubble. On apply failure the dock shows the structured error
  messages.
- New "system" bubble role for apply confirmations + dismissals.

apps/web/components/diagram-canvas/DiagramCanvas.tsx
- Bug fix: new blocks/constraints arriving via the model→RF sync (e.g.
  from accepted proposals) are now positioned to the right of the
  existing layout instead of stacking at (0, 0) offscreen.

apps/web/lib/sync/ModelStore.tsx
- Bug fix / observability: background-POST failures and version
  mismatches now log to the console with structured context instead of
  being silently swallowed. The server's authoritative state still
  replaces the optimistic local state on response, but the user can now
  see why their accept appeared to do nothing (typically: page tab
  was at version N but server had advanced to N+1).
2026-04-30 00:27:29 +02:00
5d4236a980 MVP M5.9: SQLite persistence via Prisma + optimistic-local sync
State now survives a page refresh. Every applied op writes a fresh model
snapshot + a changelog row inside one transaction; the editor route
server-component loads the latest snapshot and seeds it as the initial
ModelStore state. Optimistic-local apply on the client + background POST
to /api/projects/[id]/apply gives the UI an instant feel without giving
up the server-as-truth contract.

apps/web/prisma
- schema.prisma: Project / ModelSnapshot / ChangelogEntry. SQLite for dev
  (file:./dev.db). The same schema swaps to Postgres by changing the
  provider line + DATABASE_URL.

apps/web/lib/db
- client.ts: PrismaClient singleton with hot-reload guard.
- repo.ts: loadProject() auto-seeds Aristotle from fromFixture();
  applyOpsToProject() runs the pure applyOps() server-side inside a
  $transaction, writes the snapshot + changelog atomically, returns
  { applied, model, version, idMapping, errors }. Optimistic-concurrency
  via expectedVersion → returns the server model on mismatch so the
  client can resync without losing its tab.

apps/web/app/api/projects/[projectId]
- route.ts (GET): returns latest { model, version }
- apply/route.ts (POST): body is { ops, expectedVersion?, reason? }

apps/web/lib/sync/ModelStore.tsx
- Now takes initialModel + initialVersion + projectId. apply() updates
  local state immediately, then POSTs in the background. On response the
  authoritative server model + version replace the optimistic state
  (handles tempId resolution from the server). Network errors keep the
  optimistic state; the next successful apply reconciles.

apps/web/app/editor/[projectId]/page.tsx
- Server component now: awaits loadProject(projectId), passes initialModel
  + initialVersion + projectId to EditorShell. EditorShell falls back to
  the fixture path when those props are absent (legacy callers / tests).

package.json
- pnpm.onlyBuiltDependencies allowlists prisma + @prisma/client + @prisma/engines
- db:push / db:generate / db:reset scripts

.gitignore
- apps/web/prisma/dev.db + dev.db-journal excluded.

Pinned to prisma@6 (prisma@7 dropped url from schema in favor of the
new adapter pattern; not worth the churn for MVP).
2026-04-29 07:23:35 +02:00
384cbb4ae9 MVP M5 (in-memory): bidirectional sync via ModelOp + applyOps + ModelStore
Both canvases now share a canonical SysMLModel through React context.
Renames in the diagram inspector ripple to every chip in the narrative
referencing the same refId, and double-clicking a chip emits an update op
that re-renders the diagram block. Validation re-runs on every successful
apply. State is in-memory; M5.9 adds Postgres persistence.

apps/web/lib/sync (new)
- ops.ts: ModelOp alphabet (18 op kinds across block / property /
  association / constraint / requirement / relation, add/update/remove for
  each). tempId() helper + per-kind constructors.
- applyOps.ts: pure (model, ops) → { model, idMapping, errors, applied }
  reducer. Atomic per batch. Cascades on remove-block (drops incident
  associations + constraint applies-to + requirement satisfiers). tempId
  resolution rewrites to canonical ids on duplicate-id collision.
- ModelStore.tsx: React provider exposing { model, apply, issues,
  issuesByElement }. Validation memoized on every model change.

Editor refactor
- EditorShell wraps in ModelStoreProvider. Initial model derived from
  fixture (+ optional ?break= corruptions). Validation now lives in the
  store, not duplicated here.
- LeftRail consumes useModel(): Model section lists real blocks +
  constraints (sorted by kind), Requirements section lists real
  requirements with traced/untraced status from r.relations.

Diagram refactor (the tricky piece)
- React Flow now owns ephemeral state via useNodesState / useEdgesState.
  Positions, drag-in-progress, selection are all RF-internal.
- Model → RF: a useEffect runs on model change, applies targeted setNodes
  updates only for elements whose semantic data (label, kind, properties)
  changed. Object identity preserved for unchanged nodes — fixes the
  "re-render storm on drag" + RF measurement-cache loss.
- RF → Model: onNodesChange / onEdgesChange / onConnect / onDrop emit
  ops via useApply(). Constraint-applies edges decompose into
  updateConstraint ops. deleteKeyCode={[Backspace, Delete]}.
- onNodesChange now handles type:'remove' too (was missing — that's why
  selecting a block + Del removed only the edges, leaving the block).

Chip refactor
- ChipView resolves displayed label from useModel() via refId lookup
  (block / requirement / association / property). Double-click chip →
  inline rename input → emit update-{block,requirement,association} op.
  All other chips with the same refId update on the next render.
- Slash-menu inserted chips have refId=null and skip the rename
  affordance (until M6 wires real model element resolution).

Removed obsolete components/diagram-canvas/fixtureToFlow.ts; replaced
with modelToFlow.ts. Bumped CSS for the chip-rename input.
2026-04-29 00:42:18 +02:00