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.
203 lines
7.8 KiB
TypeScript
203 lines
7.8 KiB
TypeScript
// 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 4–6 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,
|
||
},
|
||
};
|