December 7, 2024
Sometimes the most frustrating bugs aren’t the ones that crash your app—they’re the ones that make your users question their sanity. Today I want to share a debugging story that consumed hours of my time and taught me some hard lessons about technical debt, architectural consistency, and the hidden costs of “quick fixes.”
The Mystery: When Presets Don’t Preset
It started with what seemed like a simple question: “Jeremy, did you change the BALANCED preset before building?” I was working on VoiceCatcher, my voice transcription tool, and a user reported that the BALANCED preset wasn’t behaving as expected. The preset should enforce sentence boundaries for professional formatting, but transcripts were breaking mid-sentence.
Easy fix, right? I checked the config file, verified the settings looked correct, and even confirmed they were being saved properly. But something was still wrong.
The Debugging Rabbit Hole
What followed was a classic debugging nightmare that every developer knows too well:
Hour 1: “Must be a simple configuration issue.”
- Checked config files ✓
- Verified settings persistence ✓
- Tested basic preset switching ✓
Hour 2: “Maybe it’s a timing issue with the IPC communication.”
- Added extensive logging to IPC handlers
- Verified GUI-to-backend communication
- Confirmed settings were reaching the main process
Hour 3: “Let me create some test scripts to isolate the problem.”
- Built multiple test files with different approaches
- Tested preset switching in isolation
- Everything looked like it was working… but it wasn’t
Hour 4: “Time to trace the actual execution path.”
- Added debug logging throughout the formatting pipeline
- Discovered that some presets worked, others didn’t
- BALANCED consistently failed despite showing correct settings
The Eureka Moment: A Tale of Two Settings
After hours of debugging, I finally found the smoking gun. The preset parameters were stored in two completely separate locations:
src/post-processor-integration.ts- ContainsPARAGRAPH_PRESETSused by the actual formatting logicmain/ipc-handler.ts- ContainsPRESET_MAPPINGSused by the GUI preset selection
This is what I call the “dual storage anti-pattern”—the same logical data duplicated in multiple places with no synchronization mechanism.
The Real-World Impact
Here’s what was actually happening:
- User selects “BALANCED” preset in GUI
- IPC handler updates settings with values from
PRESET_MAPPINGS - Formatting logic uses preset definition from
PARAGRAPH_PRESETS - The two definitions were different
So the GUI would show BALANCED selected and the config file would contain the “correct” values, but the formatting system was using completely different parameters. Users were getting inconsistent behavior that made no logical sense.
The Technical Debt Story
How did we get here? Like most architectural debt, this wasn’t malicious or stupid—it was organic:
- Initial Implementation: Source code defined presets for formatting logic
- Feature Addition: IPC handler needed preset mappings for GUI updates
- Quick Solution: Developer added separate preset definitions instead of refactoring
- No Synchronization: No mechanism to keep the two definitions in sync
- Gradual Drift: Over time, changes to one location didn’t update the other
This is the insidious nature of technical debt. Each individual decision was reasonable at the time, but the cumulative effect created a maintenance nightmare.
Lessons for Technical Product Managers
As someone who wears both the technical and product hats, this experience reinforced several critical lessons:
1. Technical Debt Isn’t Just “Developer Problems”
This wasn’t just messy code—it was a user experience disaster. Users couldn’t trust that preset selection would work consistently. In a voice transcription tool where formatting quality matters, this undermines the entire value proposition.
2. “Quick Fixes” Have Long-Term Costs
The original dual storage solution probably saved 30 minutes of refactoring time. But it cost us:
- Hours of debugging time
- User frustration and support burden
- Loss of confidence in a core feature
- Complex workarounds and documentation
3. Consistency Architecture Requires Investment
Having a “single source of truth” isn’t just good engineering practice—it’s a product quality requirement. When core data lives in multiple places, inconsistencies are inevitable.
4. Debug-Driven Development Is Expensive
We spent more time debugging this issue than it would have taken to architect it correctly from the beginning. The “move fast and break things” mentality works until things break in ways that confuse your users.
The Warning Signs You Should Watch For
Here are the red flags that indicate you might have similar architectural debt:
- Configuration in multiple files with no clear hierarchy
- “Sometimes it works, sometimes it doesn’t” user reports
- Settings that appear correct but don’t take effect
- Developers saying “just change it in both places”
- Test scripts that pass but real usage fails
What We’re Doing About It
The solution is architecturally straightforward but requires discipline:
- Create a shared constants file as the single source of truth
- Update both existing locations to import from shared constants
- Add automated tests to verify consistency
- Implement safeguards to prevent future dual storage
The technical fix is simple. The real challenge is building processes to prevent this pattern from recurring.
The Broader Context: Why This Matters
In the world of AI-powered tools and rapid prototyping, it’s tempting to prioritize features over architecture. But user trust is built on consistency and reliability. When basic functionality becomes unpredictable, users lose confidence in the entire product.
This is especially critical for tools like VoiceCatcher that users integrate into their daily workflows. A transcription tool that sometimes formats correctly and sometimes doesn’t becomes worse than useless—it becomes a source of cognitive load and frustration.
The Product Manager’s Dilemma: When You’re Not An Engineer
This experience highlighted something crucial that every technical product manager needs to understand: without proper engineering discipline, you will make architectural mistakes that engineers learned to avoid years ago.
The Rigor Gap
As a product manager who codes, I made a mistake that any experienced engineer would have caught immediately. The “dual storage anti-pattern” isn’t novel—it’s a well-known architectural pitfall with established solutions. But I didn’t know what I didn’t know.
(And before you ask: rigor? No, Rick works in finance. What we needed here was actual engineering rigor, not Rick’s quarterly spreadsheets.)
This is the fundamental challenge for product managers who take on technical implementation:
- Engineers learn through education and practice - they study design patterns, understand architectural principles, and have made (and fixed) these mistakes before
- Product managers learn through discovery - we figure things out as we go, often reinventing solutions that already exist
- The gap is invisible until it becomes expensive
Where AI and Tooling Can Help (And Where They Can’t)
Modern development tools can bridge some of this gap:
- Cursor rules files can enforce architectural patterns and catch obvious mistakes
- AI code assistants can suggest better approaches when you’re about to make a known error
- Automated testing can catch regressions you don’t even know to test for
But here’s what tools can’t do:
- Predict architectural debt before it becomes a problem
- Design systems that prevent entire categories of issues
- Provide the intuition that comes from years of making and fixing similar mistakes
The Time Cost of Inexperience
The brutal math of this situation:
- Original dual storage solution: Saved ~30 minutes of refactoring
- Total debugging time: 4+ hours of investigation
- User impact: Weeks of unreliable preset behavior
- Support burden: Multiple user reports and confusion
An experienced engineer would have:
- Recognized the pattern immediately
- Implemented a shared constants approach from the start
- Added tests to prevent regression
- Total time investment: Maybe 45 minutes
The difference isn’t intelligence—it’s pattern recognition that comes from experience.
The Frustration Multiplier
What makes this particularly challenging is that broken architecture makes every future feature harder. When you need to add a new preset, you have to:
- Remember to update it in two places
- Manually test both code paths
- Debug mysterious inconsistencies
- Document workarounds for other developers
Bad architecture isn’t just a one-time cost—it’s a tax on every future decision.
Strategies for Product Managers
Based on this experience, here’s what I’m implementing:
- Acknowledge the knowledge gap - I don’t know what experienced engineers know, and that’s okay as long as I account for it
- Invest in architectural review - For any significant technical decision, get input from experienced engineers
- Use AI defensively - Ask tools specifically about potential architectural problems, not just “how do I implement this?”
- Build learning systems - Document patterns that bit me so I don’t repeat the same mistakes
- Prioritize architectural consistency - Even if it takes longer upfront
The Meta-Learning
The most important insight: product managers need to develop engineering intuition, not just coding skills.
Learning to write code is relatively straightforward. Learning to write maintainable, scalable, consistent code requires understanding principles that take years to internalize. You can use AI and tools to accelerate this learning, but you can’t skip it entirely.
✅ UPDATE: Problem Resolved (January 2025)
The dual storage anti-pattern described in this post has been completely eliminated. Here’s what we implemented:
Technical Solution
- Created:
config/preset-constants.tsas single source of truth - Eliminated: 47+ lines of duplicate preset definitions
- Fixed: BALANCED preset parameter discrepancy (minBreakDistance: 6 → 8)
- Result: GUI preset selection now matches actual formatting behavior
Architecture Pattern
// BEFORE: Dual storage (broken)
// src/post-processor-integration.ts: PARAGRAPH_PRESETS = {...}
// main/ipc-handler.ts: PRESET_MAPPINGS = {...} // ❌ Different values!
// AFTER: Single source of truth (fixed)
// config/preset-constants.ts: export const PRESET_CONSTANTS = {...}
// Both files: import { PRESET_CONSTANTS } from '../config/preset-constants.js'
User Experience Impact
- ✅ Preset switching works reliably for all users
- ✅ No more mysterious preset inconsistencies
- ✅ MAX_BREAKS, FREQUENT, and BALANCED all behave as expected
Commit: 01534b2 - Complete architectural fix with 6 files changed, 138 insertions(+), 153 deletions(-)
Lessons Learned
The deeper lesson is about sustainable development practices. Technical debt isn’t just about code quality—it’s about user experience, team velocity, and long-term product viability.
As product managers who code, we need to:
- Recognize when “quick fixes” become architectural debt
- Invest in consistency and reliability over feature velocity
- Build systems that make the right thing the easy thing
- Treat user experience bugs as seriously as crashes
- Acknowledge that engineering discipline is a skill that requires dedicated learning
- Use AI and tools to bridge knowledge gaps, not replace fundamental understanding
The tale of two settings is really a tale about choices—the choice between immediate convenience and long-term maintainability. But it’s also a tale about the hidden costs of the “move fast and break things” mentality when you don’t have the engineering experience to know what you’re breaking.
In this case, we chose convenience and paid for it with hours of debugging and user frustration. More importantly, we learned that being a product manager who codes requires developing both product intuition AND engineering discipline.
Next time, we’ll choose differently.
Jeremy Gajewski is the founder of multiple tech startups and currently building VoiceCatcher, an AI-powered voice transcription tool. He writes about the intersection of product management, technical architecture, and user experience. Connect with him on Twitter/X for more thoughts on building better software.