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:
@@ -0,0 +1,72 @@
|
||||
import type { languages } from 'monaco-editor'
|
||||
|
||||
/**
|
||||
* Monarch tokenizer for ResolutionFlow tree markdown syntax.
|
||||
* Provides syntax highlighting for frontmatter, headings, options, references, and variables.
|
||||
*/
|
||||
export const resolutionFlowLanguage: languages.IMonarchLanguage = {
|
||||
tokenizer: {
|
||||
root: [
|
||||
// Frontmatter delimiter
|
||||
[/^---\s*$/, 'delimiter.frontmatter', '@frontmatter'],
|
||||
|
||||
// Headings
|
||||
[/^##\s+.*$/, 'heading.action'],
|
||||
[/^#\s+.*$/, 'heading.decision'],
|
||||
|
||||
// Blockquote (help text)
|
||||
[/^>\s.*$/, 'string.blockquote'],
|
||||
|
||||
// Option lines: - [X] Label → @target_id
|
||||
[/^-\s*\[[A-Za-z0-9]+\]/, 'keyword.option', '@optionLine'],
|
||||
|
||||
// Next node reference: → @node_id
|
||||
[/^→\s*@\S+/, 'variable.reference'],
|
||||
|
||||
// Expected outcome
|
||||
[/^\*\*Expected:\*\*\s*.*$/, 'keyword.expected'],
|
||||
|
||||
// Command block
|
||||
[/^```commands\s*$/, 'delimiter.commands', '@commandBlock'],
|
||||
[/^```\s*$/, 'delimiter.commands'],
|
||||
|
||||
// Variable tokens
|
||||
[/\[USER_INPUT:[^\]]+\]/, 'variable.input'],
|
||||
[/\[VAR:[^\]]+\]/, 'variable.reference'],
|
||||
[/\[SAVE_AS:[^\]]+\]/, 'variable.save'],
|
||||
|
||||
// Ordered list items
|
||||
[/^\d+\.\s+/, 'keyword.step'],
|
||||
|
||||
// Bold
|
||||
[/\*\*[^*]+\*\*/, 'strong'],
|
||||
|
||||
// Inline code
|
||||
[/`[^`]+`/, 'string.code'],
|
||||
|
||||
// Regular text
|
||||
[/./, 'text'],
|
||||
],
|
||||
|
||||
frontmatter: [
|
||||
[/^---\s*$/, 'delimiter.frontmatter', '@pop'],
|
||||
[/^(id)(:)(.*)$/, ['keyword.frontmatter', 'delimiter', 'string.id']],
|
||||
[/^(type)(:)(.*)$/, ['keyword.frontmatter', 'delimiter', 'type.value']],
|
||||
[/^(parent)(:)(.*)$/, ['keyword.frontmatter', 'delimiter', 'string.parent']],
|
||||
[/./, 'variable.other'],
|
||||
],
|
||||
|
||||
optionLine: [
|
||||
[/→\s*@\S+/, 'variable.reference'],
|
||||
[/$/, '', '@pop'],
|
||||
[/./, 'string.option'],
|
||||
],
|
||||
|
||||
commandBlock: [
|
||||
[/^```\s*$/, 'delimiter.commands', '@pop'],
|
||||
[/./, 'string.command'],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const LANGUAGE_ID = 'resolutionflow'
|
||||
Reference in New Issue
Block a user