Diff Ui Redesign Postmortem
Post-Mortem: Diff Tab Redesign, Mobile Drag Fix, and the CSS Specificity Trap
TLDR: Three UI problems, three different root causes. The column detection fix was algorithmic. The mobile drag was an axis-detection oversight. The CSS specificity bug was a cascade law I already know but applied wrong under time pressure.
What We Set Out To Do
Four tasks entered this session:
- Fix raiko-aistats: 0 column splits on all 9 pages of a two-column LaTeX PDF.
- Fix visual-diff mobile drag: stacked layout (< 1024px) used horizontal
clientXeven though the divider was now vertical. - Add touch support to the compare diff resizer: mouse-only, no mobile drag.
- Redesign the diff tab chrome: two rows of controls eating 82px before any content appeared.
The Mobile Drag Problem
The visual-diff layout switches to flex-direction: column at 1024px. The original initDividerResize() always read clientX and called outerWidth() on the first pane. Neither is meaningful when the axis is vertical.
The fix sounds simple: detect which axis the layout is using. The trap was how to detect it. You cannot use a window width check because the breakpoint is a CSS media query and can be overridden. The correct source of truth is:
getComputedStyle($layout[0]).flexDirection === 'column'
Computed style reads what CSS actually applied, not what the JavaScript thinks the breakpoint should be. This check runs at drag start, not at init, so it handles viewport resizes between page load and drag attempt.
The same pattern drives cursor choice: row-resize vs col-resize, and whether to write flex: 0 0 ${topPct}% to height or width.
The Diff Tab Redesign
Two problems with the original design:
- Two separate rows of controls (mode tabs + a toolbar row) consumed ~82px before any content.
- The layout and precision controls were visually grouped but semantically separated.
This required no HTML restructuring of the diff panels themselves. Only the chrome above them changed.
The CSS Specificity Disaster
After the redesign, #view-diff started rendering on top of every other tab. The panels were supposed to be display: none when inactive. The diff panel was always visible.
Initial read of the user's report: "It's not the height. It's either you rename it view-diff where the name that is being referred to is probably compare-diff or something like that."
That sentence is about an ID mismatch hypothesis. I checked the IDs. They matched. The real culprit was in the CSS cascade.
The .view-panel rule sets display: none on all panels. A later rule .diff-layout set display: flex. These are both single-class selectors with equal specificity. Source order breaks the tie. .diff-layout appears later in the file. It wins. Every .view-panel.diff-layout element gets display: flex whether it is the active tab or not.
The fix is two characters wide: add .view-panel to the diff-layout rule.
/ Before — overrides display:none for all panels: /
.diff-layout {
display: flex;
flex-direction: column;
}
/ After — only sets flex direction, never fights display:none: / .view-panel.diff-layout { flex-direction: column; }
Two-class specificity (0,2,0) beats the single-class .view-panel rule (0,1,0), so the active-tab rule wins when it needs to. The inactive tabs keep display: none.
What Failed
Wrong hypothesis first. The user's wording pointed toward an ID mismatch. I checked IDs first. That was the wrong tree. The cascade investigation was second. In hindsight: "always visible" is a specificity smell, not a naming smell.
The display: flex on a layout helper class. Adding layout properties to a semantic class that gets applied alongside view-panel is the setup for this exact problem. A layout class should set layout properties (direction, wrap, gap). It should not set display unless it is the element that owns the display decision. .view-panel owns the display decision here. .diff-layout does not.
What Survived
- Pill group segmented controls are a permanent pattern in this codebase now.
- Axis-detecting drag via
getComputedStyleis the correct approach for responsive dividers. - Touch support via
{ passive: false }ande.touches?.[0] ?? eis now consistent across both dividers.