# Critical WYSIWYG Knowledge - DO NOT LOSE

This document captures all the hard-won knowledge from our WYSIWYG editor development. These patterns and solutions took significant effort to discover and MUST be preserved during refactoring.

## 1. Static Rendering to Prevent Focus Loss

### Problem
When using Lit's reactive rendering, every state change would cause a re-render, which would:
- Lose cursor position
- Lose focus state
- Interrupt typing
- Break IME (Input Method Editor) support

### Solution
We render blocks **statically** and manage updates imperatively:

```typescript
// In dees-wysiwyg-block.ts
render(): TemplateResult {
  if (!this.block) return html``;
  // Render empty container - content set in firstUpdated
  return html`<div class="wysiwyg-block-container"></div>`;
}

firstUpdated(): void {
  const container = this.shadowRoot?.querySelector('.wysiwyg-block-container');
  if (container && this.block) {
    container.innerHTML = this.renderBlockContent();
  }
}
```

### Critical Pattern
- NEVER use reactive properties that would trigger re-renders during typing
- Use `shouldUpdate()` to prevent unnecessary renders
- Manage content updates imperatively through event handlers

## 2. Shadow DOM Selection Handling

### Problem
The Web Selection API doesn't work across Shadow DOM boundaries by default. This broke:
- Text selection
- Cursor position tracking
- Formatting detection
- Content splitting for Enter key

### Solution
Use the `getComposedRanges` API with all relevant shadow roots:

```typescript
// From paragraph.block.ts
const wysiwygBlock = element.closest('dees-wysiwyg-block');
const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg');
const parentShadowRoot = parentComponent?.shadowRoot;
const blockShadowRoot = (wysiwygBlock as any)?.shadowRoot;

const shadowRoots: ShadowRoot[] = [];
if (parentShadowRoot) shadowRoots.push(parentShadowRoot);
if (blockShadowRoot) shadowRoots.push(blockShadowRoot);

const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
```

### Critical Pattern
- ALWAYS collect all shadow roots in the hierarchy
- Use `WysiwygSelection` utility methods that handle shadow DOM
- Never use raw `window.getSelection()` without shadow root context

## 3. Cursor Position Tracking

### Problem
Cursor position would be lost during various operations, making it impossible to:
- Split content at the right position for Enter key
- Restore cursor after operations
- Track position for formatting

### Solution
Track cursor position through multiple events and maintain `lastKnownCursorPosition`:

```typescript
// Track on every relevant event
private lastKnownCursorPosition: number = 0;

// In event handlers:
const pos = this.getCursorPosition(element);
if (pos !== null) {
  this.lastKnownCursorPosition = pos;
}

// Fallback when selection not available:
if (!selectionInfo && this.lastKnownCursorPosition !== null) {
  // Use last known position
}
```

### Critical Events to Track
- `input` - After text changes
- `keydown` - Before key press
- `keyup` - After key press
- `mouseup` - After mouse selection
- `click` - With setTimeout(0) for browser to set cursor

## 4. Content Splitting for Enter Key

### Problem
Splitting content at cursor position while preserving HTML formatting was complex due to:
- Need to preserve formatting tags
- Shadow DOM complications
- Cursor position accuracy

### Solution
Use Range API to split content while preserving HTML:

```typescript
getSplitContent(): { before: string; after: string } | null {
  // Create ranges for before and after cursor
  const beforeRange = document.createRange();
  const afterRange = document.createRange();
  
  beforeRange.setStart(element, 0);
  beforeRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset);
  
  afterRange.setStart(selectionInfo.startContainer, selectionInfo.startOffset);
  afterRange.setEnd(element, element.childNodes.length);
  
  // Extract HTML content
  const beforeFragment = beforeRange.cloneContents();
  const afterFragment = afterRange.cloneContents();
  
  // Convert to HTML strings
  const tempDiv = document.createElement('div');
  tempDiv.appendChild(beforeFragment);
  const beforeHtml = tempDiv.innerHTML;
  
  tempDiv.innerHTML = '';
  tempDiv.appendChild(afterFragment);
  const afterHtml = tempDiv.innerHTML;
  
  return { before: beforeHtml, after: afterHtml };
}
```

