platform-docs/architecture/dev-content-editing-data-flow.md
2026-01-12 11:08:17 -08:00

39 KiB
Raw Blame History

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