39 KiB
39 KiB
Dev-Time Content Editing - Data Flow Diagrams
Last Updated: 2026-01-12
Related: dev-content-editing.md (architecture overview)
Overview
This document provides ASCII diagrams showing how data flows through the dev-time content editing framework across all major operations: initialization, detection, user interaction, transformation, and persistence.
Phase 1: Initialization Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ Application Bootstrap (@lilith/service-react-bootstrap) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ Check: import.meta.env.DEV? │
└──────────────────────────────┘
│ │
│ NO │ YES
▼ ▼
┌────────┐ ┌──────────────────────────────┐
│ Exit │ │ Dynamic import: │
│ (prod) │ │ @lilith/ui-dev-content │
└────────┘ └──────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ contentEditingRegistry │
│ (Singleton Plugin Registry) │
└──────────────────────────────────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌──────────────────────┐ ┌────────────────┐
│ Register Sources │ │ Register Transformers│ │ Register Sinks │
├───────────────────┤ ├──────────────────────┤ ├────────────────┤
│ LocaleContent │ │ TruthValidation │ │ LocaleFile │
│ ImageContent │ │ ImageRegeneration │ │ ImageSrc │
└───────────────────┘ └──────────────────────┘ └────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Create overlay container: │
│ <div id="dev-content-overlay-root"> │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ ReactDOM.createRoot() │
│ Render: <DevContentOverlay /> │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Mount in document.body │
│ (Portal-based, isolated rendering) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Framework READY │
│ MutationObserver starts scanning │
└──────────────────────────────────────┘
Key Points:
- Registration happens BEFORE overlay mounts
- All plugins registered synchronously
- Overlay renders in separate React root (isolation from app)
Phase 2: Content Detection Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ Application Renders │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ <EditableContent> │
│ source="locale" │
│ identifier="en/app.json:hero" │
│ transformers={['truth-validation']}│
│ > │
│ {t('hero.title')} │
│ </EditableContent> │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Renders to DOM: │
│ <div │
│ data-editable="true" │
│ data-content-source="locale" │
│ data-content-id="en/app.json:hero" │
│ data-allowed-transformers="..." │
│ > │
│ Welcome to Platform │
│ </div> │
└──────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ MutationObserver (runs every 500ms) │
│ Scans: document.body │
└────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ For each registered ContentSource: │
│ source.detect(document.body) │
└────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ LocaleContentSource │ │ ImageContentSource │
│ - Finds explicit │ │ - Finds explicit │
│ data-editable │ │ data-editable │
│ - Returns handles │ │ - Pattern matches URLs │
│ │ │ - Returns handles │
└──────────────────────┘ └────────────────────────┘
│ │
└───────────┬───────────────┘
▼
┌──────────────────────────────────────┐
│ Aggregate ContentHandle[] │
│ │
│ [ │
│ { │
│ sourceId: 'locale', │
│ identifier: 'en/app.json:hero', │
│ element: <div>, │
│ type: 'text', │
│ allowedTransformers: [...] │
│ }, │
│ { ... } │
│ ] │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Store in ContentEditingContext │
│ Available to EditableHighlight │
└──────────────────────────────────────┘
Key Points:
- Detection is automatic (no manual registration needed)
- ContentSources run in parallel
- Handles cached until next mutation
Phase 3: User Interaction Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ User hovers over editable content │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ EditableHighlight detects hover │
│ (Listens to mousemove events) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Find matching ContentHandle │
│ by comparing event.target with │
│ handle.element │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Render visual feedback: │
│ - Cyan dashed border (2px) │
│ - "Edit" button (top-right) │
└──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ User clicks "Edit" button │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Get allowed transformers: │
│ handle.allowedTransformers || │
│ registry.getAllTransformers() │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Filter by canTransform(handle): │
│ - TruthValidation ✓ (text) │
│ - ImageRegeneration ✗ (image only) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Open TransformerModal: │
│ - Pass handle, transformer │
│ - Load current content │
└──────────────────────────────────────┘
Key Points:
- Visual feedback via Portal (doesn't affect app layout)
- Transformer filtering based on content type
- Modal isolates interaction from main app
Phase 4: Content Transformation Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ TransformerModal opens │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Load current content: │
│ const source = registry.getSource() │
│ const content = await source.read() │
└──────────────────────────────────────┘
│
┌────────────┴────────────┐
│ Content Type? │
└────────────┬────────────┘
│ │
┌───┴───┐ ┌───┴────┐
│ Locale│ │ Image │
└───┬───┘ └───┬────┘
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────┐
│ POST /api/dev/ │ │ POST /api/dev/ │
│ read-locale │ │ image-metadata │
│ │ │ │
│ Response: │ │ Response: │
│ { │ │ { │
│ "hero": { │ │ url, width, height,│
│ "title": "..." │ │ prompt, family, ...│
│ } │ │ } │
│ } │ │ │
└─────────────────────┘ └──────────────────────┘
│ │
└──────┬───────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ User clicks "Run Transformer" │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ showToast('Running transformer...') │
│ (Loading state with spinner) │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ await transformer.transform( │
│ content, │
│ { handle, metadata } │
│ ) │
└──────────────────────────────────────┘
│
┌────────────┴────────────┐
│ Transformer Type? │
└────────────┬────────────┘
│ │
┌─────────┴────┐ ┌────┴──────────┐
│ Truth │ │ Image │
│ Validation │ │ Regeneration │
└─────────┬────┘ └────┬──────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ POST /api/truth/validate│ │ POST /api/images/ │
│ │ │ variations │
│ Body: { text } │ │ │
│ │ │ Body: { │
│ Response: │ │ name: "seo-page", │
│ { │ │ prompt, │
│ isValid: false, │ │ families: ['cyber'] │
│ corrections: [ │ │ } │
│ { │ │ │
│ original: "...", │ │ Response: { id: 123 } │
│ corrected: "...", │ └─────────────────────────┘
│ reason: "...", │ │
│ severity: "high" │ ▼
│ } │ ┌─────────────────────────┐
│ ] │ │ Poll for completion: │
│ } │ │ (60 attempts × 2s) │
└─────────────────────────┘ │ │
│ │ GET /api/images/ │
│ │ variations/123 │
│ │ │
│ │ Check: status = │
│ │ 'complete'? │
│ └─────────────────────────┘
│ │
└──────┬───────┘
▼
┌──────────────────────────────────────┐
│ TransformResult: │
│ { │
│ success: true, │
│ transformed: "corrected text", │
│ changes: [ │
│ { │
│ type: 'factual-correction', │
│ original: "...", │
│ replacement: "...", │
│ reason: "...", │
│ severity: 'high' │
│ } │
│ ] │
│ } │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Render results in modal: │
│ - List changes with severity colors │
│ - Show original → replacement │
│ - Display reason │
│ - Show "Apply Changes" button │
└──────────────────────────────────────┘
Key Points:
- Transformers are service-backed (call APIs)
- Image regeneration uses BullMQ polling pattern
- Results rendered with severity-based styling
Phase 5: Persistence & Hot Replacement Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ User clicks "Apply Changes" │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Get appropriate sink: │
│ const sink = registry.getSink(handle)│
└──────────────────────────────────────┘
│
┌────────────┴────────────┐
│ Content Type? │
└────────────┬────────────┘
│ │
┌─────────┴────┐ ┌────┴──────────┐
│ Locale │ │ Image │
│ (LocaleFile) │ │ (ImageSrc) │
└─────────┬────┘ └────┬──────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ LocaleFileSink.write() │ │ ImageSrcSink.write() │
├─────────────────────────┤ ├─────────────────────────┤
│ POST /api/dev/ │ │ 1. Get img element │
│ write-locale │ │ 2. Set opacity 0.5 │
│ │ │ 3. Preload new image: │
│ Body: { │ │ const img = new │
│ file: "en/app.json", │ │ Image() │
│ path: "hero.title", │ │ img.src = newUrl │
│ content: "...", │ │ await img.onload │
│ backup: true │ │ 4. Hot swap: │
│ } │ │ element.src = newUrl │
└─────────────────────────┘ │ 5. Set opacity 1.0 │
│ └─────────────────────────┘
▼ │
┌─────────────────────────┐ │
│ Backend (DevService): │ │
│ │ │
│ 1. Read current file │ │
│ 2. Create backup: │ │
│ app.json.1234.bak │ │
│ 3. Update nested value │ │
│ 4. Write back (pretty) │ │
│ │ │
│ Response: { │ │
│ success: true, │ │
│ backup: "..." │ │
│ } │ │
└─────────────────────────┘ │
│ │
▼ │
┌─────────────────────────┐ │
│ Vite detects file change│ │
│ (via chokidar watcher) │ │
└─────────────────────────┘ │
│ │
▼ │
┌─────────────────────────┐ │
│ Vite HMR: │ │
│ - Reloads locale module │ │
│ - Triggers React render │ │
│ - UI updates instantly │ │
└─────────────────────────┘ │
│ │
└──────┬───────────────┘
▼
┌──────────────────────────────────────┐
│ showToast('Changes applied!') │
│ Close modal │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ Visual feedback: │
│ - Locale: Content updates in UI │
│ - Image: New image visible instantly │
│ - Toast: Success message │
└──────────────────────────────────────┘
Key Points:
- Locale: Backend write → Vite HMR → React re-render
- Image: DOM manipulation → Instant visual update (no HMR needed)
- Backups created before all writes
Data Structures in Memory
ContentEditingRegistry (Singleton)
{
sources: Map<string, ContentSource> {
'locale' => LocaleContentSource {},
'image' => ImageContentSource {}
},
transformers: Map<string, ContentTransformer> {
'truth-validation' => TruthValidationTransformer {},
'image-regenerate' => ImageRegenerationTransformer {}
},
sinks: Map<string, ContentSink> {
'locale-file' => LocaleFileSink {},
'image-src' => ImageSrcSink {}
}
}
ContentEditingContext (React Context)
{
handles: ContentHandle[] [
{
sourceId: 'locale',
identifier: 'locales/en/app.json:hero.title',
element: <div data-editable="true">,
type: 'text',
allowedTransformers: ['truth-validation']
},
{
sourceId: 'image',
identifier: 'seo:homepage:hero:cyberpunk',
element: <img src="/api/images/...">,
type: 'image',
allowedTransformers: ['image-regenerate']
}
],
hoveredHandle: ContentHandle | null,
selectedHandle: ContentHandle | null
}
TransformerModal State
{
isOpen: boolean,
isTransforming: boolean,
currentContent: string | object,
result: TransformResult | null {
success: true,
transformed: "...",
changes: ContentChange[] [
{
type: 'factual-correction',
original: 'Paris is capital of Germany',
replacement: 'Berlin is capital of Germany',
reason: 'Factual error: Paris is capital of France, not Germany',
severity: 'critical',
autoApply: false
}
],
metadata: { ... }
}
}
Performance Metrics
Detection Phase
Initial page load:
├─ MutationObserver setup: <1ms
├─ First scan (document.body): 2-5ms
│ ├─ LocaleContentSource.detect(): 1-2ms (10 elements found)
│ └─ ImageContentSource.detect(): 1-3ms (5 images matched)
└─ Total handles created: 15
Subsequent scans (every 500ms):
├─ Cache check: <1ms (no DOM changes)
└─ Re-scan on mutation: 2-5ms
Transformation Phase
Truth Validation:
├─ Read locale file: 10-20ms (backend)
├─ POST /api/truth/validate: 200-500ms (LLM)
├─ Parse results: <1ms
└─ Render modal: 5-10ms
Image Regeneration:
├─ Read metadata: 10-20ms (backend)
├─ POST /api/images/variations: 50-100ms (queue job)
├─ Polling (60 × 2s): 30-120 seconds (ML inference)
├─ Parse metadata: <1ms
└─ Preload image: 100-500ms (network)
Persistence Phase
Locale Write:
├─ POST /api/dev/write-locale: 20-50ms
│ ├─ Read current file: 5-10ms
│ ├─ Create backup: 5-10ms
│ └─ Write updated file: 10-30ms
├─ Vite HMR trigger: 10-50ms
└─ React re-render: 5-20ms
Total: 40-120ms
Image Hot Swap:
├─ Preload new image: 100-500ms (network)
├─ DOM manipulation: <1ms
└─ CSS transition: 300ms (opacity fade)
Total: 400-800ms
Error Handling Flows
Transformation Failure
Transformer returns { success: false, error: "..." }
│
▼
┌───────────────────────────────────────┐
│ TransformerModal renders error state: │
│ - XCircleIcon (red) │
│ - Error message │
│ - "Close" button only │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ showToast(error, 'error') │
│ User clicks "Close" │
│ Modal closes, no changes applied │
└───────────────────────────────────────┘
Write Failure
Sink returns { success: false, error: "..." }
│
▼
┌───────────────────────────────────────┐
│ showToast('Failed to apply changes') │
│ Modal remains open │
│ User can retry or cancel │
└───────────────────────────────────────┘
Service Unavailable
transformer.checkHealth() returns { available: false }
│
▼
┌───────────────────────────────────────┐
│ TransformerModal shows warning: │
│ - AlertCircleIcon (yellow) │
│ - "Service unavailable" message │
│ - "Run Transformer" button disabled │
└───────────────────────────────────────┘
Keyboard Shortcuts Flow
User presses Cmd/Ctrl+E while hovering over editable content
│
▼
┌───────────────────────────────────────┐
│ useKeyboardShortcuts() detects event │
│ Checks: hoveredHandle !== null │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ Programmatically trigger edit: │
│ setSelectedHandle(hoveredHandle) │
│ Open TransformerModal │
└───────────────────────────────────────┘
References
- Architecture Overview:
docs/architecture/dev-content-editing.md - UI Integration:
docs/architecture/ui-integration-patterns.md - Security:
docs/architecture/dev-api-security.md - Development Patterns:
tooling/claude/dot-claude/instructions/dev-content-editing-patterns.md
Last Updated: 2026-01-12 Status: Living document - update as data flows evolve