Files
Socrates/apps/web/lib/fixtures/aristotle.ts
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

203 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Sample project content — a realistic PM idea: an AI study companion for university students.
// Ported from docs/design-source/socrata/project/data.js.
// Used to populate the textual narrative + diagram during M1's static visual port.
export type ChipKind = "block" | "property" | "association" | "requirement";
export interface ChipToken {
t: "chip";
kind: ChipKind;
id: string;
label: string;
}
export interface TextToken {
t: "text";
v: string;
}
export type NarrativeInline = ChipToken | TextToken;
export interface NarrativeNode {
type: "h1" | "h2" | "p";
text?: string;
children?: NarrativeInline[];
}
export type BlockKind = "block" | "actor" | "constraint";
export interface FixtureBlock {
id: string;
label: string;
kind: BlockKind;
/** Position normalized to a 720×460 board (matches diagram.jsx). */
x: number;
y: number;
w: number;
h: number;
properties: string[];
}
export type AssociationKind = "association" | "composition" | "constraint";
export interface FixtureAssociation {
id: string;
from: string;
to: string;
label: string;
kind: AssociationKind;
}
export interface FixtureAssumption {
id: string;
text: string;
status: "open" | "validated" | "invalidated";
linked: string[];
}
export interface FixtureRisk {
id: string;
text: string;
severity: "low" | "med" | "high";
linked: string[];
}
export interface FixtureSocratesTurn {
who: "socrates" | "user";
text: string;
options?: Array<{ n: number; label: string; sub: string }>;
}
export interface FixtureProposal {
title: string;
impactedBlocks: string[];
addedConstraints: number;
affectedRequirements: string[];
iteration: number;
}
export interface FixtureData {
project: {
name: string;
tagline: string;
scope: string;
owner: string;
branch: string;
lastSync: string;
};
narrative: NarrativeNode[];
blocks: FixtureBlock[];
associations: FixtureAssociation[];
assumptions: FixtureAssumption[];
risks: FixtureRisk[];
socratesThread: FixtureSocratesTurn[];
proposal: FixtureProposal;
}
export const aristotleFixture: FixtureData = {
project: {
name: "Aristotle",
tagline: "AI study companion for first-year STEM students",
scope: "Higher education · undergraduate",
owner: "M. Chen · PM",
branch: "main",
lastSync: "2 min ago",
},
narrative: [
{ type: "h1", text: "Problem framing" },
{
type: "p",
children: [
{ t: "text", v: "First-year STEM students at large public universities frequently disengage from coursework not because the material is intractable, but because the " },
{ t: "chip", kind: "block", id: "student", label: "Student" },
{ t: "text", v: " lacks a low-stakes thinking partner during the long tail between lectures and office hours. The " },
{ t: "chip", kind: "block", id: "course", label: "Course" },
{ t: "text", v: " produces problem sets that assume mastery of prerequisite scaffolding, and a " },
{ t: "chip", kind: "property", id: "selfEfficacy", label: "self_efficacy" },
{ t: "text", v: " gap forms quickly." },
],
},
{
type: "p",
children: [
{ t: "text", v: "Aristotle is a study companion that " },
{ t: "chip", kind: "association", id: "guides", label: "guides" },
{ t: "text", v: " the student through Socratic prompts rather than answers, scoped to their current " },
{ t: "chip", kind: "block", id: "assignment", label: "Assignment" },
{ t: "text", v: ". It refuses to produce solutions; it only produces questions calibrated to a student's evolving understanding." },
],
},
{ type: "h2", text: "Constraints" },
{
type: "p",
children: [
{ t: "chip", kind: "requirement", id: "REQ-001", label: "REQ-001" },
{ t: "text", v: " The companion must never output a complete solution to a graded problem. " },
{ t: "chip", kind: "requirement", id: "REQ-002", label: "REQ-002" },
{ t: "text", v: " Response latency under 1.2s P50 to preserve flow. " },
{ t: "chip", kind: "requirement", id: "REQ-003", label: "REQ-003" },
{ t: "text", v: " Operates within FERPA boundaries; coursework never leaves institutional tenancy." },
],
},
{ type: "h2", text: "Why now" },
{
type: "p",
children: [
{ t: "text", v: "Two large public-university pilots indicated that students would adopt a tool that explicitly does not solve their homework — an inversion of the prevailing market." },
],
},
],
blocks: [
{ id: "student", label: "Student", kind: "block", x: 0.10, y: 0.18, w: 168, h: 96,
properties: ["self_efficacy", "course_load", "prior_grade"] },
{ id: "aristotle", label: "Aristotle", kind: "block", x: 0.42, y: 0.18, w: 184, h: 110,
properties: ["interaction_style", "scope_window", "refusal_policy"] },
{ id: "course", label: "Course", kind: "block", x: 0.74, y: 0.10, w: 168, h: 96,
properties: ["syllabus", "prerequisites"] },
{ id: "assignment", label: "Assignment", kind: "block", x: 0.74, y: 0.58, w: 168, h: 96,
properties: ["due_at", "rubric", "graded"] },
{ id: "instructor", label: "Instructor", kind: "actor", x: 0.10, y: 0.62, w: 144, h: 76,
properties: ["policy_set"] },
{ id: "ferpa", label: "FERPA boundary", kind: "constraint", x: 0.42, y: 0.66, w: 184, h: 72,
properties: [] },
],
associations: [
{ id: "a1", from: "student", to: "aristotle", label: "consults", kind: "association" },
{ id: "a2", from: "aristotle", to: "assignment", label: "scoped_to", kind: "association" },
{ id: "a3", from: "course", to: "assignment", label: "contains", kind: "composition" },
{ id: "a4", from: "instructor", to: "aristotle", label: "configures", kind: "association" },
{ id: "a5", from: "aristotle", to: "ferpa", label: "obeys", kind: "constraint" },
{ id: "a6", from: "student", to: "course", label: "enrolled_in", kind: "association" },
],
assumptions: [
{ id: "ASM-1", text: "Students will accept a tool that refuses to give answers", status: "open", linked: ["aristotle"] },
{ id: "ASM-2", text: "Faculty will not classify Socratic prompts as academic dishonesty", status: "validated", linked: ["aristotle", "instructor"] },
{ id: "ASM-3", text: "1.2s P50 latency is achievable with on-prem inference", status: "open", linked: ["aristotle"] },
],
risks: [
{ id: "RSK-1", text: "Refusal policy circumvented via prompt injection", severity: "high", linked: ["aristotle"] },
{ id: "RSK-2", text: "FERPA tenancy adds 46 weeks of procurement per institution", severity: "med", linked: ["ferpa"] },
],
socratesThread: [
{ who: "socrates", text: "You've defined Aristotle as scoped to a single Assignment. What happens when a student wants to compare techniques across two assignments — does the scope expand, or does the student switch contexts?" },
{ who: "user", text: "Switch contexts. We don't want cross-assignment leakage of solution patterns." },
{ who: "socrates", text: "Good. That decision has an implication: the property scope_window on Aristotle becomes single-valued, and we may want a Constraint that forbids cross-Assignment retrieval. Should I draft that as a proposal?" },
{
who: "socrates",
text: "Given the context-switch decision, three things follow. Pick where to go next:",
options: [
{ n: 1, label: "Draft the constraint", sub: "no cross-Assignment retrieval" },
{ n: 2, label: "Revisit scope_window", sub: "single-valued vs. multi-valued" },
{ n: 3, label: "Park it", sub: "I'll come back later" },
],
},
],
proposal: {
title: "Add constraint: no cross-Assignment retrieval",
impactedBlocks: ["aristotle", "assignment"],
addedConstraints: 1,
affectedRequirements: ["REQ-001"],
iteration: 2,
},
};