Engineering Notes

A tabbed form that silently refused to submit — required fields hidden behind another tab

A tabbed form that silently refused to submit — required fields hidden behind another tab Background The site edit modal kept accumulating fields — site name, category, SSH connection details, WordPress install location — until editing anything meant scrolling up and down a single long form to find the right field. To clean this up, we split it into three tabs: “Registration info,” “SSH,” and “WordPress info.” That change broke form submission itself, in a way that was hard to spot at first. What tabbing broke The tab implementation itself is straightforward. Each tab’s fields live in a <div class=”site-tab-content” data-tab=”…”>, and CSS toggles which one is visible. .site-tab-content { …

Read more
Engineering Notes

SPF, DKIM, and DMARC together — why the missing DMARC record was blocking registration emails

SPF, DKIM, and DMARC together — why the missing DMARC record was blocking registration emails Background Registration confirmation emails were not reliably reaching users on Gmail and Outlook outside Japan — sometimes landing in spam, sometimes not arriving at all. Investigation pointed to a single root cause: the wpmm.jp domain had SPF and DKIM configured, but no DMARC record. What each of the three does SPF (Sender Policy Framework) declares in DNS which IP addresses are authorized to send mail for a domain. Receiving servers check the sending IP against the SPF record to confirm the source is legitimate. DKIM (DomainKeys Identified Mail) adds a cryptographic signature to the message …

Read more
Engineering Notes

Resuming email verification after the app is closed — the pending_email state that prevents re-sending

Resuming email verification after the app is closed — the pending_email state that prevents re-sending Background On first launch, the desktop app asks the user for an email address, sends a confirmation email, and completes registration once the user clicks the link in that email. There was a trap here for anyone who closed the app before clicking the link. Email does not always arrive immediately. Closing the app with the intention of clicking the link once the email shows up, then reopening later, is a perfectly natural way to use it. But on restart the app decided that registration was still incomplete and showed the first-launch screen (the email …

Read more
Engineering Notes

The cp932 crash in the build gate that only happened on Windows — static detection, behavioral testing, and a negative check

The cp932 crash in the build gate that only happened on Windows — static detection, behavioral testing, and a negative check The symptom During the Windows build for v1.6.11, the build gate reported “version number consistency check failed.” But the version numbers were correct everywhere. The real cause was not a version mismatch. The build gate in build_app.py calls tools/bump_version.py via subprocess and checks its exit code. The success messages in bump_version.py contained emoji (✅ and similar). On Japanese Windows, the default code page is cp932, which cannot encode those characters. Python raised a UnicodeEncodeError on the very first write to stdout, the process exited non-zero, and the build gate …

Read more
WordPress Maintenance

The excluded-plugin setting that Playwright ignored — fixing browser-mode updates and false residual warnings

The excluded-plugin setting that Playwright ignored — fixing browser-mode updates and false residual warnings The symptom In browser-mode maintenance (Playwright, no SSH), plugins marked as “excluded from update checks” were still being updated. After the run, a “plugin updates remaining” WARNING email arrived every time. The excluded plugins were intentionally left behind, but the residual check treated them as unfinished updates and fired a warning — a two-part problem: wrong behavior and a misleading alert. SSH path vs. Playwright path On SSH-capable sites, WP-CLI’s –skip-plugins flag carries the ignored_plugins list into the update command. That path already excluded them correctly. The Playwright path was different. browser_update_remaining_plugins() worked by clicking the …

Read more
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
WordPress Maintenance

A csh bug we thought we fixed came back in a new feature — designing a login-shell-independent SSH command wrapper

In May 2026, bash commands sent over SSH stopped working correctly on WordPress sites hosted on Sakura Internet. The root cause: Sakura’s default login shell is csh, which can’t interpret bash syntax. We patched the codebase, consolidating all bash-syntax c.run() calls behind a _safe_run helper, and the problem went away. (That original incident is documented in the csh login-shell portability article.) Then in June 2026, a report came in: DB backups were failing every single time on all Sakura-hosted sites with an “SSH update error.” Why the same problem came back The cause: a new feature had walked into the same trap. The v1.6.9 development cycle added progress monitoring to …

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