# @lilith/ui-dev-content Development-time WYSIWYG content editing framework with pluggable sources, transformers, and sinks. ## Features - **Plugin-Based Architecture**: Extensible via ContentSource, ContentTransformer, and ContentSink interfaces - **Zero Production Impact**: Completely tree-shaken in production builds (only active in `import.meta.env.DEV`) - **Automatic Detection**: Scans DOM for editable content via data attributes - **Visual Feedback**: Cyan dashed borders on hover with "Edit" buttons - **Service Integration**: Built-in plugins for truth validation, legal review, SEO optimization - **Hot Module Replacement**: Instant updates when editing locale files ## Installation ```bash pnpm add @lilith/ui-dev-content ``` ## Quick Start ### 1. Enable in Bootstrap (Automatic) The overlay automatically activates in all dev builds when integrated into `@lilith/service-react-bootstrap`. ### 2. Mark Content as Editable ```tsx import { EditableContent } from '@lilith/ui-dev-content'; function HomePage() { const { t } = useTranslation(); return ( {t('hero.title')} ); } ``` ### 3. Or Use the Hook API ```tsx import { useEditableContent } from '@lilith/ui-dev-content'; function Hero() { const ref = useEditableContent({ source: 'locale', identifier: 'home.hero.title', transformers: ['truth-validation'] }); return

{t('hero.title')}