## 5. Focus Management

### Problem
Focus would be lost or not properly set due to:
- Timing issues with DOM updates
- Shadow DOM complications
- Browser inconsistencies

### Solution
Use defensive focus management with fallbacks:

```typescript
focus(element: HTMLElement): void {
  const block = element.querySelector('.block');
  if (!block) return;
  
  // Ensure focusable
  if (!block.hasAttribute('contenteditable')) {
    block.setAttribute('contenteditable', 'true');
  }
  
  block.focus();
  
  // Fallback with microtask if focus failed
  if (document.activeElement !== block && element.shadowRoot?.activeElement !== block) {
    Promise.resolve().then(() => {
      block.focus();
    });
  }
}
```

## 6. Selection Event Handling for Formatting

### Problem
Need to show formatting menu when text is selected, but selection events don't bubble across Shadow DOM.

### Solution
Dispatch custom events with selection information:

```typescript
// Listen for selection changes
document.addEventListener('selectionchange', () => {
  const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
  
  if (selectedText !== this.lastSelectedText) {
    const range = WysiwygSelection.createRangeFromInfo(selectionInfo);
    const rect = range.getBoundingClientRect();
    
    // Dispatch custom event
    this.dispatchSelectionEvent(element, {
      text: selectedText,
      blockId: block.id,
      range: range,
      rect: rect,
      hasSelection: true
    });
  }
});

// Custom event bubbles through Shadow DOM
const event = new CustomEvent('block-text-selected', {
  detail,
  bubbles: true,
  composed: true
});
```

## 7. IME (Input Method Editor) Support

### Problem
Composition events for non-Latin input methods would break without proper handling.

### Solution
Track composition state and handle events:

```typescript
// In dees-input-wysiwyg.ts
public isComposing: boolean = false;

// In block handlers
element.addEventListener('compositionstart', () => {
  handlers.onCompositionStart(); // Sets isComposing = true
});

element.addEventListener('compositionend', () => {
  handlers.onCompositionEnd(); // Sets isComposing = false
});

// Don't process certain operations during composition
if (this.isComposing) return;
```

## 8. Programmatic Rendering

### Problem
Lit's declarative rendering would cause focus loss and performance issues with many blocks.

### Solution
Render blocks programmatically:

```typescript
public renderBlocksProgrammatically() {
  if (!this.editorContentRef) return;
  
  // Clear existing blocks
  this.editorContentRef.innerHTML = '';
  
  // Create and append block elements
  this.blocks.forEach(block => {
    const blockWrapper = this.createBlockElement(block);
    this.editorContentRef.appendChild(blockWrapper);
  });
}
```

## 9. Block Handler Architecture Requirements

When creating new block handlers, they MUST:

1. **Implement all cursor/selection methods** even if not applicable
2. **Use Shadow DOM-aware selection utilities**
3. **Track cursor position through events**
4. **Handle focus with fallbacks**
5. **Preserve HTML content when getting/setting**
6. **Dispatch selection events for formatting**
7. **Support IME composition events**
8. **Clean up event listeners on disconnect**

## 10. Testing Considerations

### webhelpers.fixture() Issue
The test helper `webhelpers.fixture()` triggers property changes during initialization that can cause null reference errors. Always:

1. Check for null/undefined before accessing nested properties
2. Set required properties in specific order when testing
3. Consider manual element creation for complex test scenarios

## Summary

These patterns represent hours of debugging and problem-solving. When refactoring:

1. **NEVER** remove static rendering approach
2. **ALWAYS** use Shadow DOM-aware selection utilities
3. **MAINTAIN** cursor position tracking through all events
4. **PRESERVE** the complex content splitting logic
5. **KEEP** all focus management fallbacks
6. **ENSURE** selection events bubble through Shadow DOM
7. **SUPPORT** IME composition events
8. **TEST** thoroughly with actual typing, not just unit tests

Any changes that break these patterns will result in a degraded user experience that took significant effort to achieve.