feat: Add tree editor validation UI (Workstream A complete)
Implements comprehensive validation feedback system for tree editor: Task A.1 - Circular Reference Detection: - Added detectCircularRefs() function in treeEditorStore - Detects loops in both decision options and action next_node_id chains - Prevents infinite navigation paths Task A.2 - ValidationSummary Component: - Created collapsible panel showing error/warning count - Click error to select problematic node - Color-coded: red for errors, yellow for warnings - Icon indicators (AlertCircle, AlertTriangle) Task A.3 - TreeEditorPage Integration: - Added ValidationSummary component display - Save button disabled when errors exist - Warnings are informational only (don't block save) - Added manual "Validate" button in toolbar - Imported CheckCircle2 icon for validate button Task A.4 - Visual Node Error Indicators: - Added error/warning badges on problem nodes - Tooltip on hover showing specific error messages - Red ring for errors, yellow ring for warnings - Shows count of errors/warnings per node All tasks from implementation plan completed. Build tested successfully. Related: Issue #1 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useParams, useNavigate, useBlocker } from 'react-router-dom'
|
||||
import { useStore } from 'zustand'
|
||||
import { Undo2, Redo2, Save } from 'lucide-react'
|
||||
import { Undo2, Redo2, Save, CheckCircle2 } from 'lucide-react'
|
||||
import { treesApi } from '@/api'
|
||||
import type { TreeCreate, TreeUpdate } from '@/types'
|
||||
import { useTreeEditorStore, useTreeEditorTemporal } from '@/store/treeEditorStore'
|
||||
import { TreeEditorLayout } from '@/components/tree-editor/TreeEditorLayout'
|
||||
import { ValidationSummary } from '@/components/tree-editor/ValidationSummary'
|
||||
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -29,7 +30,8 @@ export function TreeEditorPage() {
|
||||
getTreeForSave,
|
||||
markSaved,
|
||||
setLoading,
|
||||
setSaving
|
||||
setSaving,
|
||||
selectNode
|
||||
} = useTreeEditorStore()
|
||||
|
||||
// Access undo/redo from temporal store
|
||||
@@ -38,6 +40,9 @@ export function TreeEditorPage() {
|
||||
const [showDraftPrompt, setShowDraftPrompt] = useState(false)
|
||||
const [saveError, setSaveError] = useState<string | null>(null)
|
||||
|
||||
// Calculate if there are blocking errors
|
||||
const hasBlockingErrors = validationErrors.some(e => e.severity === 'error')
|
||||
|
||||
// Block navigation if there are unsaved changes
|
||||
const blocker = useBlocker(
|
||||
({ currentLocation, nextLocation }) =>
|
||||
@@ -122,6 +127,14 @@ export function TreeEditorPage() {
|
||||
setShowDraftPrompt(false)
|
||||
}
|
||||
|
||||
const handleManualValidate = () => {
|
||||
validate()
|
||||
}
|
||||
|
||||
const handleSelectNode = (nodeId: string) => {
|
||||
selectNode(nodeId)
|
||||
}
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
setSaveError(null)
|
||||
|
||||
@@ -307,13 +320,28 @@ export function TreeEditorPage() {
|
||||
|
||||
<div className="mx-2 h-6 w-px bg-border" />
|
||||
|
||||
{/* Validate */}
|
||||
<button
|
||||
onClick={handleManualValidate}
|
||||
disabled={isSaving}
|
||||
title="Validate tree structure (checks for errors and warnings)"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm font-medium',
|
||||
'hover:bg-accent disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
Validate
|
||||
</button>
|
||||
|
||||
{/* Save */}
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || !isDirty}
|
||||
disabled={isSaving || !isDirty || hasBlockingErrors}
|
||||
title={hasBlockingErrors ? 'Fix validation errors before saving' : undefined}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
|
||||
'hover:bg-primary/90 disabled:opacity-50'
|
||||
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<Save className="h-4 w-4" />
|
||||
@@ -329,11 +357,13 @@ export function TreeEditorPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Validation Errors Summary */}
|
||||
{validationErrors.filter(e => e.severity === 'error').length > 0 && (
|
||||
<div className="bg-destructive/10 px-4 py-2 text-sm text-destructive">
|
||||
{validationErrors.filter(e => e.severity === 'error').length} validation error(s) found.
|
||||
Please fix them before saving.
|
||||
{/* Validation Summary */}
|
||||
{validationErrors.length > 0 && (
|
||||
<div className="px-4 py-3">
|
||||
<ValidationSummary
|
||||
errors={validationErrors}
|
||||
onSelectNode={handleSelectNode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user