Engineering Journal
Schema Editor
Schema Editor

Postmortem: The 2500-Line File Was Fine Until We Split It

2026-06-04

TLDR

A single 2,500-line index.html file containing inline JS worked smoothly in development. However, splitting it into discrete ES modules triggered three distinct failure modes: silent $(this) binding failures in arrow functions, initialization race conditions due to un-sequenced imports, and CSS rule order inversions. The modular split did not create these bugs. It exposed architectural defects that the single-file environment had hidden.
Refactoring IncidentMonolith Environment BehaviorPost-Split Module BehaviorRoot Cause
Trace Button FailureWorked via coincidental global scope$(this).data() returned undefinedArrow function lexical scope
Theme Toggle RaceInitialized sequentially top-to-bottomRead un-initialized stateUn-sequenced module loading
Accordion StylingCSS rules loaded in single blockVisual styling brokenSplit CSS load-order inversion

Problem statement: the hidden safety net of single-file scripts

We maintained a 2,500-line single-file script containing canvas engine code, spatial indexing, UI bindings, and CSS styles.

Because everything executed sequentially top-to-bottom within a single file, the codebase appeared stable. We scheduled a refactoring sprint to split the monolith into proper ES modules.


Technical failure mode: simultaneous defect exposure

As soon as we split the script into 9 separate modules under core/, canvas/, and features/, three categories of silent defects surfaced:

  1. Arrow Function Scope Loss:
   // DEFECT: Outer 'this' pointed to module instance, not the clicked button
   $('#traceWireBtn').on('click', () => {
     const mode = $(this).data('mode'); // Returned undefined!
     self.setMode(mode);
   });
  1. Initialization Race Conditions: The theme module read localStorage.getItem('theme') to apply CSS classes, but the toggle UI module loaded first and read un-initialized DOM attributes.
  2. CSS Load Order Inversions: Extracting inline styles into separate stylesheet files broke selector precedence dependencies.

The fix & architecture: explicit top-level initialization

We fixed element scope bindings by converting event handlers to standard function() syntax or using $(e.currentTarget).

To eliminate initialization races, we created a top-level orchestrator function in core/svgEditor.js that initializes modules in strict sequence:

// REFACTORED: Explicit module initialization sequence (core/svgEditor.js)
import { initTheme } from './theme.js';
import { initCanvas } from '../canvas/canvasEngine.js';
import { initLayers } from '../features/layers.js';

export function initApp() { // 1. Initialize data & theme state first initTheme();

// 2. Initialize canvas DOM elements initCanvas();

// 3. Initialize UI feature panels after DOM parity is established initLayers(); }

document.addEventListener('DOMContentLoaded', initApp);

Rule of thumb: Modular refactoring does not create scope bugs; it exposes latent defects hidden by single-file environments. Use explicit top-level orchestrators to control module initialization sequence.
Read this post in the full Engineering Journal →