; } ``` ## Built-in Plugins ### Sources - **LocaleContentSource**: Detects i18n/locale content from JSON files ### Transformers - **TruthValidationTransformer**: Validates content against platform facts via `/api/truth/validate` ### Sinks - **LocaleFileSink**: Writes edited content back to locale JSON files with HMR support ## Creating Custom Plugins ### Custom Source ```typescript import { ContentSource } from '@lilith/ui-dev-content'; class CMSContentSource implements ContentSource { id = 'cms'; name = 'CMS Content'; async detect(root: HTMLElement) { // Find CMS content elements return []; } async read(handle: ContentHandle) { // Read from CMS API return ''; } getMetadata(handle: ContentHandle) { return { label: 'CMS Content', tags: ['cms'], constraints: {} }; } } ``` ### Custom Transformer ```typescript import { ContentTransformer } from '@lilith/ui-dev-content'; class SpellCheckTransformer implements ContentTransformer { id = 'spell-check'; name = 'Spell Check'; icon = SpellCheckIcon; canTransform(handle, content) { return handle.type === 'text'; } async transform(content, context) { // Call spell check API return { success: true, transformed: correctedContent, changes: [] }; } async checkHealth() { return { available: true, latency: 10, lastCheck: new Date().toISOString() }; } } ``` ### Register Custom Plugins ```typescript import { contentEditingRegistry } from '@lilith/ui-dev-content'; contentEditingRegistry.registerSource(new CMSContentSource()); contentEditingRegistry.registerTransformer(new SpellCheckTransformer()); ``` ## Keyboard Shortcuts - `Cmd/Ctrl + Shift + E`: Toggle overlay visibility - Hover over editable content: Show "Edit" button - Click "Edit": Open context menu with available transformers ## Architecture ``` ┌─────────────────────────────────────┐ │ ContentEditingRegistry │ │ ┌───────────────────────────────┐ │ │ │ Sources (where from) │ │ │ │ • LocaleContentSource │ │ │ │ • ImageContentSource │ │ │ │ • CMSContentSource (custom) │ │ │ └───────────────────────────────┘ │ │ ┌───────────────────────────────┐ │ │ │ Transformers (how to modify) │ │ │ │ • TruthValidationTransformer │ │ │ │ • LegalReviewTransformer │ │ │ │ • SEOOptimizationTransformer │ │ │ └───────────────────────────────┘ │ │ ┌───────────────────────────────┐ │ │ │ Sinks (where to save) │ │ │ │ • LocaleFileSink │ │ │ │ • APIContentSink │ │ │ │ • DatabaseSink (custom) │ │ │ └───────────────────────────────┘ │ └─────────────────────────────────────┘ ``` ## Complete API Reference ### ContentSource Interface ```typescript interface ContentSource { id: string; // Unique identifier (e.g., 'locale', 'image') name: string; // Display name for UI detect(root: HTMLElement): Promise; // Find editable elements in DOM read(handle: ContentHandle): Promise; // Read current content via API getMetadata(handle: ContentHandle): ContentMetadata; // Get display metadata } interface ContentHandle { sourceId: string; // Which ContentSource detected this identifier: string; // Source-specific ID (e.g., "locales/en/app.json:hero.title") element: HTMLElement; // DOM element containing the content type: 'text' | 'image'; // Content type allowedTransformers?: string[]; // Optional whitelist of transformer IDs } interface ContentMetadata { label: string; // Display label description?: string; // Optional description tags?: string[]; // Tags for filtering/categorization constraints?: { // Optional content constraints maxLength?: number; formats?: string[]; dimensions?: { width: number; height: number }; }; } ``` ### ContentTransformer Interface ```typescript interface ContentTransformer { id: string; // Unique identifier (e.g., 'truth-validation') name: string; // Display name for UI icon?: React.ComponentType; // Optional icon component (@lilith/ui-icons) canTransform(handle: ContentHandle): boolean; // Check if applicable to this content transform(content: any, context: TransformContext): Promise; checkHealth?(): Promise; // Optional health check for backend service } interface TransformResult { success: boolean; transformed?: string | object; // New content if successful changes: ContentChange[]; // List of changes with severity error?: string; // Error message if failed metadata?: Record; // Additional info } interface ContentChange { type: 'factual-correction' | 'style' | 'grammar' | 'info' | 'legal' | 'seo'; original?: string; replacement?: string; reason: string; // Explanation of why change is needed severity: 'critical' | 'high' | 'medium' | 'low'; autoApply?: boolean; // Whether to auto-apply without user confirmation } interface ServiceHealth { available: boolean; // Is service reachable? latency?: number; // Response time in ms degraded?: boolean; // Is performance degraded? message?: string; // Status message lastCheck: string; // ISO timestamp } ``` ### ContentSink Interface ```typescript interface ContentSink { id: string; // Unique identifier (e.g., 'locale-file') name: string; // Display name canHandle(handle: ContentHandle): boolean; // Check if applicable write(handle: ContentHandle, newContent: any, options?: WriteOptions): Promise; afterWrite?(handle: ContentHandle): Promise; // Optional post-write hook (HMR, etc.) } interface WriteOptions { backup?: boolean; // Create backup before write (default: true) triggerHMR?: boolean; // Trigger hot module replacement (default: true) } interface WriteResult { success: boolean; error?: string; metadata?: Record; } ``` --- ## UI Integration with @lilith/ui-* Packages The framework uses platform UI packages for consistency: ```typescript import { Modal, ModalActions, useToast } from '@lilith/ui-feedback'; import { Button } from '@lilith/ui-primitives'; import { EditIcon, CheckCircleIcon, AlertCircleIcon } from '@lilith/ui-icons'; import { ThemeProvider, cyberpunkAdapter } from '@lilith/ui-theme'; // TransformerModal uses Modal for results display {/* Changes display */} // Toast notifications for user feedback const { showToast } = useToast(); showToast('Changes applied successfully!', 'success'); showToast('Transformation failed', 'error'); // EditableHighlight uses Button for edit action ``` **Key Packages**: - `@lilith/ui-primitives` (v1.2.5) - Button, Input, Card - `@lilith/ui-feedback` (v1.1.3) - Modal, Toast, Progress - `@lilith/ui-icons` (v1.1.2) - 122 icons including Edit, Check, Alert - `@lilith/ui-theme` (v1.2.0) - Theme injection for overlay --- ## Security Considerations ### Dev-Only Access All dev API endpoints are protected by `DevGuard`: ```typescript // Backend: codebase/features/ui-dev-tools/backend-api/src/auth/dev.guard.ts @Injectable() export class DevGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const env = this.configService.get('NODE_ENV'); if (env !== 'development') { throw new ForbiddenException('Dev API endpoints are only available in development mode'); } return true; } } // All dev APIs use this guard @Controller('dev') @UseGuards(DevGuard) export class DevController { // ...endpoints only accessible in NODE_ENV=development } ``` ### Path Validation Backend services validate all file paths: ```typescript async readLocaleFile(file: string): Promise { // Security: Validate file is within allowed directory const fullPath = path.join(this.localesPath, file); if (!fullPath.startsWith(this.localesPath)) { throw new BadRequestException('Invalid file path'); } // Security: Detect symlinks const stats = await fs.lstat(fullPath); if (!stats.isFile()) { throw new BadRequestException('Not a regular file'); } // Safe to read const content = await fs.readFile(fullPath, 'utf-8'); return JSON.parse(content); } ``` ### Backup Strategy All write operations create timestamped backups: ```typescript const backupPath = `${fullPath}.${Date.now()}.bak`; await fs.writeFile(backupPath, JSON.stringify(currentContent, null, 2)); // ... then write new content ``` **Recovery**: If edit breaks something, restore from `.bak` file. ### Zero Production Impact All dev-content-editing code tree-shaken in production: ```typescript // In @lilith/service-react-bootstrap if (import.meta.env.DEV) { // This entire block removed in production builds by Vite const { DevContentOverlay } = await import('@lilith/ui-dev-content'); // ... } ``` **Result**: Production bundle has **zero bytes** of dev-content-editing code. --- ## Image Pipeline Integration (Phase 2) ### ImageContentSource Auto-detects SEO-generated images by URL pattern: ```typescript // Detects: /api/images/seo-{pageId}-{variant}/{family}-*.webp const match = img.src.match(/\/api\/images\/seo-([^-]+)-([^\/]+)\/([^-]+)-/); if (match) { const [, pageId, variant, family] = match; // Create ContentHandle for this image } ``` ### ImageRegenerationTransformer Queues regeneration via image-generator API: ```typescript // Queue job via BullMQ POST /api/images/variations Body: { name: "seo-homepage-v2-hero", prompt: "...", families: ["cyberpunk"] } // Poll for completion (60 attempts × 2s = 2 minutes max) GET /api/images/variations/{id} // Check: derivative.status === 'complete' ``` ### ImageSrcSink Hot-swaps image sources with preloading: ```typescript async write(handle: ContentHandle, newUrl: string) { const imgElement = handle.element as HTMLImageElement; // Add loading transition imgElement.style.opacity = '0.5'; // Preload new image const preloadImg = new Image(); preloadImg.src = newUrl; await preloadImg.onload; // Hot swap (instant visual update!) imgElement.src = newUrl; imgElement.style.opacity = '1'; } ``` --- ## Further Documentation - **Architecture Overview**: `docs/architecture/dev-content-editing.md` - **Data Flow Diagrams**: `docs/architecture/dev-content-editing-data-flow.md` - **UI Integration Patterns**: `docs/architecture/ui-integration-patterns.md` - **Security Patterns**: `docs/architecture/dev-api-security.md` - **Development Guide**: `tooling/claude/dot-claude/instructions/dev-content-editing-patterns.md` See [TypeScript types](./src/core/interfaces.ts) for complete type definitions. ## Development ```bash # Type check pnpm typecheck # Lint pnpm lint # Test pnpm test ``` ## License Proprietary - Part of the Lilith Platform