Engineering Journal
Pdf Processor
Pdf Processor

Post-Mortem: Diff Tab Redesign, Mobile Drag Fix, and the CSS Specificity Trap

2026-06-04

TLDR

During a front-end overhaul of the Ginexys PDF Processor diff panel, three distinct defects emerged: incorrect mouse event coordinate math on mobile screen widths ($<1024\text{px}$), missing touch interaction support on split-pane resizers, and a CSS cascade collision that left inactive diff panels visible on all tabs. We resolved all three by querying computed element styles at drag runtime, registering touch listeners with { passive: false }, and decoupling layout utility classes from component visibility logic.
Defect AreaRoot CauseEngineering Solution
Mobile Axis ResizingHardcoded clientX on vertical layoutsQuery getComputedStyle($el).flexDirection
Mobile Touch ResizingBrowser intercepted touch scroll eventsAdd { passive: false } & call preventDefault()
Panel Visibility CollisionEqual specificity display: flex overrideScope selector to .view-panel.diff-layout

Technical problem analysis & solutions

1. Dynamic layout axis detection

The visual diff panel switches from flex-direction: row to flex-direction: column at $1024\text{px}$. The initial resizing handler calculated drag offsets using clientX and outerWidth(), which produced invalid dimensions on vertical stacked layouts.

To fix this, determine layout direction dynamically at drag start by inspecting computed element styles:

export function initResponsivePanelResizer($container, $divider) {
  $divider.on('mousedown touchstart', (e) => {
    e.preventDefault();

// Query active computed style directly from DOM const isStacked = window.getComputedStyle($container[0]).flexDirection === 'column'; const firstPane = $container.find('.vd-pane').first(); const startSizePx = isStacked ? firstPane.outerHeight() : firstPane.outerWidth(); const startPos = extractPointCoordinate(e, isStacked);

const onMove = (moveEvt) => { const currentPos = extractPointCoordinate(moveEvt, isStacked); const delta = currentPos - startPos; const totalSize = isStacked ? $container.outerHeight() : $container.outerWidth();

const newSizePx = Math.max(120, Math.min(totalSize - 120, startSizePx + delta)); const percentage = (newSizePx / totalSize) * 100;

$container.find('.vd-pane').eq(0).css('flex', 0 0 ${percentage}%); $container.find('.vd-pane').eq(1).css('flex', 0 0 ${100 - percentage}%); };

bindDocumentEvents(onMove); }); }


2. Touch drag event registration

Mobile web browsers default touchmove listeners to passive, preventing preventDefault() calls and causing the page to scroll during divider dragging.

Register touch listeners explicitly with { passive: false }:

function extractPointCoordinate(evt, isStacked) {
  const src = evt.touches?.[0] ?? evt;
  return isStacked ? src.clientY : src.clientX;
}

// Ensure event listener explicitly allows preventDefault() dividerElement.addEventListener('touchstart', (e) => { if (e.touches.length === 1) { e.preventDefault(); // Prevents scroll gesture interference startDragHandler(e); } }, { passive: false });


3. CSS cascade resolution

A layout helper rule (.diff-layout { display: flex }) shared equal specificity ((0, 1, 0)) with .view-panel { display: none }. Because .diff-layout appeared later in the stylesheet source order, it forced inactive diff panels to remain displayed (display: flex).
/ REFACTORED: Remove display: flex to prevent cascade collision /
.view-panel.diff-layout {
  flex-direction: column;
}
Rule of thumb: Use getComputedStyle() at runtime to handle responsive layout switches, and keep component visibility (display) separated from helper styling.
Read this post in the full Engineering Journal →