Real Bugs, Real Fixes: Debugging in the Open
What breaks when you build a network tool alone, and the recurring bug-and-security sweep habit that substitutes for a second engineer's review.
Part 4 of 10 in Building MistPulse: A Network Architect's Field Notes

Every engineering story that only talks about what worked is missing half the picture. MistPulse has shipped its share of bugs, and I've come to think that's not a mark against the project; it's what happens when you build fast, alone, and honestly. What matters more than avoiding every mistake is having a repeatable process for finding them before they cause real damage, and being willing to admit when something needs a second pass.
The rhythm of a solo bug and security sweep
Without a team of reviewers, I built my own substitute: a recurring habit of stepping back from feature work to run a deliberate bug-and-security sweep across recent changes. This isn't a vague "let me look things over." It's a checklist-driven pass: are there any regex patterns that could match more than intended? Any write path that doesn't validate its input? Any place where an error message might leak more detail than it should? Any code that assumes a happy-path response shape without handling the null or empty case?
These sweeps happen most rigorously right before anything gets deployed, especially anything touching the Supabase backend, since a mistake there has a much larger blast radius than a mistake in a single screen of the mobile app. The habit has caught real issues: overly permissive path patterns in the proxy's allowlist, missing validation on a device-rename input, and, more than once, a case where a newly added feature's error handling assumed the wrong shape for a failure response.
What matters more than avoiding every mistake is having a repeatable process for finding them before they cause damage.

A generalization bug: when a feature outgrows its own assumptions
One instructive case came from a feature that started narrow and grew. A bulk-rename tool was originally built to handle only access points: rename a batch of APs at once by pattern, useful when standardizing naming conventions across a site. It worked well and got used often enough that the obvious next step was extending it to switches too, since the same operational need applied there.
The naive approach would have been to copy the AP-specific component, swap a few labels, and ship a near-duplicate for switches. I chose instead to generalize the underlying component to handle both device types through a shared interface, which is the right call for long-term maintainability, but it's also exactly the kind of change that introduces subtle bugs. Assumptions that were safe when the component only ever saw one device type (certain fields always being present, certain validation rules always applying) stopped being safe once a second, differently-shaped device type came through the same code path.
Catching that required going back to the same discipline from the last post: pulling real API responses for both device types side by side and checking, field by field, where the assumptions the original component made no longer held. A couple of them didn't, and the fix was straightforward once identified, but it wouldn't have been caught by just running the happy path with a switch and seeing that it appeared to work.
- Generalizing a feature to a new use case means re-verifying every assumption the original version relied on
- Side-by-side comparison of API response shapes across use cases catches drift that a single test case won't
- A component that "appears to work" after a quick manual test isn't the same as a component that's been checked against edge cases for the new use case
Bugs that only show up on real infrastructure
Some of the most useful bugs to find are the ones that only appear on real, messy, production-like data, not the clean, small dataset you build against during initial development. A device with an unusual configuration, a site with an unusually large number of clients, a metric that returns an unexpected but technically valid value. Running MistPulse against my own home lab's real Mist deployment, rather than only against a minimal test setup, surfaced issues that a smaller, tidier test environment never would have.
This is part of why the "curl first" discipline from the previous post pays for itself repeatedly: the same live-testing muscle that verifies a new feature before building it is also the muscle that catches regressions when an existing feature meets a real-world edge case for the first time.
Fixing without a new build, when the fix doesn't need one
Not every fix requires shipping a new TestFlight build. Backend-only changes: a corrected regex in the proxy's allowlist, a validation rule added to an edge function, can be deployed directly to Supabase without touching the mobile app at all, since the app already calls those endpoints generically. Knowing which layer a bug lives in, and whether fixing it requires a client update or just a backend deployment, has saved a meaningful amount of iteration time across this project. It's also one more argument for the proxy architecture from Part 2: a real security boundary turns out to double as a very convenient place to patch bugs quickly.
The bugs so far have mostly been about the app talking to Mist. The next post turns to a much harder integration challenge: teaching the phone's own voice assistant to talk to Mist too.
Coming up next: Teaching Siri to Speak Mist: building native App Intents integration, and the real build failures that came with it
Building MistPulse: A Network Architect's Field Notes
- 1.The Idea: Why I Built MistPulse
- 2.Laying the Foundation: Architecture and the Security-First Proxy
- 3.Curl First, Build Second: The API Validation Methodology
- 4.Real Bugs, Real Fixes: Debugging in the Open (you are here)
- 5.Teaching Siri to Speak Mist
- 6.The Entity Resolution Saga
- 7.Generalizing Features: The Bulk Rename Story
- 8.Security by Design
- 9.When APIs Change Under You: The SLE Migration Story
- 10.Lessons Learned & What's Next
Related posts

Laying the Foundation: Architecture and the Security-First Proxy
Why MistPulse's mobile app never touches a Mist API token, and the Supabase edge-function relay that stands in its place as the system's only security boundary.

Teaching Siri to Speak Mist
Building native App Intents integration for MistPulse: a parallel Swift API client, its own build line, and the real TestFlight failures that came with going native.

Curl First, Build Second: The API Validation Methodology
The one rule that shaped every feature in MistPulse: never write application code against an API response shape you haven't verified yourself, with a real curl call, first.

The Entity Resolution Saga
Getting a spoken name to reliably match the right site or device in Mist: why exact matching failed, and how fuzzy matching with a confidence threshold fixed it.
Comments
No comments yet. Be the first.