Pdf Processor
Error Fix: 5 Bugs in the HTML CAD Editor (And Why Each One Was Predictable)
TLDR
Interactive DOM layout editors frequently encounter state synchronization, event listener, and CSS Grid bugs when combining drag-and-drop interactions withcontenteditable surfaces. Five distinct bugs across marquee selection, ghost column rendering, state synchronization order, dragover frame rate thrashing, and Vite ES module bundling were isolated and resolved.
| Defect Area | Root Cause | Engineering Solution |
|---|---|---|
| Marquee Deselection | Missing shiftKey check on mouseup | Check !e.ctrlKey && !e.metaKey && !e.shiftKey |
| Ghost Column Leakage | Inferring column count from DOM children | Read explicit class string pdf-zone--cols-N |
| Divider Sync Persistence | Async state clear after sync call | Clear dragState = null before state sync |
| Dragover Thrashing | Un-throttled CSS toggles on 60fps drag | Gate class mutations on target element diff |
| Monaco Bundle Crash | Window global lookup (window.monaco) | Import directly: import * as monaco from 'monaco-editor' |
Technical defect diagnostics & fixes
1. Marquee deselection modifier check
// REFACTORED: Include Shift key in modifier preservation check
export function onMarqueeMouseUp(evt) {
if (!evt.ctrlKey && !evt.metaKey && !evt.shiftKey) {
clearSelection();
}
}
2. Source-of-Truth column count inspection
Single-column zones contain regions as direct children (no.pdf-col wrapper). Counting .pdf-col children returned 0, causing ghost columns to render on 1-column containers:
// REFACTORED: Read column count explicitly from class name
export function getZoneColumnCount(zoneElement) {
const match = zoneElement.className.match(/pdf-zone--cols-(\d)/);
return match ? parseInt(match[1], 10) : 1;
}
3. Drag state nullification order
Setting_resizeDrag = null after calling state synchronization caused stale dividers to be re-injected:
// REFACTORED: Clear drag state BEFORE triggering state sync
export function finalizeColumnResize(zoneElement, computedWidths) {
const currentDrag = _resizeDrag;
_resizeDrag = null; // Clear state first!
saveColumnWidths(zoneElement, computedWidths); // Triggers downstream syncState()
}
4. Idempotent quadrant dragover indicators
CallingremoveIndicator() on every 60fps dragover frame caused severe UI flickering:
// REFACTORED: Gate indicator mutation on target change
export function updateQuadrantIndicator(targetElement, isLeftQuadrant) {
if (_activeQuadrantTarget && _activeQuadrantTarget !== targetElement) {
clearQuadrantHighlight(_activeQuadrantTarget);
}
_activeQuadrantTarget = targetElement; targetElement.classList.remove('sel-drop-left', 'sel-drop-right'); targetElement.classList.add(isLeftQuadrant ? 'sel-drop-left' : 'sel-drop-right'); }
5. Vite module import strategy
Vite bundles libraries as ES modules, leavingwindow.monaco undefined in production builds:
// REFACTORED: Explicit ES module import
import * as monaco from 'monaco-editor';
export function initMonacoInstance(containerElement) { return monaco.editor.create(containerElement, { theme: 'vs-dark' }); }
Rule of thumb: Clear state flags before triggering sync callbacks, gate high-frequency dragover handlers on target diff checks, and import bundled dependencies explicitly.
Read this post in the full Engineering Journal →