feat: add dual-mode tree editor with Code Mode, variables, and markdown sync
Implements the full dual-mode tree editor (Plan Phases 1-5): Backend: - JSONB↔Markdown bidirectional serializer/parser with mistune - Markdown validator with line/column error reporting - 3 API endpoints: export-markdown, import-markdown, validate-markdown - Variable extraction/resolution service ([USER_INPUT], [VAR], [SAVE_AS]) - Session variables JSONB column (migration 028) - 39 tree markdown tests + variable service tests (403 total passing) Frontend: - Monaco-based Code Mode with custom Monarch tokenizer and dark theme - Autocomplete for @node_id refs, type values, variable names - Debounced validation (800ms) with inline Monaco error markers - Syntax help panel (absolute overlay, toggleable) - Starter template for new trees with valid cross-references - Bidirectional metadata sync (name/description/category/tags frontmatter) - Synchronous tree→markdown serializer (fixes async race condition) - Pre-save validation blocks save on broken refs or missing tree name - Mode-aware undo/redo: Monaco native in Code Mode, throttled zundo in Flow Mode - Variable prompt modal and frontend resolver for session navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,22 @@
|
||||
import { lazy, Suspense } from 'react'
|
||||
import { TreeMetadataForm } from './TreeMetadataForm'
|
||||
import { NodeList } from './NodeList'
|
||||
import { TreePreviewPanel } from '@/components/tree-preview/TreePreviewPanel'
|
||||
import { useTreeEditorStore } from '@/store/treeEditorStore'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// Lazy load CodeModeEditor (Monaco is ~2MB)
|
||||
const CodeModeEditor = lazy(() =>
|
||||
import('./code-mode/CodeModeEditor').then(m => ({ default: m.CodeModeEditor }))
|
||||
)
|
||||
|
||||
interface TreeEditorLayoutProps {
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
||||
export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
|
||||
const editorMode = useTreeEditorStore(s => s.editorMode)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -15,28 +24,52 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
|
||||
isMobile ? 'flex-col' : 'flex-row'
|
||||
)}
|
||||
>
|
||||
{/* Left Panel - Form Editor */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col overflow-y-auto border-white/[0.06]',
|
||||
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
||||
)}
|
||||
>
|
||||
<div className="space-y-4 p-4">
|
||||
<TreeMetadataForm />
|
||||
<NodeList />
|
||||
</div>
|
||||
</div>
|
||||
{editorMode === 'code' ? (
|
||||
<>
|
||||
{/* Code Mode: Monaco editor (60%) + Preview (40%) */}
|
||||
<div className={cn(
|
||||
'flex flex-col overflow-hidden border-white/[0.06]',
|
||||
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
||||
)}>
|
||||
<Suspense fallback={
|
||||
<div className="flex h-full items-center justify-center bg-[#0a0a0a]">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-white/20 border-t-white" />
|
||||
</div>
|
||||
}>
|
||||
<CodeModeEditor />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Preview */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex-1 overflow-hidden bg-white/[0.02]',
|
||||
isMobile ? 'hidden' : 'block'
|
||||
)}
|
||||
>
|
||||
<TreePreviewPanel />
|
||||
</div>
|
||||
{/* Right Panel - Preview */}
|
||||
<div className={cn(
|
||||
'flex-1 overflow-hidden bg-white/[0.02]',
|
||||
isMobile ? 'hidden' : 'block'
|
||||
)}>
|
||||
<TreePreviewPanel />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Flow Mode: Form editor (60%) + Preview (40%) */}
|
||||
<div className={cn(
|
||||
'flex flex-col overflow-y-auto border-white/[0.06]',
|
||||
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
||||
)}>
|
||||
<div className="space-y-4 p-4">
|
||||
<TreeMetadataForm />
|
||||
<NodeList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Preview */}
|
||||
<div className={cn(
|
||||
'flex-1 overflow-hidden bg-white/[0.02]',
|
||||
isMobile ? 'hidden' : 'block'
|
||||
)}>
|
||||
<TreePreviewPanel />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user