Theme
Legacy code
Changing a big old app that still earns money, without a rewrite.
42 posts by DX, in two lanes: plain-language stories and the technical build log.
Everyday lane
Plain-language stories. No jargon. 8 posts.
Start here
Five Changes, 47,490 Lines, and a Dropdown That Split Two Matching Labels
A small new choice exposed an old assumption, sending event work to the wrong account until each step named its destination.
Modernizing a legacy app step by step
-
Five Changes, 47,490 Lines, and a Dropdown That Split Two Matching Labels
A small new choice exposed an old assumption, sending event work to the wrong account until each step named its destination.
-
Two Coupon Admin Pages, 478 Lines Added, 235 Removed
A small rebuild of two old admin pages made future changes safer without forcing a risky whole-app rewrite.
Small legacy web bugs and their fixes
-
Four Lines That Replaced Response.Redirect on the Bad-ID Page
A tiny change stopped a bad link from turning into a blank apology, and left a note about what still needed work.
-
The Closing Marker Inside a Comment That the Server Treated as Real
A small documentation example had to be rewritten because four punctuation marks could change how an older web page read the file.
Why old code guesses and drifts
-
One Missing minPkg Label and a Menu Entry With No Service Level
A small label already in the site menu solved most of a tier question, until one page appeared after the label had been applied.
-
Four Copies of the Same Lookup Rule Hid Behind One Broken Feature
An event feature failed in three different ways because four parts of the system were each deciding its identity for themselves.
-
1,477 Lines of Recovery Code Replaced Three Scripts That Disagreed
A recovery emergency showed why keeping several almost-identical fixes can make one old mistake far more dangerous.
-
Why We Put a Switchboard in Front of Every Customer Web Address
A warning about aging machines became a reason to move carefully without turning an urgent repair risk into a costly rewrite.
Technical lane
The build log: the logs, the code, the fix. 34 posts.
Start here
A 47,490-line JavaScript file and what it tells you about a profitable legacy app
Two 'current account' globals were equal until a context switch split them and broke hundreds of call sites in one 47,490-line file.
Modernizing a legacy app step by step
-
A 47,490-line JavaScript file and what it tells you about a profitable legacy app
Two 'current account' globals were equal until a context switch split them and broke hundreds of call sites in one 47,490-line file.
-
478 Insertions, 235 Deletions, and a Few Hundred Pages Left Untouched
How we replaced string-soup pages with a fluent DOM builder on Page.prototype: no framework, no big-bang rewrite, one page at a time.
-
Hand-rolled hydration: passing server state to the browser with no framework
Hand-rolled hydration broke silently when a second account context appeared. The fix was one named global and a one-line comment at the injection site.
-
3,853 Insertions Across 20 Files and Not a Single New Framework
Five report pages inside a legacy admin application, one page pattern, and the habit that paid: check in your review SQL as the canonical business definition.
-
Adding OpenTelemetry to a Legacy Web App Without Rewriting It
Thread traceparent headers from a legacy application into a modern Node collector via IIS header forwarding, so a sidecar emits spans on behalf of the legacy layer without touching its core logic.
-
The ambient-global bug: when "current account" is a mutable global 100 call sites read
We shipped a feature that let an admin pick a child event from a dropdown, and the moment they picked one, every save started writing to the wrong account. No error. No exception.
-
When pageHQ and activeHQ Stopped Being the Same Object
A helper function in our SaaS app had a default argument that pointed at the wrong account. Fixing it meant threading the same eight-character override through 27 call sites by hand.
-
One copy-pasted ctx.orgName expression and three different symptoms
Four queries each derived the same entity key inline and disagreed. Why duplicated derivation is a live correctness bug, and the one-resolver fix.
-
A Plan Price Over 100 Quietly Blocked Every Trial
A client-side delete gate re-derived a node count and treated plan price as money received. Moving the decision to server-owned counts and transaction totals aligned the gate with its actual policy.
-
The ?div= Query Param and the Page That Loaded Everything First
Resolve a page's selection prerequisite on entry: redirect on zero options, auto-pick on one, prompt on many. The expensive fetch only fires once something is selected.
-
A 500ms Reload Sent Users Back to the Parent Account
A query param carried selection state until two links forgot to re-emit it. Why URL-as-state needs one central URL builder, not hand-appending at call sites.
-
The feature was fine. First-time state was the bug.
The event feature worked after week two. The other three weeks went to everything around it. Every signup path left different first-run state behind.
-
Three Commits and One Afternoon for a Staff-Only Event Signup
A request called for a low-discoverability event signup path. An existing admin linking flow already supplied the needed context and downstream handling, so extending it avoided a parallel implementation.
-
Slow Admin Pages, No 500s, and a SELECT Inside Session Init
We closed a redirect-loop bug in two hours. Then the fix itself saturated the IIS worker pool on every authenticated admin page load. Two tickets, one afternoon.
-
One ADO Cursor Mode Sat Under 314 Broken Stats Pages
Stats pages were broken across 314 sites. The cause was a shared recordset helper opening keyset ADO cursors over aggregate queries. The data caught up.
-
Why 4,481 pixels of dashboard collapsed to 1,256 after deleting panels
One number stood out: 8. Out of 3,673 session rows scattered across six endpoints that each answered "what's running right now" differently, only 8 rows were the ones that mattered: the live agent sessions actually in flight.
Small legacy web bugs and their fixes
-
The Rules File That Outlived a 34,161 Commit History
The IIS-URLAuthorization script came first, and it never got a comment explaining why. iisreset, disable a module, move on, ship the ticket.
-
ScheduleTools260219.js and the six digits nobody explained
A legacy web application had no bundler or asset manifest, so it versioned compiled JavaScript and CSS with date-stamped filenames. The convention changes the asset URL with a minimal, auditable release step.
-
One Changed Line in commit-4E74 and a Robocopy Purge
Commit commit-4E74 changed one line on March 28, 2026, and that line stopped a legacy deployment script from deleting its own memory.
-
Response.Redirect Blew Up the Error Page. Server.Transfer Didn't.
A bad ID reached an internal-error screen because a late redirect ran inside the failure path. The fix replaced brittle URL assembly with a safe internal handoff and documented the remaining fallback issue.
-
Why a JScript Block Comment Could Not Protect the Include Header
An include comment in an older web application contained a literal closing delimiter that the template scanner could interpret before the JScript block comment existed. The prototype was rewritten to describe the syntax without reproducing the delimiter.
-
Why Request.Form Stays Empty After a Clean 200 POST
In a legacy web application, Request.Form only parses application/x-www-form-urlencoded. Post FormData from a modern fetch call and the collection comes back silently empty with no error. Here is why, and the one-line fix.
-
Where `&` Goes in a Chained Entity Decode
An admin opens an edit dialog and the label field shows entity garbage instead of their text. Here is the decode/encode boundary problem behind it, the fix, and the one ordering detail that will burn you if you get careless.
-
267 Script Contexts, 33 Customer Inputs, and One Filter That Broke Uploads
267 places in the codebase write a string into a <script> block or an inline handler, and we had to decide which of them were dangerous.
Why old code guesses and drifts
-
Why a Forced Server Migration Did Not Become a Rewrite for Us
The host warned they can't promise replacement parts for our dedicated boxes. The fix everyone expects is a rewrite. The fix that pays the bills is decoupling: DNS to the edge first, hardware second, stack unchanged.
-
Why minPkg was missing on a single page and nothing threw
When a group of pages needed tier-aware navigation, an existing per-page tier field avoided a parallel UI catalog. One dynamically added page exposed the ordering bug that declarative setup can hide.
-
What the First Real Account Found That Sample Data Missed
Moving one organization onto its category's real code path surfaced a long-standing tier gate that had simply never been built.
Replacing legacy screens with new ones
-
What a Cleared Name Field Revealed About Our Create Event Dialog
A complex event form improved after a product review reconsidered its container. Dialogs are not always wrong, but form scope, lifecycle, and mobile use should determine the interaction surface.
-
Why One Organization Showed Three Different Member Counts on April 20
A ticket started as a Packages UI redesign. By the time we closed it, we had touched the organization taxonomy, the credit constants, and the account type routing logic in three separate files. The UI was the last thing we changed.
-
The reuse story died, so we shipped a new cart instead
We went in expecting to wire event registration onto the existing cart. One grep later, the premise was dead and we were designing from scratch. The new cart shipped the same day.
-
What 72 minutes of review tooling did to a legacy codebase
At 4:06 PM on May 8, we merged the checkout feature branch into the Packages redesign branch, pushed to an internal host, and sent the reviewer a URL. That part was routine. What was different was the page the reviewer loaded.
-
The 22 language codes behind one site setting and a widget that rewrites everything
A language switch had to reach a visitor's screen without ever touching the buttons the site's own owner uses to run it.
-
The Filter Fix That Looked Right for 90 Seconds
The filter fix looked correct for about 90 seconds. Then we noticed we had changed the dropdown builder, not the display loop that was actually suppressing the control.
More in this theme
Hitting one of these walls in your own codebase or your own machine? Start an assessment or talk to us.