Engineering Journal
Pdf Processor
Pdf Processor

Diff Ui Redesign Deepdive

2026-05-30

Under the Hood: Responsive Drag, Touch Events, and CSS Cascade in the Diff UI

TLDR: Three interconnected UI problems reveal how layout-aware drag detection, unified touch/mouse event handling, and CSS specificity interact when you redesign a panel that lives inside a visibility-toggled tab system.


The Responsive Drag Problem

The visual-diff layout uses flex-direction: row on wide screens and flex-direction: column on mobile (< 1024px). The divider between the two panes needs to do different things depending on which axis is active.

Naive approach and why it fails

A window-width check:

if (window.innerWidth <= 1024) { / vertical / } else { / horizontal / }

This breaks if the user resizes the window after the divider was initialized. It also breaks if the breakpoint is overridden by a more specific CSS rule. Window width is not the source of truth here. CSS is.

Correct approach: read computed flex direction

function isStacked() {
    return getComputedStyle($layout[0]).flexDirection === 'column';
}

getComputedStyle returns the resolved value after all cascades and media queries have applied. Reading it inside startDrag() means it reflects the layout at the moment the user puts a finger or pointer on the divider -- not the layout at page load.

Unified event position extraction

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

Mouse events and touch events have the same coordinate fields once you extract the first touch from the list. The optional chaining ?.[0] safely returns undefined for mouse events, which then falls through to ?? e (the event itself). This is equivalent to a ternary but shorter and avoids importing lodash or a touch-helper library.

Dimension tracking

At drag start, capture the current first-pane dimension:

const $first = $layout.find('.vd-pane').first();
startSize = isStacked() ? $first.outerHeight() : $first.outerWidth();

During drag, compute the new size clamped to a min and max to prevent panes from collapsing:

const totalH = $layout.outerHeight();
const newH = Math.max(120, Math.min(totalH - 120, startSize + delta));
const topPct = (newH / totalH) * 100;
$panes.eq(0).css('flex', 0 0 ${topPct}%);
$panes.eq(1).css('flex', 0 0 ${100 - topPct}%);

Using flex: 0 0 N% instead of width: N% or height: N% works on both axis orientations because the flex shorthand sets the flex-basis. The browser maps flex-basis to the main axis -- width in row layouts, height in column layouts -- automatically.


Touch Event Registration

The dragging pattern requires three event pairs:

| Phase | Mouse | Touch | |-------|-------|-------| | Start | mousedown | touchstart | | Move | mousemove | touchmove | | End | mouseup | touchend |

Why { passive: false } matters

The browser defaults touchmove to passive to enable smooth scrolling. A passive listener cannot call e.preventDefault(). Without preventDefault() on touchmove, the browser scrolls the page instead of running the drag handler.

Registering touch events with { passive: false } tells the browser this listener may prevent default:

$divider[0].addEventListener('touchstart', startDrag, { passive: false });
document.addEventListener('touchmove', doDrag, { passive: false });
document.addEventListener('touchend', endDrag);

Note: touchend does not need { passive: false } because we never prevent default on it.

Why touchmove goes on document, not the divider

If the user's finger moves faster than the browser can process drag events, the pointer position can leave the divider element. If the listener is only on the divider, you lose the event mid-drag. Attaching touchmove and touchend to document ensures the drag completes correctly even if the pointer drifts off the handle.


The Diff Tab CSS Architecture

The 36px diff bar

The original diff chrome had two rows: a tab row (Rich/Plain) and a toolbar row (Split/Unified, Word/Char, stats). Total height: ~82px.

The redesign collapses this into a single div.diff-bar at height: 36px. The bar is a flex container:

.diff-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 36px;
    flex-shrink: 0;
    background: var(--toolbar-bg);
    border-bottom: 1px solid var(--border-dark);
}

flex-shrink: 0 prevents the bar from compressing when the diff workspace below it is larger than the available height. height: 36px is an explicit ceiling -- not min-height -- because the bar should never grow.

Pill group segmented control

.diff-pill-group {
    display: flex;
    align-items: center;
    background: var(--border);
    border-radius: 6px;
    padding: 2px;
    gap: 1px;
}
.diff-pill {
    height: 22px;
    padding: 0 8px;
    border-radius: 4px;
    background: transparent;
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 500;
    transition: background .1s, color .1s;
}
.diff-pill.active {
    background: var(--surface);
    color: var(--accent-dark);
    box-shadow: 0 1px 3px rgba(0,0,0,.10);
}

The container background: var(--border) serves as the "track" color. The active pill lifts out of it with background: var(--surface) and a shadow. This is the same pattern Apple uses for segmented controls in UIKit -- it reads as a single control, not a group of buttons.


The Specificity Bug

Setup

The tab visibility system works like this:

/ line ~645 in styles.css /
.view-panel {
    display: none;
}

/ active panel overrides per JS / .view-panel.active { display: flex; }

The diff layout helper class was added to make the diff panel a column-direction flex container:

/ line ~888 in styles.css -- WRONG /
.diff-layout {
    display: flex;
    flex-direction: column;
}

Why it overwrote display: none

CSS specificity score for .view-panel is (0, 1, 0). CSS specificity score for .diff-layout is (0, 1, 0). Equal specificity. Source order breaks the tie. .diff-layout appears later in the file. It wins. Every element with class diff-layout gets display: flex regardless of any earlier display: none.

Fix

/ CORRECT /
.view-panel.diff-layout {
    flex-direction: column;
}

Two-class specificity score is (0, 2, 0). This beats the single-class .view-panel rule when both apply. More importantly: removing display: flex from this rule means it never fights the visibility system at all. .view-panel.diff-layout now only sets direction, which does nothing unless the element is already displayed.

The .view-panel.active rule (also (0, 2, 0)) fires when JS adds the active class and sets display: flex correctly. Source order then resolves the two (0, 2, 0) rules in favor of .active because it was written after .view-panel.diff-layout.

The lesson

Layout helper classes should not set display. The element that controls visibility owns the display property. A class that sets layout direction on a visibility-toggled element must either match specificity exactly or remove display from its rule.

Read this post in the full Engineering Journal →