Assurance Case Editor
The assurance case editor is the core interactive component of the TEA Platform. It allows users to visually create and edit assurance cases as node-based diagrams. This page documents how the editor is implemented.
Technology
The editor is built with ReactFlow , a highly customisable library for building node-based editors and diagrams in React.
ReactFlow Documentation
For general ReactFlow concepts and API reference, see the official ReactFlow documentation .
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ FlowEditor Component │
│ (Client Component) │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ ReactFlow │ │ Toolbar │ │ Node Inspector │ │
│ │ Canvas │ │ │ │ Panel │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Custom Node Types │
│ ┌────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Goal │ │ Strategy │ │ Evidence │ │ Context │ │
│ │ Node │ │ Node │ │ Node │ │ Node │ │
│ └────────┘ └──────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ State Management │
│ (React state + Server Actions) │
└─────────────────────────────────────────────────────────┘Key Files
| File | Purpose |
|---|---|
components/cases/flow-editor/flow-editor.tsx | Main editor component |
components/cases/flow-editor/nodes/ | Custom node components |
components/cases/flow-editor/edges/ | Custom edge components |
components/cases/flow-editor/toolbar/ | Editor toolbar |
components/cases/flow-editor/panels/ | Side panels (inspector, etc.) |
actions/elements.ts | Server Actions for element CRUD |
Custom Node Types
Each element type has a corresponding custom node component. These are registered with ReactFlow via the nodeTypes prop.
Node Type Registration
const nodeTypes = {
goal: GoalNode,
strategy: StrategyNode,
propertyClaim: PropertyClaimNode,
evidence: EvidenceNode,
context: ContextNode,
assumption: AssumptionNode,
justification: JustificationNode,
module: ModuleNode,
}
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
// ...
/>Node Component Structure
Each custom node follows a similar pattern:
interface NodeData {
id: string
elementType: ElementType
name: string
description: string
// ... other element fields
}
function GoalNode({ data, selected }: NodeProps<NodeData>) {
return (
<div className={cn(
'goal-node',
selected && 'selected'
)}>
<Handle type="target" position={Position.Top} />
<div className="node-header">
<span className="node-label">{data.name}</span>
</div>
<div className="node-content">
{data.description}
</div>
<Handle type="source" position={Position.Bottom} />
</div>
)
}Node Styling
Nodes are styled to follow GSN (Goal Structuring Notation) conventions:
| Element Type | Shape | Colour |
|---|---|---|
| Goal | Rectangle | Blue |
| Strategy | Parallelogram | Green |
| Property Claim | Rectangle | Light Blue |
| Evidence | Circle/Rounded | Orange |
| Context | Rounded Rectangle | Grey |
| Assumption | Ellipse | Yellow |
| Justification | Ellipse | Purple |
Data Flow
Loading the Editor
- Server Component fetches case data with elements
- Elements are transformed to ReactFlow nodes/edges
- FlowEditor receives initial state as props
// app/(main)/cases/[id]/page.tsx
async function CasePage({ params }) {
const caseData = await getCaseWithElements(params.id)
const { nodes, edges } = transformToFlow(caseData.elements)
return <FlowEditor initialNodes={nodes} initialEdges={edges} />
}Element-to-Node Transformation
Elements from the database are transformed to ReactFlow nodes:
function elementToNode(element: AssuranceElement): Node {
return {
id: element.id,
type: elementTypeToNodeType(element.elementType),
position: element.position ?? { x: 0, y: 0 },
data: {
id: element.id,
elementType: element.elementType,
name: element.name,
description: element.description,
// ... other fields
}
}
}Edge Generation
Edges are generated from the parent-child relationships:
function generateEdges(elements: AssuranceElement[]): Edge[] {
return elements
.filter(el => el.parentId)
.map(el => ({
id: `${el.parentId}-${el.id}`,
source: el.parentId,
target: el.id,
type: 'smoothstep',
}))
}Interactivity
Adding Elements
When a user adds a new element:
- User selects element type from toolbar
- User clicks on canvas or existing node
- Client creates optimistic node
- Server Action creates element in database
- On success, node ID is updated; on failure, node is removed
async function handleAddElement(type: ElementType, parentId?: string) {
// Optimistic update
const tempId = `temp-${Date.now()}`
addNode({ id: tempId, type, data: { ... } })
// Server Action
const result = await createElement({ type, parentId, caseId })
if (result.success) {
updateNodeId(tempId, result.data.id)
} else {
removeNode(tempId)
toast.error(result.error)
}
}Editing Elements
Element editing is handled through the inspector panel:
- User selects a node
- Inspector panel shows element details
- User edits fields
- Changes are debounced and saved via Server Action
Deleting Elements
Deletion cascades to child elements:
- User selects node and presses Delete or uses context menu
- Confirmation dialog for elements with children
- Server Action deletes element and children
- Nodes are removed from canvas
State Management
The editor uses a combination of:
- Local React state for UI state (selection, viewport)
- ReactFlow state for nodes and edges
- Server Actions for persistence
Concurrency Control
To prevent conflicts in collaborative editing:
- Cases have a
lockUuidfield - When editing, user acquires a lock
- Lock is released on save or timeout
- Other users see “Case is being edited” message
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Delete / Backspace | Delete selected elements |
Ctrl/Cmd + C | Copy selected elements |
Ctrl/Cmd + V | Paste elements |
Ctrl/Cmd + Z | Undo |
Ctrl/Cmd + Shift + Z | Redo |
Ctrl/Cmd + A | Select all |
Escape | Deselect all |
Further Reading
- Assurance Case Model - Data model for elements
- ReactFlow Documentation - Official ReactFlow docs