Migration recap · week of September 14 · 2026

Monday’s entry lasted three days.

Monday I wrote — carefully, accurately — that historical invoices were displayed in the portal but not payable online. By Wednesday night that sentence was obsolete. A good week for the build is a rough week for the archivist.

The milestone first. Customers can now pay their legacy-history invoices online — one at a time or several at once — and the payments land in a clean queue built as a handoff interface for the legacy system’s side of the house, with a confirmation email arriving in the accounting inbox in the same layout that team has trusted for years. Alongside payment came full parity: the historical invoice page and the native one are now visually identical — same brand bands, a one-page PDF with the same density as the legacy prints, a backorder column computed correctly for each era, matching totals blocks. A customer moving between a 2018 invoice and last week’s should notice nothing at all, including at payment time. And two documents were born that mark where this project is: the Phase 1 cutover runbook, and the Phase 2 backlog — both committed to the repo, not living in anyone’s head.

Now the engineering story. The digital catalog had been slow for months for logged-in customers with real purchase history — slow as in minutes. DevTools split the page in two: the catalog data itself answered in 0.7 seconds, while the previously-purchased lookup simply hung. The dig found a SKU-matching join wrapped in uppercase-and-trim functions that no existing index could serve, forcing a full scan of roughly 424,000 product rows per purchased SKU — and the reproducing account had 1,381 of them. Measured honestly: 154.8 seconds, on the order of 586 million evaluations, per page load. The fix is one expression index matching the join’s exact shape, delivered with a migration so the index is a reproducible fact of the codebase rather than something someone once typed on a server. And because a page should never be hostage to its slowest garnish, the catalog now renders the moment its own data arrives, with purchase badges landing asynchronously whenever they’re ready. The measured hang, post-fix: gone.

Special orders grew up this week too. Items we order in specifically for a customer had their acknowledgment living as a checkbox in the cart — functional, skippable, forgettable. It’s now a dedicated checkout step that appears only when the cart actually contains special-order items, records the acknowledgment with a timestamp, and re-arms itself if the cart changes afterward. Consent as an auditable fact instead of a scrolled-past formality.

Quieter but load-bearing: a post-commit review of new sync tooling did exactly what the review gate exists to do — caught findings before anything ran anywhere, including a background path that would have hardcoded a dry-run safety switch to off, and a subtle interaction between two tools that could have let a wrong price win permanently. Findings were written up two ways — plain language and a technical file — handed off with an interim guard in place. No drama; that’s the point of the gate.

Lines from the week: the portal password policy landed per spec; all four order tabs now share one card-header standard; a sweep ensured every customer-facing surface leads with the order number customers actually know, with the new platform’s number as fallback and both searchable. One discovery worth sharing with other builders: the platform freezes an invoice’s PDF the first time it’s generated and never regenerates it on template changes — now documented, with cutover implications. And a stuck OS package that had been silently blocking the host’s weekly security updates got diagnosed and cleared — unglamorous, and exactly the kind of thing you want found on a Tuesday instead of during launch week.

The build outran the log this week. As problems go, I’ll take it.