Engineering Notes

Site list scrolls to top on every delete — fixing the missing keepScroll argument across 6 call sites

Site list scrolls to top on every delete — fixing the missing keepScroll argument across 6 call sites The symptom Every time a site was deleted from the WordPress Maintenance Manager site list, the page jumped back to the very top. In environments managing many sites, removing an item in the middle of the list forced users to scroll back down before they could act on the next one. The same behavior occurred in five other operations: drag-and-drop reordering, thumbnail fetching (running behind an open modal), category deletion, tag deletion, and after maintenance completion. Root cause: omitting the keepScroll argument The fetchSites() function accepts two arguments: keepPage and keepScroll. When …

Read more
Engineering Notes

When maintenance ends with an error, the plugin update badge disappears — designing a backend marker and frontend count sync

The WordPress maintenance tool shows a badge on each site card when there are pending plugin updates. Under normal operation, when maintenance completes successfully, the badge clears until the next dashboard scan picks up fresh data. That’s correct behavior. But a report came in: when maintenance ends with a warning or an SSH error partway through, the badge disappears even if plugins were left unupdated. “If the update didn’t finish, I need to know — but the badge is telling me there’s nothing left to do.” Why the badge was disappearing Tracing the bug, the root cause was in the frontend badge-update logic, which was built on a hard-coded assumption: …

Read more
Engineering Notes

Deploying and committing to git are not the same done — the trap of assuming uploaded means synced

Near the end of a release, every file transfer to the production server succeeded, and the version file that triggers distribution was updated too. With that confirmed, the release got reported as complete — except the local git repository never actually had those changes committed. Note: “Deploying” here means transferring changed files to the production server (via scp, for example) so they’re actually live for users. “git push” is a separate operation that records the change history in a remote repository. What happened This release involved transferring seven files to the production server: five landing-page update-notice files, the version file that triggers distribution, and a progress-log file. The transfer itself …

Read more
Engineering Notes

Checking that a count did not change — a sanity check before writing a reorder API result

Building a drag-and-drop reorder feature for a list of items means sending the new order to the server and saving it. If there’s even a small flaw in the reordering logic, that save can quietly persist data with items missing or duplicated. Note: A “sanity check” here means a lightweight check that the result of a process satisfies a condition that should obviously hold — not a thorough validation, just a last-resort check for “is this clearly wrong?” The shape of the reorder logic The new order from a drag-and-drop interaction arrives at the server as an array of IDs. The server reorders the existing data to match. # Place …

Read more
Engineering Notes

A version bump that got left behind — fixing it with a single source of truth and a –check gate

Bumping a desktop app’s version number sounds like a one-line change. In practice it can mean editing five different files at once. During one release, exactly one of those files got missed, and an installer nearly got built carrying the old version number. Note: A “version bump” just means incrementing the version number — say, from 1.6.9 to 1.6.10. The operation sounds trivial, but it can end up scattered across multiple files in practice. What happened This app’s version number lives in five separate places: The VERSION variable in version.py MyAppVersion in the Windows installer definition file (.iss) The version number and download URLs in version.json Two download links on …

Read more
Engineering Notes

Three site-list UX improvements: instant completion feedback, error highlighting, and auto-deselect

Three site-list UX improvements: instant completion feedback, error highlighting, and auto-deselect The site-list visualization work has run across several posts: the blue pulse and green completion border, the marker-based running-site detection, and the three-state visual hierarchy. v1.6.8 added three more UX improvements to the same area. This post records the design decisions behind them. All three share a common starting point: managing 10–20 sites in a bulk maintenance run that takes several tens of minutes, and making that time easier to follow without introducing new failure modes. Improvement 1 — refresh the thumbnail and badge the moment a site finishes Previously, thumbnails and the pending-plugin badge were only refreshed after …

Read more
Engineering Notes

Why environment variables don’t suppress WP-CLI PHP Deprecated warnings — the phar + shebang path and a three-part structural fix

A previous post covered how to absorb PHP 8.2 Deprecated warnings from WP-CLI using a three-layer defense. The approach — prepending WP_CLI_PHP_ARGS to set error_reporting — works in many environments. But a case came up where Deprecated warnings wouldn’t disappear despite the same configuration. Tracing the cause revealed a structural reason why the environment variable never arrived. This post records that root cause and the three-part fix added in v1.6.8. Why environment variables don’t arrive — the phar + shebang execution path An agency reported that on Xserver, plugin list retrieval was failing across multiple sites (referred to here as “site A / site B”) with a large volume of …

Read more
Engineering Notes

A variable I refactored into one function — and kept referencing from another. Python lazy evaluation hid it, and an AST test finally caught it

One day the browser automation flow started failing right after plugin updates with NameError: name ‘plugin_form_selectors’ is not defined in the post-update “residual check” step. The refactor that introduced this had landed back in v1.6.1. The error didn’t surface until many rounds later. Reading the code, the cause is obvious in seconds — but nobody hit it for ages, because Python’s lazy evaluation kept the leftover reference hidden until exactly the right execution path ran. This post walks through what the bug was and how we structurally prevented its kind via an AST static-analysis test. What happened — a reference that crossed a scope boundary browser_utils.py has two functions involved: …

Read more
Engineering Notes

The day a user told me the running site looks weak — rebuilding the three-state visual hierarchy with background color

After we’d shipped the colored borders for in-maintenance and completed sites on the multi-site list, real-world usage produced an unexpected report: “the running site somehow looks weak.” Followed by: “the green border that’s supposed to stick for 24 hours doesn’t show up.” The first one was surprising. The running site has a pulsing blue border — it should be obvious. But when I looked at the screen again, yes, it did look weak relative to its neighbors. The problem turned out not to be the running-state color itself, but a hierarchy among the three states that had quietly inverted. This post walks through that visual-hierarchy collapse and rebuild, along with …

Read more
Engineering Notes

Visualizing maintenance status on the site list — blue pulsing border for running, green solid for done

When you’re running maintenance across several WordPress sites in sequence, a list view with text-only status doesn’t make “which site is being processed now” or “which ones are already done” easy to spot at a glance. A client put it plainly: “Make it visually obvious in the list which sites are in maintenance and which are finished.“ A colored border is the obvious move, but there are real choices to make. What colors? Where do we get the state from? When does the “done” mark go away? And — can we ship this without touching the backend? This post walks through those four calls and the minimal frontend-only implementation we …

Read more